From 6432025b6533c81c3957b4d09dc45401f08bca1a Mon Sep 17 00:00:00 2001 From: ustaxs Date: Mon, 22 Jun 2026 21:02:14 +0100 Subject: [PATCH] Implement Transaction Tracking & History --- package-lock.json | 143 ++++------ package.json | 3 +- src/app.controller.ts | 3 - src/app.module.ts | 3 - src/app.service.ts | 3 - .../oracle/dto/create-payload.dto.ts | 3 - .../oracle/dto/payload-response.dto.ts | 3 - src/blockchain/oracle/dto/sign-payload.dto.ts | 3 - .../oracle/dto/submit-payload.dto.ts | 3 - .../oracle/dto/verify-signature.dto.ts | 3 - .../oracle/entities/signed-payload.entity.ts | 3 - .../entities/submission-nonce.entity.ts | 3 - src/blockchain/oracle/oracle.controller.ts | 3 - src/blockchain/oracle/oracle.module.ts | 3 - .../services/nonce-management.service.ts | 3 - .../oracle/services/oracle.service.ts | 3 - .../services/payload-signing.service.ts | 3 - .../services/submission-batch.service.spec.ts | 3 - .../services/submission-batch.service.ts | 3 - .../oracle/services/submitter.service.ts | 3 - .../oracle/submission-verifier.controller.ts | 3 - .../oracle/submission-verifier.e2e-spec.ts | 3 - .../oracle/submission-verifier.service.ts | 3 - src/common/database/database-index.service.ts | 3 - src/common/decorators/public.decorator.ts | 3 - src/common/decorators/rate-limit.decorator.ts | 3 - src/common/decorators/roles.decorator.ts | 3 - src/common/decorators/skip-kyc.decorator.ts | 3 - src/common/filters/global-exception.filter.ts | 3 - src/common/guard/kyc.guard.ts | 3 - src/common/guard/nonce.guard.spec.ts | 3 - src/common/guard/nonce.guard.ts | 3 - src/common/guard/quota.guard.spec.ts | 3 - src/common/guard/quota.guard.ts | 3 - src/common/guard/role-separation.spec.ts | 3 - src/common/guard/roles.decorator.ts | 3 - src/common/guard/roles.enum.ts | 3 - src/common/guard/roles.guard.spec.ts | 3 - src/common/guard/roles.guard.ts | 3 - src/common/guard/throttler.guard.ts | 3 - src/common/index.ts | 3 - src/common/middleware/logging.config.ts | 3 - .../middleware/logging.middleware.spec.ts | 3 - src/common/middleware/logging.middleware.ts | 3 - src/common/middleware/sentry.middleware.ts | 3 - .../pagination/cursor-pagination.dto.ts | 3 - .../cursor-pagination.service.spec.ts | 3 - .../pagination/cursor-pagination.service.ts | 3 - src/common/pagination/pagination.module.ts | 3 - src/common/pipes/sanitize.pipe.ts | 3 - .../sensitive-throttler.guard.spec.ts | 3 - .../throttler/sensitive-throttler.guard.ts | 3 - src/config/cors.config.ts | 3 - src/config/env.validation.ts | 3 - src/config/helmet.config.ts | 3 - src/config/logger.ts | 3 - src/config/metrics.ts | 3 - src/config/nest-pino-logger.ts | 3 - src/config/quota.config.ts | 3 - src/config/sentry.ts | 3 - src/config/swagger.config.ts | 3 - src/config/tracing.ts | 3 - src/core/auth/auth.controller.ts | 3 - src/core/auth/auth.module.ts | 3 - src/core/auth/auth.service.spec.ts | 3 - src/core/auth/auth.service.ts | 3 - src/core/auth/challenge.service.ts | 3 - .../allowed-strategies.decorator.ts | 3 - .../auth/decorators/auth-type.decorator.ts | 3 - .../auth/decorators/current-user.decorator.ts | 3 - src/core/auth/decorators/index.ts | 3 - src/core/auth/delegation.service.ts | 3 - src/core/auth/dto/auth.dto.ts | 3 - src/core/auth/dto/kyc.dto.ts | 3 - src/core/auth/dto/link-email.dto.ts | 3 - src/core/auth/dto/link-wallet.dto.ts | 3 - src/core/auth/dto/recover-wallet.dto.ts | 3 - src/core/auth/dto/request-recovery.dto.ts | 3 - src/core/auth/dto/unlink-wallet.dto.ts | 3 - src/core/auth/dto/verify-email.dto.ts | 3 - src/core/auth/email-linking.service.ts | 3 - src/core/auth/email.service.ts | 3 - src/core/auth/enhanced-auth.controller.ts | 3 - src/core/auth/enhanced-auth.service.spec.ts | 3 - src/core/auth/enhanced-auth.service.ts | 3 - src/core/auth/entities/auth.entity.ts | 3 - .../entities/email-verification.entity.ts | 3 - src/core/auth/entities/wallet.entity.ts | 3 - .../guards/admin-two-factor.guard.spec.ts | 3 - .../auth/guards/admin-two-factor.guard.ts | 3 - src/core/auth/guards/jwt-auth.guard.ts | 3 - src/core/auth/guards/strategy-auth.guard.ts | 3 - src/core/auth/jwt.guard.ts | 3 - src/core/auth/jwt.strategy.ts | 3 - src/core/auth/kyc.guard.ts | 3 - src/core/auth/recovery.service.ts | 3 - src/core/auth/session-recovery.service.ts | 3 - .../strategies/api-key/api-key.strategy.ts | 3 - src/core/auth/strategies/index.ts | 3 - .../interfaces/auth-strategy.interface.ts | 3 - .../auth/strategies/oauth/oauth.strategy.ts | 3 - src/core/auth/strategies/strategy.registry.ts | 3 - .../traditional/traditional.strategy.ts | 3 - .../auth/strategies/wallet/wallet.strategy.ts | 3 - src/core/auth/strategy-auth.service.ts | 3 - src/core/auth/token-blacklist.service.ts | 3 - src/core/auth/wallet-auth.service.spec.ts | 3 - src/core/auth/wallet-auth.service.ts | 3 - src/core/profile/dto/create-profile.dto.ts | 3 - src/core/profile/dto/update-profile.dto.ts | 3 - src/core/profile/entities/profile.entity.ts | 3 - src/core/profile/profile.controller.ts | 3 - src/core/profile/profile.module.ts | 3 - src/core/profile/profile.service.ts | 3 - src/core/user/dto/create-user.dto.ts | 3 - src/core/user/dto/update-user.dto.ts | 3 - src/core/user/entities/user.entity.ts | 3 - src/core/user/user-role-separation.spec.ts | 3 - src/core/user/user.controller.ts | 3 - src/core/user/user.module.ts | 3 - src/core/user/user.service.ts | 3 - src/dashboard/dashboard.controller.ts | 3 - src/dashboard/dashboard.module.ts | 3 - src/dashboard/dashboard.service.ts | 3 - src/dashboard/dto/dashboard.dto.ts | 3 - .../adapters/dashboard-ws-auth.adapter.ts | 3 - src/dashboard/websocket/dashboard.gateway.ts | 3 - .../websocket/filters/ws-exception.filter.ts | 3 - src/dashboard/websocket/index.ts | 3 - .../interfaces/websocket.interfaces.ts | 3 - .../connection-manager.service.spec.ts | 3 - .../services/connection-manager.service.ts | 3 - .../services/connection-pool.service.ts | 3 - .../services/dashboard-client.service.ts | 3 - .../services/dashboard-metrics.service.ts | 3 - .../services/event-buffer.service.spec.ts | 3 - .../services/event-buffer.service.ts | 3 - .../services/reconnection.service.spec.ts | 3 - .../services/reconnection.service.ts | 3 - .../services/websocket-health.service.ts | 3 - .../websocket/websocket.stress.spec.ts | 3 - src/defi/defi.controller.ts | 3 - src/defi/defi.module.ts | 3 - src/defi/dto/defi.dto.ts | 3 - src/defi/dto/yield-strategy.dto.ts | 3 - src/defi/entities/defi-position.entity.ts | 3 - .../entities/defi-risk-assessment.entity.ts | 3 - src/defi/entities/defi-transaction.entity.ts | 3 - src/defi/entities/defi-yield-record.entity.ts | 3 - .../entities/defi-yield-strategy.entity.ts | 3 - src/defi/protocols/aave.adapter.ts | 3 - src/defi/protocols/compound.adapter.ts | 3 - .../protocols/protocol-adapter.interface.ts | 3 - src/defi/protocols/protocol-registry.ts | 3 - .../services/position-tracking.service.ts | 3 - src/defi/services/risk-assessment.service.ts | 3 - .../transaction-optimization.service.ts | 3 - .../services/yield-optimization.service.ts | 3 - src/defi/trade-lock.service.ts | 3 - src/defi/trade.controller.ts | 3 - .../algorithms/agent-scoring.spec.ts | 3 - src/discovery/algorithms/agent-scoring.ts | 3 - src/growth/alerts/alerts.controller.ts | 3 - src/growth/alerts/alerts.module.ts | 3 - src/growth/alerts/alerts.service.spec.ts | 3 - src/growth/alerts/alerts.service.ts | 3 - src/growth/alerts/dto/alert-preference.dto.ts | 3 - src/growth/alerts/dto/alert.dto.ts | 3 - .../entities/alert-preference.entity.ts | 3 - .../entities/alert-trigger-log.entity.ts | 3 - src/growth/alerts/entities/alert.entity.ts | 3 - .../listeners/portfolio-alert.listener.ts | 3 - .../alerts/listeners/risk-alert.listener.ts | 3 - .../services/alert-dispatcher.service.spec.ts | 3 - .../services/alert-dispatcher.service.ts | 3 - .../services/alert-evaluation.service.spec.ts | 3 - .../services/alert-evaluation.service.ts | 3 - src/health/dto/health-response.dto.ts | 3 - src/health/health.constants.ts | 3 - src/health/health.controller.spec.ts | 3 - src/health/health.controller.ts | 3 - src/health/health.module.ts | 3 - src/health/health.service.spec.ts | 3 - src/health/health.service.ts | 3 - .../ai-compute/provider-failover.spec.ts | 3 - .../ai-compute/provider-failover.ts | 3 - src/infrastructure/audit/audit-log.service.ts | 3 - src/infrastructure/audit/audit.module.ts | 3 - .../audit/dto/create-provenance-record.dto.ts | 3 - .../audit/dto/provenance-response.dto.ts | 3 - .../audit/dto/query-provenance.dto.ts | 3 - .../audit/entities/agent-event.entity.ts | 3 - .../audit/entities/compute-result.entity.ts | 3 - .../entities/oracle-submission.entity.ts | 3 - .../entities/provenance-record.entity.ts | 3 - .../audit/guards/provenance-access.guard.ts | 3 - .../audit/provenance.controller.ts | 3 - .../audit/provenance.service.ts | 3 - .../portfolio/algorithms/black-litterman.ts | 3 - .../algorithms/constraint-optimizer.ts | 3 - .../algorithms/modern-portfolio-theory.ts | 3 - .../performance-calculations.spec.ts | 3 - .../algorithms/performance-calculations.ts | 3 - .../algorithms/portfolio-validation.spec.ts | 3 - .../algorithms/portfolio-validation.ts | 3 - .../controllers/transaction.controller.ts | 62 +++++ src/investment/portfolio/dto/api-error.dto.ts | 3 - src/investment/portfolio/dto/backtest.dto.ts | 3 - .../portfolio/dto/optimization.dto.ts | 3 - .../portfolio/dto/performance.dto.ts | 3 - .../portfolio/dto/portfolio-asset.dto.ts | 3 - .../portfolio/dto/portfolio-management.dto.ts | 3 - src/investment/portfolio/dto/portfolio.dto.ts | 3 - .../portfolio/dto/rebalancing.dto.ts | 3 - .../portfolio/dto/risk-profile.dto.ts | 3 - .../portfolio/dto/transaction.dto.ts | 67 +++++ .../entities/backtest-result.entity.ts | 3 - .../entities/optimization-history.entity.ts | 3 - .../entities/performance-metric.entity.ts | 3 - .../entities/portfolio-asset.entity.ts | 3 - .../portfolio/entities/portfolio.entity.ts | 3 - .../entities/rebalancing-event.entity.ts | 3 - .../portfolio/entities/risk-profile.entity.ts | 3 - .../portfolio/entities/transaction.entity.ts | 26 +- .../portfolio/guards/portfolio-owner.guard.ts | 3 - .../portfolio/ml-models/predictor.ts | 3 - .../portfolio-management.controller.ts | 3 - .../portfolio/portfolio.controller.ts | 3 - src/investment/portfolio/portfolio.module.ts | 10 +- .../portfolio/services/backtesting.service.ts | 3 - .../services/ml-prediction.service.ts | 3 - .../performance-analytics.service.spec.ts | 3 - .../services/performance-analytics.service.ts | 3 - .../portfolio-constraint.service.spec.ts | 3 - .../services/portfolio-constraint.service.ts | 3 - .../portfolio/services/portfolio.service.ts | 3 - .../portfolio/services/rebalancing.service.ts | 3 - .../services/trading-transaction.service.ts | 187 ++++++++++++- .../circuit-breaker.service.spec.ts | 3 - .../circuit-breaker.service.ts | 3 - .../risk-management/dto/risk.dto.ts | 3 - .../risk-management.controller.ts | 3 - .../risk-management/risk-management.health.ts | 3 - .../risk-management/risk-management.module.ts | 3 - .../risk-management.service.ts | 3 - src/main.ts | 3 - .../database-timing.interceptor.ts | 3 - .../monitoring-dashboard.spec.ts | 3 - .../observability.controller.spec.ts | 3 - src/observability/observability.controller.ts | 3 - src/observability/observability.module.ts | 3 - .../performance-baseline.service.ts | 3 - src/observability/profiling.controller.ts | 3 - src/observability/profiling.service.ts | 3 - .../request-timing.middleware.spec.ts | 3 - .../request-timing.middleware.ts | 3 - .../controllers/portfolio.controller.ts | 3 - src/portfolio/portfolio.module.ts | 3 - src/portfolio/services/rebalancing.service.ts | 3 - src/profiling/profiling.controller.ts | 3 - src/profiling/profiling.middleware.ts | 3 - src/profiling/profiling.module.ts | 3 - src/profiling/profiling.service.ts | 3 - src/types/jest-globals.d.ts | 3 - src/types/supertest.d.ts | 3 - test/audit/provenance.e2e-spec.ts | 255 +++++++++--------- test/auth/wallet-advanced.e2e-spec.ts | 202 +++++++------- test/email-linking.spec.ts | 198 ++++++++------ test/oracle-e2e.spec.ts | 200 +++++++------- test/oracle/payload-signing.service.spec.ts | 108 ++++---- test/portfolio/mpt.spec.ts | 179 ++++++------ .../portfolio-management.e2e-spec.ts | 1 - test/portfolio/portfolio.service.spec.ts | 204 +++++++------- test/recovery.spec.ts | 89 +++--- test/traditional-auth.e2e-spec.ts | 174 ++++++------ test/wallet-auth.spec.ts | 69 ++--- test/wallet-management.e2e-spec.ts | 174 ++++++------ tsconfig.json | 6 +- 278 files changed, 1344 insertions(+), 1787 deletions(-) create mode 100644 src/investment/portfolio/controllers/transaction.controller.ts create mode 100644 src/investment/portfolio/dto/transaction.dto.ts diff --git a/package-lock.json b/package-lock.json index 7776472..9adf301 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,6 +50,7 @@ "express": "^5.2.1", "express-mongo-sanitize": "^2.2.0", "express-rate-limit": "^8.2.1", + "fast-csv": "^5.0.7", "helmet": "^8.1.0", "hpp": "^0.2.3", "ioredis": "^5.9.3", @@ -93,7 +94,7 @@ "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^6.17.0", "@typescript-eslint/parser": "^6.17.0", - "eslint": "^8.56.0", + "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.32.0", "eslint-plugin-prettier": "^5.1.2", @@ -1026,11 +1027,30 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fast-csv/format": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-5.0.7.tgz", + "integrity": "sha512-VdypoRxv7PF+LsyPouTMKdB0d76hync+gLpgdNqfqVK44MsgW4oiCJSdrki2FisWT7v2QGUYDHjp4L7w5oO6gw==", + "license": "MIT", + "dependencies": { + "lodash.escaperegexp": "^4.1.2" + } + }, + "node_modules/@fast-csv/parse": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-5.0.7.tgz", + "integrity": "sha512-MeuDeQj0DVmIGWky8STaKtvj232Gpq8kjrOGplHJ99Os7OO5CetVfXvUGqX/3GHYFOUsz6FK8isWtDY2XTFNWw==", + "license": "MIT", + "dependencies": { + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.uniq": "^4.5.0" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "dev": true, "license": "MIT", "optional": true }, @@ -2911,7 +2931,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -2924,7 +2943,6 @@ "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", "deprecated": "This functionality has been moved to @npmcli/fs", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2939,7 +2957,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2952,7 +2969,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -2974,7 +2990,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -2989,7 +3004,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -6392,7 +6406,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -7356,7 +7369,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true, "license": "ISC", "optional": true }, @@ -7453,7 +7465,6 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -7467,7 +7478,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -7659,7 +7669,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", - "dev": true, "license": "ISC", "optional": true }, @@ -7668,7 +7677,6 @@ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", "deprecated": "This package is no longer supported.", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -8392,7 +8400,6 @@ "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -8423,7 +8430,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -8436,7 +8442,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -8458,7 +8463,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -8472,7 +8476,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -8486,7 +8489,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -8501,7 +8503,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -8518,7 +8519,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, "license": "ISC", "optional": true }, @@ -8751,7 +8751,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -8895,7 +8894,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, "license": "ISC", "optional": true, "bin": { @@ -8962,7 +8960,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/concat-stream": { @@ -8990,7 +8988,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true, "license": "ISC", "optional": true }, @@ -9371,7 +9368,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true, "license": "MIT", "optional": true }, @@ -9715,7 +9711,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -9726,7 +9721,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true, "license": "MIT", "optional": true }, @@ -10780,6 +10774,19 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-csv": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-5.0.7.tgz", + "integrity": "sha512-KirK9wYIUXF/xTYmg/jv9ehUWbQ4Faf6XutuJKlEz72eoqKMMLdCnz+rGDeBSjpV2wwkHQnLSuJ5i+FRRnzZwA==", + "license": "MIT", + "dependencies": { + "@fast-csv/format": "5.0.7", + "@fast-csv/parse": "5.0.7" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -11304,7 +11311,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/fsevents": { @@ -11370,7 +11377,6 @@ "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", "deprecated": "This package is no longer supported.", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -11391,7 +11397,6 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, "license": "ISC", "optional": true }, @@ -11708,7 +11713,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/graphemer": { @@ -11841,7 +11846,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true, "license": "ISC", "optional": true }, @@ -11925,7 +11929,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "dev": true, "license": "BSD-2-Clause", "optional": true }, @@ -11953,7 +11956,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -11992,7 +11994,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -12101,7 +12102,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -12111,7 +12112,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -12122,7 +12122,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true, "license": "ISC", "optional": true }, @@ -12131,7 +12130,7 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -12509,7 +12508,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true, "license": "MIT", "optional": true }, @@ -13830,6 +13828,18 @@ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "license": "MIT" }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -13886,6 +13896,12 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -13967,7 +13983,6 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -13996,7 +14011,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -14010,7 +14024,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -14024,7 +14037,6 @@ "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -14035,7 +14047,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, "license": "ISC", "optional": true }, @@ -14253,7 +14264,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -14267,7 +14277,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -14281,7 +14290,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, "license": "ISC", "optional": true }, @@ -14289,7 +14297,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -14308,7 +14315,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -14322,7 +14328,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, "license": "ISC", "optional": true }, @@ -14330,7 +14335,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", - "dev": true, "license": "BlueOak-1.0.0", "optional": true, "dependencies": { @@ -14344,7 +14348,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -14358,7 +14361,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, "license": "ISC", "optional": true }, @@ -14366,7 +14368,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -14380,7 +14381,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -14394,7 +14394,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, "license": "ISC", "optional": true }, @@ -14402,7 +14401,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -14416,7 +14414,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -14430,7 +14427,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, "license": "ISC", "optional": true }, @@ -14679,7 +14675,6 @@ "version": "8.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -14731,7 +14726,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -14744,7 +14738,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -14766,7 +14759,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -14781,7 +14773,6 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -14914,7 +14905,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -14955,7 +14945,6 @@ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", "deprecated": "This package is no longer supported.", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -15277,7 +15266,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -15405,7 +15393,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -15501,7 +15489,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", - "dev": true, "license": "MIT", "optional": true }, @@ -15941,7 +15928,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true, "license": "ISC", "optional": true }, @@ -15949,7 +15935,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -16565,7 +16550,6 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -17187,7 +17171,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -17299,7 +17282,6 @@ "version": "2.8.9", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -17315,7 +17297,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -17447,7 +17428,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -17461,7 +17441,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -17475,7 +17454,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, "license": "ISC", "optional": true }, @@ -18943,7 +18921,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -18954,7 +18931,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { @@ -19382,7 +19358,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, "license": "ISC", "optional": true, "dependencies": { diff --git a/package.json b/package.json index 3f1e726..6dbfe9c 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "express": "^5.2.1", "express-mongo-sanitize": "^2.2.0", "express-rate-limit": "^8.2.1", + "fast-csv": "^5.0.7", "helmet": "^8.1.0", "hpp": "^0.2.3", "ioredis": "^5.9.3", @@ -121,7 +122,7 @@ "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^6.17.0", "@typescript-eslint/parser": "^6.17.0", - "eslint": "^8.56.0", + "eslint": "^8.57.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.32.0", "eslint-plugin-prettier": "^5.1.2", diff --git a/src/app.controller.ts b/src/app.controller.ts index 01532df..0d422e7 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -120,6 +120,3 @@ export class AppController { }; } } - - - diff --git a/src/app.module.ts b/src/app.module.ts index 2c91b7f..a0ab7ee 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -251,6 +251,3 @@ export class AppModule implements NestModule, OnModuleInit { this.verifier.start(); } } - - - diff --git a/src/app.service.ts b/src/app.service.ts index 942e330..4926cb3 100644 --- a/src/app.service.ts +++ b/src/app.service.ts @@ -29,6 +29,3 @@ export class AppService { }; } } - - - diff --git a/src/blockchain/oracle/dto/create-payload.dto.ts b/src/blockchain/oracle/dto/create-payload.dto.ts index 2123720..f14a77f 100644 --- a/src/blockchain/oracle/dto/create-payload.dto.ts +++ b/src/blockchain/oracle/dto/create-payload.dto.ts @@ -36,6 +36,3 @@ export class CreatePayloadDto { @IsOptional() metadata?: Record; } - - - diff --git a/src/blockchain/oracle/dto/payload-response.dto.ts b/src/blockchain/oracle/dto/payload-response.dto.ts index 8c8186e..b383a2d 100644 --- a/src/blockchain/oracle/dto/payload-response.dto.ts +++ b/src/blockchain/oracle/dto/payload-response.dto.ts @@ -24,6 +24,3 @@ export class PayloadResponseDto { submittedAt: Date | null; confirmedAt: Date | null; } - - - diff --git a/src/blockchain/oracle/dto/sign-payload.dto.ts b/src/blockchain/oracle/dto/sign-payload.dto.ts index e7a8dad..5f66bc9 100644 --- a/src/blockchain/oracle/dto/sign-payload.dto.ts +++ b/src/blockchain/oracle/dto/sign-payload.dto.ts @@ -15,6 +15,3 @@ export class SignPayloadDto { }) privateKey: string; } - - - diff --git a/src/blockchain/oracle/dto/submit-payload.dto.ts b/src/blockchain/oracle/dto/submit-payload.dto.ts index d0ff475..76e1eff 100644 --- a/src/blockchain/oracle/dto/submit-payload.dto.ts +++ b/src/blockchain/oracle/dto/submit-payload.dto.ts @@ -14,6 +14,3 @@ export class SubmitPayloadDto { @IsNotEmpty() payloadId: string; } - - - diff --git a/src/blockchain/oracle/dto/verify-signature.dto.ts b/src/blockchain/oracle/dto/verify-signature.dto.ts index d3cb15d..c3f9d54 100644 --- a/src/blockchain/oracle/dto/verify-signature.dto.ts +++ b/src/blockchain/oracle/dto/verify-signature.dto.ts @@ -22,6 +22,3 @@ export class VerifySignatureDto { }) expectedSigner: string; } - - - diff --git a/src/blockchain/oracle/entities/signed-payload.entity.ts b/src/blockchain/oracle/entities/signed-payload.entity.ts index d42f3ee..28f6a39 100644 --- a/src/blockchain/oracle/entities/signed-payload.entity.ts +++ b/src/blockchain/oracle/entities/signed-payload.entity.ts @@ -141,6 +141,3 @@ export class SignedPayload { @Column({ type: "timestamp", nullable: true }) confirmedAt: Date | null; } - - - diff --git a/src/blockchain/oracle/entities/submission-nonce.entity.ts b/src/blockchain/oracle/entities/submission-nonce.entity.ts index 17880a7..b4b84ac 100644 --- a/src/blockchain/oracle/entities/submission-nonce.entity.ts +++ b/src/blockchain/oracle/entities/submission-nonce.entity.ts @@ -41,6 +41,3 @@ export class SubmissionNonce { @UpdateDateColumn() updatedAt: Date; } - - - diff --git a/src/blockchain/oracle/oracle.controller.ts b/src/blockchain/oracle/oracle.controller.ts index aa1af2a..d5cd3c5 100644 --- a/src/blockchain/oracle/oracle.controller.ts +++ b/src/blockchain/oracle/oracle.controller.ts @@ -252,6 +252,3 @@ export class OracleController { }; } } - - - diff --git a/src/blockchain/oracle/oracle.module.ts b/src/blockchain/oracle/oracle.module.ts index 3abe040..cfcf204 100644 --- a/src/blockchain/oracle/oracle.module.ts +++ b/src/blockchain/oracle/oracle.module.ts @@ -41,6 +41,3 @@ import { AuditModule } from "src/infrastructure/audit/audit.module"; ], }) export class OracleModule {} - - - diff --git a/src/blockchain/oracle/services/nonce-management.service.ts b/src/blockchain/oracle/services/nonce-management.service.ts index e4f2c2d..5781ffc 100644 --- a/src/blockchain/oracle/services/nonce-management.service.ts +++ b/src/blockchain/oracle/services/nonce-management.service.ts @@ -267,6 +267,3 @@ export class NonceManagementService { return deletedCount; } } - - - diff --git a/src/blockchain/oracle/services/oracle.service.ts b/src/blockchain/oracle/services/oracle.service.ts index 6af1c21..87760f2 100644 --- a/src/blockchain/oracle/services/oracle.service.ts +++ b/src/blockchain/oracle/services/oracle.service.ts @@ -393,6 +393,3 @@ export class OracleService { }; } } - - - diff --git a/src/blockchain/oracle/services/payload-signing.service.ts b/src/blockchain/oracle/services/payload-signing.service.ts index aba5f0e..426ed1e 100644 --- a/src/blockchain/oracle/services/payload-signing.service.ts +++ b/src/blockchain/oracle/services/payload-signing.service.ts @@ -251,6 +251,3 @@ export class PayloadSigningService { return payloadHash; } } - - - diff --git a/src/blockchain/oracle/services/submission-batch.service.spec.ts b/src/blockchain/oracle/services/submission-batch.service.spec.ts index 44e3b0b..284dba4 100644 --- a/src/blockchain/oracle/services/submission-batch.service.spec.ts +++ b/src/blockchain/oracle/services/submission-batch.service.spec.ts @@ -446,6 +446,3 @@ describe("SubmissionBatchService - Exponential Backoff", () => { expect(result.attemptNumber).toBe(1); }); }); - - - diff --git a/src/blockchain/oracle/services/submission-batch.service.ts b/src/blockchain/oracle/services/submission-batch.service.ts index a54a3f0..da6db45 100644 --- a/src/blockchain/oracle/services/submission-batch.service.ts +++ b/src/blockchain/oracle/services/submission-batch.service.ts @@ -747,6 +747,3 @@ export class SubmissionBatchService { return new Promise((resolve) => setTimeout(resolve, ms)); } } - - - diff --git a/src/blockchain/oracle/services/submitter.service.ts b/src/blockchain/oracle/services/submitter.service.ts index 8e370e0..c424ca0 100644 --- a/src/blockchain/oracle/services/submitter.service.ts +++ b/src/blockchain/oracle/services/submitter.service.ts @@ -697,6 +697,3 @@ export class SubmitterService { }; } } - - - diff --git a/src/blockchain/oracle/submission-verifier.controller.ts b/src/blockchain/oracle/submission-verifier.controller.ts index fcc1719..36eaabe 100644 --- a/src/blockchain/oracle/submission-verifier.controller.ts +++ b/src/blockchain/oracle/submission-verifier.controller.ts @@ -21,6 +21,3 @@ export class SubmissionVerifierController { return this.audit.getLogs(); } } - - - diff --git a/src/blockchain/oracle/submission-verifier.e2e-spec.ts b/src/blockchain/oracle/submission-verifier.e2e-spec.ts index a1ba9d2..ddf0ad1 100644 --- a/src/blockchain/oracle/submission-verifier.e2e-spec.ts +++ b/src/blockchain/oracle/submission-verifier.e2e-spec.ts @@ -19,6 +19,3 @@ describe("Submission Verifier", () => { expect(Array.isArray(res.body)).toBe(true); }); }); - - - diff --git a/src/blockchain/oracle/submission-verifier.service.ts b/src/blockchain/oracle/submission-verifier.service.ts index bd087a3..87ef32d 100644 --- a/src/blockchain/oracle/submission-verifier.service.ts +++ b/src/blockchain/oracle/submission-verifier.service.ts @@ -150,6 +150,3 @@ export class SubmissionVerifierService { }; } } - - - diff --git a/src/common/database/database-index.service.ts b/src/common/database/database-index.service.ts index 3585ccd..ada38ef 100644 --- a/src/common/database/database-index.service.ts +++ b/src/common/database/database-index.service.ts @@ -281,6 +281,3 @@ export class DatabaseIndexService { } } } - - - diff --git a/src/common/decorators/public.decorator.ts b/src/common/decorators/public.decorator.ts index 0ac145e..6b72f1f 100644 --- a/src/common/decorators/public.decorator.ts +++ b/src/common/decorators/public.decorator.ts @@ -12,6 +12,3 @@ export const IS_PUBLIC_KEY = "isPublic"; * healthCheck() { ... } */ export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); - - - diff --git a/src/common/decorators/rate-limit.decorator.ts b/src/common/decorators/rate-limit.decorator.ts index 7661b76..419d36d 100644 --- a/src/common/decorators/rate-limit.decorator.ts +++ b/src/common/decorators/rate-limit.decorator.ts @@ -42,6 +42,3 @@ export function SensitiveRateLimit(tier: SensitiveTier = "default") { export function RateLimit(options: RateLimitOptions) { return applyDecorators(SetMetadata(RATE_LIMIT_KEY, options)); } - - - diff --git a/src/common/decorators/roles.decorator.ts b/src/common/decorators/roles.decorator.ts index d363b48..4c228fb 100644 --- a/src/common/decorators/roles.decorator.ts +++ b/src/common/decorators/roles.decorator.ts @@ -1,6 +1,3 @@ // Re-export from canonical location in guard/ to maintain backward compatibility export * from "../guard/roles.decorator"; export * from "../guard/roles.enum"; - - - diff --git a/src/common/decorators/skip-kyc.decorator.ts b/src/common/decorators/skip-kyc.decorator.ts index 19327c9..a62e18c 100644 --- a/src/common/decorators/skip-kyc.decorator.ts +++ b/src/common/decorators/skip-kyc.decorator.ts @@ -7,6 +7,3 @@ export const SKIP_KYC_KEY = "skipKyc"; * Use only for onboarding flows that are required to become KYC verified. */ export const SkipKyc = () => SetMetadata(SKIP_KYC_KEY, true); - - - diff --git a/src/common/filters/global-exception.filter.ts b/src/common/filters/global-exception.filter.ts index 6c4dd9f..f18b853 100644 --- a/src/common/filters/global-exception.filter.ts +++ b/src/common/filters/global-exception.filter.ts @@ -105,6 +105,3 @@ export class GlobalExceptionFilter implements ExceptionFilter { }); } } - - - diff --git a/src/common/guard/kyc.guard.ts b/src/common/guard/kyc.guard.ts index b591299..f14b3cb 100644 --- a/src/common/guard/kyc.guard.ts +++ b/src/common/guard/kyc.guard.ts @@ -37,6 +37,3 @@ export class KycGuard implements CanActivate { return user?.kycVerified === true; } } - - - diff --git a/src/common/guard/nonce.guard.spec.ts b/src/common/guard/nonce.guard.spec.ts index 5b19e7c..f19bf64 100644 --- a/src/common/guard/nonce.guard.spec.ts +++ b/src/common/guard/nonce.guard.spec.ts @@ -92,6 +92,3 @@ describe("NonceGuard", () => { ).rejects.toThrow(ConflictException); }); }); - - - diff --git a/src/common/guard/nonce.guard.ts b/src/common/guard/nonce.guard.ts index bee6f14..51b1c44 100644 --- a/src/common/guard/nonce.guard.ts +++ b/src/common/guard/nonce.guard.ts @@ -56,6 +56,3 @@ export class NonceGuard implements CanActivate { return true; } } - - - diff --git a/src/common/guard/quota.guard.spec.ts b/src/common/guard/quota.guard.spec.ts index 249480c..f0b1437 100644 --- a/src/common/guard/quota.guard.spec.ts +++ b/src/common/guard/quota.guard.spec.ts @@ -127,6 +127,3 @@ describe("QuotaGuard", () => { // ); // }); }); - - - diff --git a/src/common/guard/quota.guard.ts b/src/common/guard/quota.guard.ts index 289c78d..9a3a749 100644 --- a/src/common/guard/quota.guard.ts +++ b/src/common/guard/quota.guard.ts @@ -285,6 +285,3 @@ export class QuotaGuard implements CanActivate { return `ip:${ip || "unknown"}`; } } - - - diff --git a/src/common/guard/role-separation.spec.ts b/src/common/guard/role-separation.spec.ts index 613cb90..a6ed82f 100644 --- a/src/common/guard/role-separation.spec.ts +++ b/src/common/guard/role-separation.spec.ts @@ -90,6 +90,3 @@ describe("Role Separation — Governance cannot access KYC endpoints and vice ve }); }); }); - - - diff --git a/src/common/guard/roles.decorator.ts b/src/common/guard/roles.decorator.ts index 8bee32a..9b20ae6 100644 --- a/src/common/guard/roles.decorator.ts +++ b/src/common/guard/roles.decorator.ts @@ -25,6 +25,3 @@ export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles); * remove() { ... } */ export const RequireRole = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles); - - - diff --git a/src/common/guard/roles.enum.ts b/src/common/guard/roles.enum.ts index 744be09..98ade7f 100644 --- a/src/common/guard/roles.enum.ts +++ b/src/common/guard/roles.enum.ts @@ -55,6 +55,3 @@ export function hasRole(candidate: Role, required: Role): boolean { return candidateIdx >= requiredIdx; } - - - diff --git a/src/common/guard/roles.guard.spec.ts b/src/common/guard/roles.guard.spec.ts index 22e1cfe..6d715cd 100644 --- a/src/common/guard/roles.guard.spec.ts +++ b/src/common/guard/roles.guard.spec.ts @@ -161,6 +161,3 @@ describe("RolesGuard", () => { }); }); }); - - - diff --git a/src/common/guard/roles.guard.ts b/src/common/guard/roles.guard.ts index c8dd728..3b188d8 100644 --- a/src/common/guard/roles.guard.ts +++ b/src/common/guard/roles.guard.ts @@ -75,6 +75,3 @@ export class RolesGuard implements CanActivate { return true; } } - - - diff --git a/src/common/guard/throttler.guard.ts b/src/common/guard/throttler.guard.ts index 4ea2cd1..ef342e4 100644 --- a/src/common/guard/throttler.guard.ts +++ b/src/common/guard/throttler.guard.ts @@ -32,6 +32,3 @@ export class ThrottlerUserIpGuard extends ThrottlerGuard { return req.ip ?? "unknown"; } } - - - diff --git a/src/common/index.ts b/src/common/index.ts index 778a585..295353c 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -26,6 +26,3 @@ export { SENSITIVE_BODY_FIELDS, REQUEST_ID_HEADER, } from "./middleware/logging.config"; - - - diff --git a/src/common/middleware/logging.config.ts b/src/common/middleware/logging.config.ts index ae9a731..9c4dc88 100644 --- a/src/common/middleware/logging.config.ts +++ b/src/common/middleware/logging.config.ts @@ -72,6 +72,3 @@ export const DEFAULT_LOGGING_CONFIG: Required = { }; export const REQUEST_ID_HEADER = "x-request-id"; - - - diff --git a/src/common/middleware/logging.middleware.spec.ts b/src/common/middleware/logging.middleware.spec.ts index a10e182..759184c 100644 --- a/src/common/middleware/logging.middleware.spec.ts +++ b/src/common/middleware/logging.middleware.spec.ts @@ -464,6 +464,3 @@ describe("LoggingMiddleware – performance", () => { expect(avgMs).toBeLessThan(2); }); }); - - - diff --git a/src/common/middleware/logging.middleware.ts b/src/common/middleware/logging.middleware.ts index 551700f..3d2c178 100644 --- a/src/common/middleware/logging.middleware.ts +++ b/src/common/middleware/logging.middleware.ts @@ -152,6 +152,3 @@ export class LoggingMiddleware implements NestMiddleware { return contentLength > this.cfg.maxBodySize; } } - - - diff --git a/src/common/middleware/sentry.middleware.ts b/src/common/middleware/sentry.middleware.ts index ccc00ad..2c953b0 100644 --- a/src/common/middleware/sentry.middleware.ts +++ b/src/common/middleware/sentry.middleware.ts @@ -25,6 +25,3 @@ export const sentryBreadcrumbMiddleware = ( next(); }; - - - diff --git a/src/common/pagination/cursor-pagination.dto.ts b/src/common/pagination/cursor-pagination.dto.ts index 7fc8d3a..19d20fa 100644 --- a/src/common/pagination/cursor-pagination.dto.ts +++ b/src/common/pagination/cursor-pagination.dto.ts @@ -34,6 +34,3 @@ export interface CursorOptions { orderBy: string; orderDirection: "ASC" | "DESC"; } - - - diff --git a/src/common/pagination/cursor-pagination.service.spec.ts b/src/common/pagination/cursor-pagination.service.spec.ts index 90df2fc..7d662af 100644 --- a/src/common/pagination/cursor-pagination.service.spec.ts +++ b/src/common/pagination/cursor-pagination.service.spec.ts @@ -212,6 +212,3 @@ describe("CursorPaginationService", () => { }); }); }); - - - diff --git a/src/common/pagination/cursor-pagination.service.ts b/src/common/pagination/cursor-pagination.service.ts index ae406c6..38b6f9f 100644 --- a/src/common/pagination/cursor-pagination.service.ts +++ b/src/common/pagination/cursor-pagination.service.ts @@ -153,6 +153,3 @@ export class CursorPaginationService { } } } - - - diff --git a/src/common/pagination/pagination.module.ts b/src/common/pagination/pagination.module.ts index e0ca3ae..b54b0d8 100644 --- a/src/common/pagination/pagination.module.ts +++ b/src/common/pagination/pagination.module.ts @@ -6,6 +6,3 @@ import { CursorPaginationService } from "./cursor-pagination.service"; exports: [CursorPaginationService], }) export class PaginationModule {} - - - diff --git a/src/common/pipes/sanitize.pipe.ts b/src/common/pipes/sanitize.pipe.ts index 9f87bf8..b384cb7 100644 --- a/src/common/pipes/sanitize.pipe.ts +++ b/src/common/pipes/sanitize.pipe.ts @@ -45,6 +45,3 @@ export class SanitizePipe implements PipeTransform { ); } } - - - diff --git a/src/common/throttler/sensitive-throttler.guard.spec.ts b/src/common/throttler/sensitive-throttler.guard.spec.ts index db1f672..47ba543 100644 --- a/src/common/throttler/sensitive-throttler.guard.spec.ts +++ b/src/common/throttler/sensitive-throttler.guard.spec.ts @@ -80,6 +80,3 @@ describe("SensitiveThrottlerGuard", () => { }); }); }); - - - diff --git a/src/common/throttler/sensitive-throttler.guard.ts b/src/common/throttler/sensitive-throttler.guard.ts index 4eade07..7dafc9b 100644 --- a/src/common/throttler/sensitive-throttler.guard.ts +++ b/src/common/throttler/sensitive-throttler.guard.ts @@ -56,6 +56,3 @@ export class SensitiveThrottlerGuard extends ThrottlerGuard { return false; } } - - - diff --git a/src/config/cors.config.ts b/src/config/cors.config.ts index 11c0e28..2e0c010 100644 --- a/src/config/cors.config.ts +++ b/src/config/cors.config.ts @@ -22,6 +22,3 @@ export function createCorsConfig(configService: ConfigService): CorsOptions { maxAge: 3600, }; } - - - diff --git a/src/config/env.validation.ts b/src/config/env.validation.ts index f09e447..93c49c1 100644 --- a/src/config/env.validation.ts +++ b/src/config/env.validation.ts @@ -245,6 +245,3 @@ export class EnvironmentVariables { @Transform(({ value }) => value === "true") REFERRAL_ENABLE_VPN_DETECTION?: boolean = false; } - - - diff --git a/src/config/helmet.config.ts b/src/config/helmet.config.ts index 32ece08..fcef4b4 100644 --- a/src/config/helmet.config.ts +++ b/src/config/helmet.config.ts @@ -21,6 +21,3 @@ export function createHelmetConfig(): HelmetOptions { }, }; } - - - diff --git a/src/config/logger.ts b/src/config/logger.ts index eb5539a..ec8e679 100644 --- a/src/config/logger.ts +++ b/src/config/logger.ts @@ -31,6 +31,3 @@ export const logger = pino({ export const createLogger = (context: Record) => { return logger.child(context); }; - - - diff --git a/src/config/metrics.ts b/src/config/metrics.ts index 096bb11..21097c7 100644 --- a/src/config/metrics.ts +++ b/src/config/metrics.ts @@ -96,6 +96,3 @@ export const queueLength = new client.Gauge({ labelNames: ["queue_name", "state"], registers: [register], }); - - - diff --git a/src/config/nest-pino-logger.ts b/src/config/nest-pino-logger.ts index 63af924..f91e61c 100644 --- a/src/config/nest-pino-logger.ts +++ b/src/config/nest-pino-logger.ts @@ -46,6 +46,3 @@ export class PinoLogger implements LoggerService { logger[level](logMessage); } } - - - diff --git a/src/config/quota.config.ts b/src/config/quota.config.ts index 4ea8ecf..e1f1d34 100644 --- a/src/config/quota.config.ts +++ b/src/config/quota.config.ts @@ -33,6 +33,3 @@ export const QUOTA_LEVELS: Record = { }; export const DEFAULT_QUOTA = QUOTA_LEVELS.free; - - - diff --git a/src/config/sentry.ts b/src/config/sentry.ts index 135f2a4..f09c29a 100644 --- a/src/config/sentry.ts +++ b/src/config/sentry.ts @@ -72,6 +72,3 @@ export const addBreadcrumb = (breadcrumb: Sentry.Breadcrumb) => { Sentry.addBreadcrumb(breadcrumb); } }; - - - diff --git a/src/config/swagger.config.ts b/src/config/swagger.config.ts index a590b1e..751eac3 100644 --- a/src/config/swagger.config.ts +++ b/src/config/swagger.config.ts @@ -77,6 +77,3 @@ export function setupSwagger(app: INestApplication): void { }, }); } - - - diff --git a/src/config/tracing.ts b/src/config/tracing.ts index 2247c7c..1a04088 100644 --- a/src/config/tracing.ts +++ b/src/config/tracing.ts @@ -78,6 +78,3 @@ export const createSpan = async ( } }); }; - - - diff --git a/src/core/auth/auth.controller.ts b/src/core/auth/auth.controller.ts index 8ddffbd..1b7a6f0 100644 --- a/src/core/auth/auth.controller.ts +++ b/src/core/auth/auth.controller.ts @@ -490,6 +490,3 @@ export class AuthController { }; } } - - - diff --git a/src/core/auth/auth.module.ts b/src/core/auth/auth.module.ts index 39c83c5..7897472 100644 --- a/src/core/auth/auth.module.ts +++ b/src/core/auth/auth.module.ts @@ -143,6 +143,3 @@ export class AuthModule implements OnModuleInit { this.strategyRegistry.register(this.apiKeyStrategy); } } - - - diff --git a/src/core/auth/auth.service.spec.ts b/src/core/auth/auth.service.spec.ts index d560206..1b65b2b 100644 --- a/src/core/auth/auth.service.spec.ts +++ b/src/core/auth/auth.service.spec.ts @@ -261,6 +261,3 @@ describe("AuthService", () => { }); }); }); - - - diff --git a/src/core/auth/auth.service.ts b/src/core/auth/auth.service.ts index c1c38ca..f8b01d6 100644 --- a/src/core/auth/auth.service.ts +++ b/src/core/auth/auth.service.ts @@ -185,6 +185,3 @@ export class AuthService { }; } } - - - diff --git a/src/core/auth/challenge.service.ts b/src/core/auth/challenge.service.ts index 74a81ab..79819e7 100644 --- a/src/core/auth/challenge.service.ts +++ b/src/core/auth/challenge.service.ts @@ -61,6 +61,3 @@ export class ChallengeService { return match ? match[1] : null; } } - - - diff --git a/src/core/auth/decorators/allowed-strategies.decorator.ts b/src/core/auth/decorators/allowed-strategies.decorator.ts index e589a8c..7b1e669 100644 --- a/src/core/auth/decorators/allowed-strategies.decorator.ts +++ b/src/core/auth/decorators/allowed-strategies.decorator.ts @@ -15,6 +15,3 @@ export const ALLOWED_STRATEGIES_KEY = "allowedStrategies"; */ export const AllowedStrategies = (...strategies: AuthType[]) => SetMetadata(ALLOWED_STRATEGIES_KEY, strategies); - - - diff --git a/src/core/auth/decorators/auth-type.decorator.ts b/src/core/auth/decorators/auth-type.decorator.ts index a3bfcf1..3e88b8a 100644 --- a/src/core/auth/decorators/auth-type.decorator.ts +++ b/src/core/auth/decorators/auth-type.decorator.ts @@ -16,6 +16,3 @@ export const AuthType = createParamDecorator( return request.authType as AuthTypeEnum; }, ); - - - diff --git a/src/core/auth/decorators/current-user.decorator.ts b/src/core/auth/decorators/current-user.decorator.ts index ddddcbe..b4b7bcc 100644 --- a/src/core/auth/decorators/current-user.decorator.ts +++ b/src/core/auth/decorators/current-user.decorator.ts @@ -25,6 +25,3 @@ export const CurrentUser = createParamDecorator( return data ? user[data] : user; }, ); - - - diff --git a/src/core/auth/decorators/index.ts b/src/core/auth/decorators/index.ts index 1a0e553..429967a 100644 --- a/src/core/auth/decorators/index.ts +++ b/src/core/auth/decorators/index.ts @@ -1,6 +1,3 @@ export * from "./allowed-strategies.decorator"; export * from "./current-user.decorator"; export * from "./auth-type.decorator"; - - - diff --git a/src/core/auth/delegation.service.ts b/src/core/auth/delegation.service.ts index 8ffb268..e982108 100644 --- a/src/core/auth/delegation.service.ts +++ b/src/core/auth/delegation.service.ts @@ -520,6 +520,3 @@ export class DelegationService { ); } } - - - diff --git a/src/core/auth/dto/auth.dto.ts b/src/core/auth/dto/auth.dto.ts index 09e6525..990fcc3 100644 --- a/src/core/auth/dto/auth.dto.ts +++ b/src/core/auth/dto/auth.dto.ts @@ -125,6 +125,3 @@ export class AuthStatusDto { }) user?: AuthUserDto; } - - - diff --git a/src/core/auth/dto/kyc.dto.ts b/src/core/auth/dto/kyc.dto.ts index 2adae00..08ef93c 100644 --- a/src/core/auth/dto/kyc.dto.ts +++ b/src/core/auth/dto/kyc.dto.ts @@ -21,6 +21,3 @@ export class RefreshTokenDto { @IsNotEmpty() refreshToken: string; } - - - diff --git a/src/core/auth/dto/link-email.dto.ts b/src/core/auth/dto/link-email.dto.ts index 170d350..8df0d0d 100644 --- a/src/core/auth/dto/link-email.dto.ts +++ b/src/core/auth/dto/link-email.dto.ts @@ -11,6 +11,3 @@ export class LinkEmailDto { @IsNotEmpty() email: string; } - - - diff --git a/src/core/auth/dto/link-wallet.dto.ts b/src/core/auth/dto/link-wallet.dto.ts index 45105df..2d26496 100644 --- a/src/core/auth/dto/link-wallet.dto.ts +++ b/src/core/auth/dto/link-wallet.dto.ts @@ -44,6 +44,3 @@ export class LinkWalletDto { @IsOptional() permissions?: string[]; } - - - diff --git a/src/core/auth/dto/recover-wallet.dto.ts b/src/core/auth/dto/recover-wallet.dto.ts index e3a1a17..98f79cb 100644 --- a/src/core/auth/dto/recover-wallet.dto.ts +++ b/src/core/auth/dto/recover-wallet.dto.ts @@ -8,6 +8,3 @@ export class RecoverWalletDto { @Length(64, 64) recoveryToken: string; } - - - diff --git a/src/core/auth/dto/request-recovery.dto.ts b/src/core/auth/dto/request-recovery.dto.ts index 29689d7..52348a4 100644 --- a/src/core/auth/dto/request-recovery.dto.ts +++ b/src/core/auth/dto/request-recovery.dto.ts @@ -4,6 +4,3 @@ export class RequestRecoveryDto { @IsEmail() email: string; } - - - diff --git a/src/core/auth/dto/unlink-wallet.dto.ts b/src/core/auth/dto/unlink-wallet.dto.ts index a10dc5a..e62eabe 100644 --- a/src/core/auth/dto/unlink-wallet.dto.ts +++ b/src/core/auth/dto/unlink-wallet.dto.ts @@ -9,6 +9,3 @@ export class UnlinkWalletDto { @IsUUID() walletId: string; } - - - diff --git a/src/core/auth/dto/verify-email.dto.ts b/src/core/auth/dto/verify-email.dto.ts index 2a91f6f..94b85d9 100644 --- a/src/core/auth/dto/verify-email.dto.ts +++ b/src/core/auth/dto/verify-email.dto.ts @@ -12,6 +12,3 @@ export class VerifyEmailDto { @IsNotEmpty() token: string; } - - - diff --git a/src/core/auth/email-linking.service.ts b/src/core/auth/email-linking.service.ts index 52ae59b..1bcd8af 100644 --- a/src/core/auth/email-linking.service.ts +++ b/src/core/auth/email-linking.service.ts @@ -216,6 +216,3 @@ export class EmailLinkingService { }); } } - - - diff --git a/src/core/auth/email.service.ts b/src/core/auth/email.service.ts index 028ca3f..d9be22a 100644 --- a/src/core/auth/email.service.ts +++ b/src/core/auth/email.service.ts @@ -316,6 +316,3 @@ export class EmailService { }; } } - - - diff --git a/src/core/auth/enhanced-auth.controller.ts b/src/core/auth/enhanced-auth.controller.ts index 096c6a5..0bf098d 100644 --- a/src/core/auth/enhanced-auth.controller.ts +++ b/src/core/auth/enhanced-auth.controller.ts @@ -246,6 +246,3 @@ export class EnhancedAuthController { return this.enhancedAuthService.getTwoFactorStatus(req.user.sub); } } - - - diff --git a/src/core/auth/enhanced-auth.service.spec.ts b/src/core/auth/enhanced-auth.service.spec.ts index a05d51b..414d530 100644 --- a/src/core/auth/enhanced-auth.service.spec.ts +++ b/src/core/auth/enhanced-auth.service.spec.ts @@ -349,6 +349,3 @@ describe("EnhancedAuthService — 2FA", () => { }); }); }); - - - diff --git a/src/core/auth/enhanced-auth.service.ts b/src/core/auth/enhanced-auth.service.ts index dfad1c6..9f53b2f 100644 --- a/src/core/auth/enhanced-auth.service.ts +++ b/src/core/auth/enhanced-auth.service.ts @@ -647,6 +647,3 @@ export class EnhancedAuthService { return this.userRepository.findOne({ where: { id: userId } }); } } - - - diff --git a/src/core/auth/entities/auth.entity.ts b/src/core/auth/entities/auth.entity.ts index 75d0683..83d914b 100644 --- a/src/core/auth/entities/auth.entity.ts +++ b/src/core/auth/entities/auth.entity.ts @@ -119,6 +119,3 @@ export class TwoFactorAuth { @UpdateDateColumn() updatedAt: Date; } - - - diff --git a/src/core/auth/entities/email-verification.entity.ts b/src/core/auth/entities/email-verification.entity.ts index c5fbce0..94cf411 100644 --- a/src/core/auth/entities/email-verification.entity.ts +++ b/src/core/auth/entities/email-verification.entity.ts @@ -28,6 +28,3 @@ export class EmailVerification { @CreateDateColumn() createdAt: Date; } - - - diff --git a/src/core/auth/entities/wallet.entity.ts b/src/core/auth/entities/wallet.entity.ts index d26b414..10f8237 100644 --- a/src/core/auth/entities/wallet.entity.ts +++ b/src/core/auth/entities/wallet.entity.ts @@ -165,6 +165,3 @@ export class Wallet { @UpdateDateColumn() updatedAt: Date; } - - - diff --git a/src/core/auth/guards/admin-two-factor.guard.spec.ts b/src/core/auth/guards/admin-two-factor.guard.spec.ts index 8b3dc63..3773c47 100644 --- a/src/core/auth/guards/admin-two-factor.guard.spec.ts +++ b/src/core/auth/guards/admin-two-factor.guard.spec.ts @@ -74,6 +74,3 @@ describe("AdminTwoFactorGuard", () => { expect(isTwoFactorEnabled).toHaveBeenCalledWith("admin-2"); }); }); - - - diff --git a/src/core/auth/guards/admin-two-factor.guard.ts b/src/core/auth/guards/admin-two-factor.guard.ts index 70364a6..045f22a 100644 --- a/src/core/auth/guards/admin-two-factor.guard.ts +++ b/src/core/auth/guards/admin-two-factor.guard.ts @@ -105,6 +105,3 @@ export class AdminTwoFactorGuard implements CanActivate { return null; } } - - - diff --git a/src/core/auth/guards/jwt-auth.guard.ts b/src/core/auth/guards/jwt-auth.guard.ts index 38af00a..7367108 100644 --- a/src/core/auth/guards/jwt-auth.guard.ts +++ b/src/core/auth/guards/jwt-auth.guard.ts @@ -1,4 +1 @@ export { JwtAuthGuard } from "../jwt.guard"; - - - diff --git a/src/core/auth/guards/strategy-auth.guard.ts b/src/core/auth/guards/strategy-auth.guard.ts index 065297f..daf42f7 100644 --- a/src/core/auth/guards/strategy-auth.guard.ts +++ b/src/core/auth/guards/strategy-auth.guard.ts @@ -136,6 +136,3 @@ export class StrategyAuthGuard implements CanActivate { }; } } - - - diff --git a/src/core/auth/jwt.guard.ts b/src/core/auth/jwt.guard.ts index 84b854a..2e81dba 100644 --- a/src/core/auth/jwt.guard.ts +++ b/src/core/auth/jwt.guard.ts @@ -3,6 +3,3 @@ import { AuthGuard } from "@nestjs/passport"; @Injectable() export class JwtAuthGuard extends AuthGuard("jwt") {} - - - diff --git a/src/core/auth/jwt.strategy.ts b/src/core/auth/jwt.strategy.ts index 4e06812..38adac7 100644 --- a/src/core/auth/jwt.strategy.ts +++ b/src/core/auth/jwt.strategy.ts @@ -88,6 +88,3 @@ export class JwtStrategy extends PassportStrategy(Strategy) { } } } - - - diff --git a/src/core/auth/kyc.guard.ts b/src/core/auth/kyc.guard.ts index 3f13f75..06c2967 100644 --- a/src/core/auth/kyc.guard.ts +++ b/src/core/auth/kyc.guard.ts @@ -24,6 +24,3 @@ export class KycGuard implements CanActivate { return user?.kycVerified === true; } } - - - diff --git a/src/core/auth/recovery.service.ts b/src/core/auth/recovery.service.ts index 15c36d5..94aebcf 100644 --- a/src/core/auth/recovery.service.ts +++ b/src/core/auth/recovery.service.ts @@ -72,6 +72,3 @@ export class RecoveryService { }; } } - - - diff --git a/src/core/auth/session-recovery.service.ts b/src/core/auth/session-recovery.service.ts index e05e14a..4d92667 100644 --- a/src/core/auth/session-recovery.service.ts +++ b/src/core/auth/session-recovery.service.ts @@ -500,6 +500,3 @@ export class SessionRecoveryService { // In production, store this in audit log } } - - - diff --git a/src/core/auth/strategies/api-key/api-key.strategy.ts b/src/core/auth/strategies/api-key/api-key.strategy.ts index 3650aa4..c332785 100644 --- a/src/core/auth/strategies/api-key/api-key.strategy.ts +++ b/src/core/auth/strategies/api-key/api-key.strategy.ts @@ -252,6 +252,3 @@ export class ApiKeyStrategy implements AuthStrategy { } } } - - - diff --git a/src/core/auth/strategies/index.ts b/src/core/auth/strategies/index.ts index 715750e..cabe639 100644 --- a/src/core/auth/strategies/index.ts +++ b/src/core/auth/strategies/index.ts @@ -9,6 +9,3 @@ export * from "./wallet/wallet.strategy"; export * from "./traditional/traditional.strategy"; export * from "./oauth/oauth.strategy"; export * from "./api-key/api-key.strategy"; - - - diff --git a/src/core/auth/strategies/interfaces/auth-strategy.interface.ts b/src/core/auth/strategies/interfaces/auth-strategy.interface.ts index 0829ba6..27f8081 100644 --- a/src/core/auth/strategies/interfaces/auth-strategy.interface.ts +++ b/src/core/auth/strategies/interfaces/auth-strategy.interface.ts @@ -158,6 +158,3 @@ export interface ApiKeyCredentials { /** API secret */ apiSecret: string; } - - - diff --git a/src/core/auth/strategies/oauth/oauth.strategy.ts b/src/core/auth/strategies/oauth/oauth.strategy.ts index 5ea4248..f7025a0 100644 --- a/src/core/auth/strategies/oauth/oauth.strategy.ts +++ b/src/core/auth/strategies/oauth/oauth.strategy.ts @@ -279,6 +279,3 @@ export class OAuthStrategy implements AuthStrategy { } } } - - - diff --git a/src/core/auth/strategies/strategy.registry.ts b/src/core/auth/strategies/strategy.registry.ts index e7d9178..daf9880 100644 --- a/src/core/auth/strategies/strategy.registry.ts +++ b/src/core/auth/strategies/strategy.registry.ts @@ -118,6 +118,3 @@ export class StrategyRegistry implements OnModuleInit { this.logger.log("All strategies cleared from registry"); } } - - - diff --git a/src/core/auth/strategies/traditional/traditional.strategy.ts b/src/core/auth/strategies/traditional/traditional.strategy.ts index 093dff6..6aa5fe3 100644 --- a/src/core/auth/strategies/traditional/traditional.strategy.ts +++ b/src/core/auth/strategies/traditional/traditional.strategy.ts @@ -178,6 +178,3 @@ export class TraditionalStrategy implements AuthStrategy { } } } - - - diff --git a/src/core/auth/strategies/wallet/wallet.strategy.ts b/src/core/auth/strategies/wallet/wallet.strategy.ts index 1d3d184..d9f5e0e 100644 --- a/src/core/auth/strategies/wallet/wallet.strategy.ts +++ b/src/core/auth/strategies/wallet/wallet.strategy.ts @@ -131,6 +131,3 @@ export class WalletStrategy implements AuthStrategy { } } } - - - diff --git a/src/core/auth/strategy-auth.service.ts b/src/core/auth/strategy-auth.service.ts index 8adcf0c..e859423 100644 --- a/src/core/auth/strategy-auth.service.ts +++ b/src/core/auth/strategy-auth.service.ts @@ -144,6 +144,3 @@ export class StrategyAuthService { return this.strategyRegistry.get(strategyName); } } - - - diff --git a/src/core/auth/token-blacklist.service.ts b/src/core/auth/token-blacklist.service.ts index f006d20..94500a4 100644 --- a/src/core/auth/token-blacklist.service.ts +++ b/src/core/auth/token-blacklist.service.ts @@ -37,6 +37,3 @@ export class TokenBlacklistService { } } } - - - diff --git a/src/core/auth/wallet-auth.service.spec.ts b/src/core/auth/wallet-auth.service.spec.ts index 22c4d1d..51b6492 100644 --- a/src/core/auth/wallet-auth.service.spec.ts +++ b/src/core/auth/wallet-auth.service.spec.ts @@ -408,6 +408,3 @@ describe("WalletAuthService", () => { }); }); }); - - - diff --git a/src/core/auth/wallet-auth.service.ts b/src/core/auth/wallet-auth.service.ts index 8e19bca..a1fb38d 100644 --- a/src/core/auth/wallet-auth.service.ts +++ b/src/core/auth/wallet-auth.service.ts @@ -550,6 +550,3 @@ export class WalletAuthService { }; } } - - - diff --git a/src/core/profile/dto/create-profile.dto.ts b/src/core/profile/dto/create-profile.dto.ts index 6a90a2f..2913518 100644 --- a/src/core/profile/dto/create-profile.dto.ts +++ b/src/core/profile/dto/create-profile.dto.ts @@ -1,4 +1 @@ export class CreateProfileDto {} - - - diff --git a/src/core/profile/dto/update-profile.dto.ts b/src/core/profile/dto/update-profile.dto.ts index d03eed8..2fe87cc 100644 --- a/src/core/profile/dto/update-profile.dto.ts +++ b/src/core/profile/dto/update-profile.dto.ts @@ -2,6 +2,3 @@ import { PartialType } from "@nestjs/mapped-types"; import { CreateProfileDto } from "./create-profile.dto"; export class UpdateProfileDto extends PartialType(CreateProfileDto) {} - - - diff --git a/src/core/profile/entities/profile.entity.ts b/src/core/profile/entities/profile.entity.ts index 10442af..b4a8829 100644 --- a/src/core/profile/entities/profile.entity.ts +++ b/src/core/profile/entities/profile.entity.ts @@ -1,4 +1 @@ export class Profile {} - - - diff --git a/src/core/profile/profile.controller.ts b/src/core/profile/profile.controller.ts index 321f284..b993c28 100644 --- a/src/core/profile/profile.controller.ts +++ b/src/core/profile/profile.controller.ts @@ -40,6 +40,3 @@ export class ProfileController { return this.profileService.remove(+id); } } - - - diff --git a/src/core/profile/profile.module.ts b/src/core/profile/profile.module.ts index 30b89d1..4f4c9af 100644 --- a/src/core/profile/profile.module.ts +++ b/src/core/profile/profile.module.ts @@ -7,6 +7,3 @@ import { ProfileController } from "./profile.controller"; providers: [ProfileService], }) export class ProfileModule {} - - - diff --git a/src/core/profile/profile.service.ts b/src/core/profile/profile.service.ts index 1ef3e70..405afa7 100644 --- a/src/core/profile/profile.service.ts +++ b/src/core/profile/profile.service.ts @@ -24,6 +24,3 @@ export class ProfileService { return `This action removes a #${id} profile`; } } - - - diff --git a/src/core/user/dto/create-user.dto.ts b/src/core/user/dto/create-user.dto.ts index a1cb0e8..0311be1 100644 --- a/src/core/user/dto/create-user.dto.ts +++ b/src/core/user/dto/create-user.dto.ts @@ -1,4 +1 @@ export class CreateUserDto {} - - - diff --git a/src/core/user/dto/update-user.dto.ts b/src/core/user/dto/update-user.dto.ts index 4459632..ad061f2 100644 --- a/src/core/user/dto/update-user.dto.ts +++ b/src/core/user/dto/update-user.dto.ts @@ -2,6 +2,3 @@ import { PartialType } from "@nestjs/mapped-types"; import { CreateUserDto } from "./create-user.dto"; export class UpdateUserDto extends PartialType(CreateUserDto) {} - - - diff --git a/src/core/user/entities/user.entity.ts b/src/core/user/entities/user.entity.ts index a0d57d4..fb1ade6 100644 --- a/src/core/user/entities/user.entity.ts +++ b/src/core/user/entities/user.entity.ts @@ -99,6 +99,3 @@ export class User { @OneToMany(() => User, (user) => user.referredBy) referrals: User[]; } - - - diff --git a/src/core/user/user-role-separation.spec.ts b/src/core/user/user-role-separation.spec.ts index 4b005e4..a9b0ea5 100644 --- a/src/core/user/user-role-separation.spec.ts +++ b/src/core/user/user-role-separation.spec.ts @@ -110,6 +110,3 @@ describe("UserService — role separation (Governance vs KYC)", () => { }); }); }); - - - diff --git a/src/core/user/user.controller.ts b/src/core/user/user.controller.ts index d7ef899..99c68b3 100644 --- a/src/core/user/user.controller.ts +++ b/src/core/user/user.controller.ts @@ -40,6 +40,3 @@ export class UserController { return this.userService.remove(id); } } - - - diff --git a/src/core/user/user.module.ts b/src/core/user/user.module.ts index 1bc69e3..2d82058 100644 --- a/src/core/user/user.module.ts +++ b/src/core/user/user.module.ts @@ -11,6 +11,3 @@ import { UserController } from "./user.controller"; exports: [UserService], }) export class UserModule {} - - - diff --git a/src/core/user/user.service.ts b/src/core/user/user.service.ts index fba53e3..d0b42f9 100644 --- a/src/core/user/user.service.ts +++ b/src/core/user/user.service.ts @@ -81,6 +81,3 @@ export class UserService { } } } - - - diff --git a/src/dashboard/dashboard.controller.ts b/src/dashboard/dashboard.controller.ts index 8129865..74ab108 100644 --- a/src/dashboard/dashboard.controller.ts +++ b/src/dashboard/dashboard.controller.ts @@ -60,6 +60,3 @@ export class DashboardController { return this.dashboardService.getHealth(id); } } - - - diff --git a/src/dashboard/dashboard.module.ts b/src/dashboard/dashboard.module.ts index 69b12ea..f1f4dc4 100644 --- a/src/dashboard/dashboard.module.ts +++ b/src/dashboard/dashboard.module.ts @@ -59,6 +59,3 @@ import { WsExceptionFilter } from "./websocket/filters/ws-exception.filter"; ], }) export class DashboardModule {} - - - diff --git a/src/dashboard/dashboard.service.ts b/src/dashboard/dashboard.service.ts index 27c511e..f0bd5bd 100644 --- a/src/dashboard/dashboard.service.ts +++ b/src/dashboard/dashboard.service.ts @@ -81,6 +81,3 @@ export class DashboardService { }; } } - - - diff --git a/src/dashboard/dto/dashboard.dto.ts b/src/dashboard/dto/dashboard.dto.ts index ae4f73b..6f813ff 100644 --- a/src/dashboard/dto/dashboard.dto.ts +++ b/src/dashboard/dto/dashboard.dto.ts @@ -20,6 +20,3 @@ export class TimeRangeDto { @IsEnum(TimeRange) timeRange?: TimeRange; } - - - diff --git a/src/dashboard/websocket/adapters/dashboard-ws-auth.adapter.ts b/src/dashboard/websocket/adapters/dashboard-ws-auth.adapter.ts index 9d4f77c..de183a9 100644 --- a/src/dashboard/websocket/adapters/dashboard-ws-auth.adapter.ts +++ b/src/dashboard/websocket/adapters/dashboard-ws-auth.adapter.ts @@ -113,6 +113,3 @@ export function setupAdapter( ) { server.adapter = adapter; } - - - diff --git a/src/dashboard/websocket/dashboard.gateway.ts b/src/dashboard/websocket/dashboard.gateway.ts index 3b4ef23..70e40a2 100644 --- a/src/dashboard/websocket/dashboard.gateway.ts +++ b/src/dashboard/websocket/dashboard.gateway.ts @@ -335,6 +335,3 @@ export class DashboardGateway }, 60000); } } - - - diff --git a/src/dashboard/websocket/filters/ws-exception.filter.ts b/src/dashboard/websocket/filters/ws-exception.filter.ts index aed848c..d915583 100644 --- a/src/dashboard/websocket/filters/ws-exception.filter.ts +++ b/src/dashboard/websocket/filters/ws-exception.filter.ts @@ -56,6 +56,3 @@ export class WsExceptionFilter implements ExceptionFilter { client.emit(DashboardEvent.ERROR, errorResponse); } } - - - diff --git a/src/dashboard/websocket/index.ts b/src/dashboard/websocket/index.ts index facad1f..169586a 100644 --- a/src/dashboard/websocket/index.ts +++ b/src/dashboard/websocket/index.ts @@ -59,6 +59,3 @@ export { ConnectionState, EventHandler, } from "./services/dashboard-client.service"; - - - diff --git a/src/dashboard/websocket/interfaces/websocket.interfaces.ts b/src/dashboard/websocket/interfaces/websocket.interfaces.ts index e30b773..74c3b19 100644 --- a/src/dashboard/websocket/interfaces/websocket.interfaces.ts +++ b/src/dashboard/websocket/interfaces/websocket.interfaces.ts @@ -156,6 +156,3 @@ export interface EventBufferConfig { maxAge: number; // Maximum age of buffered events in ms flushInterval: number; // Interval to check for expired buffers } - - - diff --git a/src/dashboard/websocket/services/connection-manager.service.spec.ts b/src/dashboard/websocket/services/connection-manager.service.spec.ts index 438779f..2da9c08 100644 --- a/src/dashboard/websocket/services/connection-manager.service.spec.ts +++ b/src/dashboard/websocket/services/connection-manager.service.spec.ts @@ -284,6 +284,3 @@ describe("ConnectionManagerService", () => { }); }); }); - - - diff --git a/src/dashboard/websocket/services/connection-manager.service.ts b/src/dashboard/websocket/services/connection-manager.service.ts index 07c88ed..53dc445 100644 --- a/src/dashboard/websocket/services/connection-manager.service.ts +++ b/src/dashboard/websocket/services/connection-manager.service.ts @@ -259,6 +259,3 @@ export class ConnectionManagerService { } } } - - - diff --git a/src/dashboard/websocket/services/connection-pool.service.ts b/src/dashboard/websocket/services/connection-pool.service.ts index 32790a1..3dd69f0 100644 --- a/src/dashboard/websocket/services/connection-pool.service.ts +++ b/src/dashboard/websocket/services/connection-pool.service.ts @@ -420,6 +420,3 @@ export class ConnectionPoolService implements OnModuleDestroy { this.logger.log("Connection pool service shut down"); } } - - - diff --git a/src/dashboard/websocket/services/dashboard-client.service.ts b/src/dashboard/websocket/services/dashboard-client.service.ts index 496e3d0..f6be201 100644 --- a/src/dashboard/websocket/services/dashboard-client.service.ts +++ b/src/dashboard/websocket/services/dashboard-client.service.ts @@ -476,6 +476,3 @@ export function createDashboardClient( ): DashboardClientService { return new DashboardClientService(config); } - - - diff --git a/src/dashboard/websocket/services/dashboard-metrics.service.ts b/src/dashboard/websocket/services/dashboard-metrics.service.ts index a4aae25..35806cb 100644 --- a/src/dashboard/websocket/services/dashboard-metrics.service.ts +++ b/src/dashboard/websocket/services/dashboard-metrics.service.ts @@ -292,6 +292,3 @@ export class DashboardMetricsService { return register.contentType; } } - - - diff --git a/src/dashboard/websocket/services/event-buffer.service.spec.ts b/src/dashboard/websocket/services/event-buffer.service.spec.ts index 9e3c70f..1df4145 100644 --- a/src/dashboard/websocket/services/event-buffer.service.spec.ts +++ b/src/dashboard/websocket/services/event-buffer.service.spec.ts @@ -227,6 +227,3 @@ describe("EventBufferService", () => { }); }); }); - - - diff --git a/src/dashboard/websocket/services/event-buffer.service.ts b/src/dashboard/websocket/services/event-buffer.service.ts index 08805af..9f87b07 100644 --- a/src/dashboard/websocket/services/event-buffer.service.ts +++ b/src/dashboard/websocket/services/event-buffer.service.ts @@ -179,6 +179,3 @@ export class EventBufferService { return this.disconnectionTracker.get(userId); } } - - - diff --git a/src/dashboard/websocket/services/reconnection.service.spec.ts b/src/dashboard/websocket/services/reconnection.service.spec.ts index 20ae22c..6746296 100644 --- a/src/dashboard/websocket/services/reconnection.service.spec.ts +++ b/src/dashboard/websocket/services/reconnection.service.spec.ts @@ -220,6 +220,3 @@ describe("Exponential Backoff", () => { expect(currentDelay).toBe(30000); }); }); - - - diff --git a/src/dashboard/websocket/services/reconnection.service.ts b/src/dashboard/websocket/services/reconnection.service.ts index 7b94dd9..1f3d262 100644 --- a/src/dashboard/websocket/services/reconnection.service.ts +++ b/src/dashboard/websocket/services/reconnection.service.ts @@ -536,6 +536,3 @@ export class WebSocketClientManager { }); } } - - - diff --git a/src/dashboard/websocket/services/websocket-health.service.ts b/src/dashboard/websocket/services/websocket-health.service.ts index 6b7d9fd..4938311 100644 --- a/src/dashboard/websocket/services/websocket-health.service.ts +++ b/src/dashboard/websocket/services/websocket-health.service.ts @@ -115,6 +115,3 @@ export class WebSocketHealthService { }; } } - - - diff --git a/src/dashboard/websocket/websocket.stress.spec.ts b/src/dashboard/websocket/websocket.stress.spec.ts index 7512956..4c20ed6 100644 --- a/src/dashboard/websocket/websocket.stress.spec.ts +++ b/src/dashboard/websocket/websocket.stress.spec.ts @@ -543,6 +543,3 @@ describe("WebSocket Client Manager Tests", () => { }); }); }); - - - diff --git a/src/defi/defi.controller.ts b/src/defi/defi.controller.ts index 2152c5b..57fffd8 100644 --- a/src/defi/defi.controller.ts +++ b/src/defi/defi.controller.ts @@ -431,6 +431,3 @@ export class DeFiController { return alerts; } } - - - diff --git a/src/defi/defi.module.ts b/src/defi/defi.module.ts index f3746d7..71a0fb2 100644 --- a/src/defi/defi.module.ts +++ b/src/defi/defi.module.ts @@ -139,6 +139,3 @@ export class DeFiModule implements OnModuleInit { }); } } - - - diff --git a/src/defi/dto/defi.dto.ts b/src/defi/dto/defi.dto.ts index 549e514..3d5de63 100644 --- a/src/defi/dto/defi.dto.ts +++ b/src/defi/dto/defi.dto.ts @@ -270,6 +270,3 @@ export class DeFiAnalyticsDto { risk_distribution: Record; performance_chart: Array<{ date: Date; value: number; apy: number }>; } - - - diff --git a/src/defi/dto/yield-strategy.dto.ts b/src/defi/dto/yield-strategy.dto.ts index d0cc808..8cdc9a0 100644 --- a/src/defi/dto/yield-strategy.dto.ts +++ b/src/defi/dto/yield-strategy.dto.ts @@ -181,6 +181,3 @@ export class StrategyPerformanceResponseDto { period_start: Date; period_end: Date; } - - - diff --git a/src/defi/entities/defi-position.entity.ts b/src/defi/entities/defi-position.entity.ts index 77aa90b..e00c8aa 100644 --- a/src/defi/entities/defi-position.entity.ts +++ b/src/defi/entities/defi-position.entity.ts @@ -148,6 +148,3 @@ export class DeFiPosition { @Column("timestamp", { nullable: true }) last_updated_on_chain: Date; } - - - diff --git a/src/defi/entities/defi-risk-assessment.entity.ts b/src/defi/entities/defi-risk-assessment.entity.ts index 1d05a15..4eecd58 100644 --- a/src/defi/entities/defi-risk-assessment.entity.ts +++ b/src/defi/entities/defi-risk-assessment.entity.ts @@ -88,6 +88,3 @@ export class DeFiRiskAssessment { @Column("timestamp", { nullable: true }) effective_until: Date; } - - - diff --git a/src/defi/entities/defi-transaction.entity.ts b/src/defi/entities/defi-transaction.entity.ts index cc2a362..238aad0 100644 --- a/src/defi/entities/defi-transaction.entity.ts +++ b/src/defi/entities/defi-transaction.entity.ts @@ -100,6 +100,3 @@ export class DeFiTransaction { @Column("timestamp", { nullable: true }) executed_at: Date; } - - - diff --git a/src/defi/entities/defi-yield-record.entity.ts b/src/defi/entities/defi-yield-record.entity.ts index c351ec2..4687227 100644 --- a/src/defi/entities/defi-yield-record.entity.ts +++ b/src/defi/entities/defi-yield-record.entity.ts @@ -50,6 +50,3 @@ export class DeFiYieldRecord { @Column("boolean", { default: false }) claimed: boolean; } - - - diff --git a/src/defi/entities/defi-yield-strategy.entity.ts b/src/defi/entities/defi-yield-strategy.entity.ts index f52af43..b81005e 100644 --- a/src/defi/entities/defi-yield-strategy.entity.ts +++ b/src/defi/entities/defi-yield-strategy.entity.ts @@ -124,6 +124,3 @@ export class DeFiYieldStrategy { @UpdateDateColumn() updated_at: Date; } - - - diff --git a/src/defi/protocols/aave.adapter.ts b/src/defi/protocols/aave.adapter.ts index e502730..9e29ef3 100644 --- a/src/defi/protocols/aave.adapter.ts +++ b/src/defi/protocols/aave.adapter.ts @@ -536,6 +536,3 @@ export class AaveAdapter implements ProtocolAdapter { return prices[token] || 0; } } - - - diff --git a/src/defi/protocols/compound.adapter.ts b/src/defi/protocols/compound.adapter.ts index e9ea17a..330fe02 100644 --- a/src/defi/protocols/compound.adapter.ts +++ b/src/defi/protocols/compound.adapter.ts @@ -351,6 +351,3 @@ export class CompoundAdapter implements ProtocolAdapter { return prices[token] || 0; } } - - - diff --git a/src/defi/protocols/protocol-adapter.interface.ts b/src/defi/protocols/protocol-adapter.interface.ts index 59b6d14..b69f16c 100644 --- a/src/defi/protocols/protocol-adapter.interface.ts +++ b/src/defi/protocols/protocol-adapter.interface.ts @@ -149,6 +149,3 @@ export interface SwapRoute { priceImpact: number; fee: number; } - - - diff --git a/src/defi/protocols/protocol-registry.ts b/src/defi/protocols/protocol-registry.ts index 7aaddba..308fd9a 100644 --- a/src/defi/protocols/protocol-registry.ts +++ b/src/defi/protocols/protocol-registry.ts @@ -56,6 +56,3 @@ export class ProtocolRegistry { return supportingAdapters; } } - - - diff --git a/src/defi/services/position-tracking.service.ts b/src/defi/services/position-tracking.service.ts index aa4800d..f5ef487 100644 --- a/src/defi/services/position-tracking.service.ts +++ b/src/defi/services/position-tracking.service.ts @@ -593,6 +593,3 @@ export interface RiskPosition { riskLevel: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL"; hoursToLiquidation?: number; } - - - diff --git a/src/defi/services/risk-assessment.service.ts b/src/defi/services/risk-assessment.service.ts index 31b2bc4..542ff1d 100644 --- a/src/defi/services/risk-assessment.service.ts +++ b/src/defi/services/risk-assessment.service.ts @@ -430,6 +430,3 @@ export interface RiskMonitoringResult { healthRating: string; }; } - - - diff --git a/src/defi/services/transaction-optimization.service.ts b/src/defi/services/transaction-optimization.service.ts index 7a5fcdb..794a0d4 100644 --- a/src/defi/services/transaction-optimization.service.ts +++ b/src/defi/services/transaction-optimization.service.ts @@ -523,6 +523,3 @@ export interface NetworkConditions { networkCongestion: "low" | "moderate" | "high"; avgBlockTime: number; } - - - diff --git a/src/defi/services/yield-optimization.service.ts b/src/defi/services/yield-optimization.service.ts index cd1b9ff..afaad4e 100644 --- a/src/defi/services/yield-optimization.service.ts +++ b/src/defi/services/yield-optimization.service.ts @@ -491,6 +491,3 @@ export interface CompoundingResult { value: number; }>; } - - - diff --git a/src/defi/trade-lock.service.ts b/src/defi/trade-lock.service.ts index dac8a66..a0f6d4c 100644 --- a/src/defi/trade-lock.service.ts +++ b/src/defi/trade-lock.service.ts @@ -144,6 +144,3 @@ export class TradeLockService { } } } - - - diff --git a/src/defi/trade.controller.ts b/src/defi/trade.controller.ts index 419058b..2d6842f 100644 --- a/src/defi/trade.controller.ts +++ b/src/defi/trade.controller.ts @@ -33,6 +33,3 @@ export class TradeController { }); } } - - - diff --git a/src/discovery/algorithms/agent-scoring.spec.ts b/src/discovery/algorithms/agent-scoring.spec.ts index 21bbf0f..0fd238e 100644 --- a/src/discovery/algorithms/agent-scoring.spec.ts +++ b/src/discovery/algorithms/agent-scoring.spec.ts @@ -163,6 +163,3 @@ describe("AgentScoring", () => { }); }); }); - - - diff --git a/src/discovery/algorithms/agent-scoring.ts b/src/discovery/algorithms/agent-scoring.ts index 303f57d..07a6091 100644 --- a/src/discovery/algorithms/agent-scoring.ts +++ b/src/discovery/algorithms/agent-scoring.ts @@ -187,6 +187,3 @@ export class AgentScoring { }); } } - - - diff --git a/src/growth/alerts/alerts.controller.ts b/src/growth/alerts/alerts.controller.ts index ca32e8d..87f69f1 100644 --- a/src/growth/alerts/alerts.controller.ts +++ b/src/growth/alerts/alerts.controller.ts @@ -136,6 +136,3 @@ export class AlertsController { return this.alertsService.getPreference(userId); } } - - - diff --git a/src/growth/alerts/alerts.module.ts b/src/growth/alerts/alerts.module.ts index 21b1935..4f321dd 100644 --- a/src/growth/alerts/alerts.module.ts +++ b/src/growth/alerts/alerts.module.ts @@ -27,6 +27,3 @@ import { PortfolioAlertListener } from "./listeners/portfolio-alert.listener"; exports: [AlertsService, AlertDispatcherService, AlertEvaluationService], }) export class AlertsModule {} - - - diff --git a/src/growth/alerts/alerts.service.spec.ts b/src/growth/alerts/alerts.service.spec.ts index d9d8659..a6f5daf 100644 --- a/src/growth/alerts/alerts.service.spec.ts +++ b/src/growth/alerts/alerts.service.spec.ts @@ -504,6 +504,3 @@ describe("AlertsService", () => { }); }); }); - - - diff --git a/src/growth/alerts/alerts.service.ts b/src/growth/alerts/alerts.service.ts index ea312b8..49d2e8e 100644 --- a/src/growth/alerts/alerts.service.ts +++ b/src/growth/alerts/alerts.service.ts @@ -287,6 +287,3 @@ export class AlertsService { await this.preferenceRepo.remove(pref); } } - - - diff --git a/src/growth/alerts/dto/alert-preference.dto.ts b/src/growth/alerts/dto/alert-preference.dto.ts index 5d6e3fd..7339278 100644 --- a/src/growth/alerts/dto/alert-preference.dto.ts +++ b/src/growth/alerts/dto/alert-preference.dto.ts @@ -80,6 +80,3 @@ export class UnsubscribeAlertDto { @IsString() userId: string; } - - - diff --git a/src/growth/alerts/dto/alert.dto.ts b/src/growth/alerts/dto/alert.dto.ts index 007fdae..f47e0bb 100644 --- a/src/growth/alerts/dto/alert.dto.ts +++ b/src/growth/alerts/dto/alert.dto.ts @@ -99,6 +99,3 @@ export class CreatePerformanceAlertDto { @Min(0) cooldownSeconds?: number; } - - - diff --git a/src/growth/alerts/entities/alert-preference.entity.ts b/src/growth/alerts/entities/alert-preference.entity.ts index a3901d2..8dcb162 100644 --- a/src/growth/alerts/entities/alert-preference.entity.ts +++ b/src/growth/alerts/entities/alert-preference.entity.ts @@ -47,6 +47,3 @@ export class AlertPreference { @UpdateDateColumn() updatedAt: Date; } - - - diff --git a/src/growth/alerts/entities/alert-trigger-log.entity.ts b/src/growth/alerts/entities/alert-trigger-log.entity.ts index cfdd14c..21b02ec 100644 --- a/src/growth/alerts/entities/alert-trigger-log.entity.ts +++ b/src/growth/alerts/entities/alert-trigger-log.entity.ts @@ -26,6 +26,3 @@ export class AlertTriggerLog { @CreateDateColumn() triggeredAt: Date; } - - - diff --git a/src/growth/alerts/entities/alert.entity.ts b/src/growth/alerts/entities/alert.entity.ts index b546c13..4436882 100644 --- a/src/growth/alerts/entities/alert.entity.ts +++ b/src/growth/alerts/entities/alert.entity.ts @@ -57,6 +57,3 @@ export class Alert { @UpdateDateColumn() updatedAt: Date; } - - - diff --git a/src/growth/alerts/listeners/portfolio-alert.listener.ts b/src/growth/alerts/listeners/portfolio-alert.listener.ts index eeb356a..a206067 100644 --- a/src/growth/alerts/listeners/portfolio-alert.listener.ts +++ b/src/growth/alerts/listeners/portfolio-alert.listener.ts @@ -55,6 +55,3 @@ export class PortfolioAlertListener { }); } } - - - diff --git a/src/growth/alerts/listeners/risk-alert.listener.ts b/src/growth/alerts/listeners/risk-alert.listener.ts index 3092c45..0357318 100644 --- a/src/growth/alerts/listeners/risk-alert.listener.ts +++ b/src/growth/alerts/listeners/risk-alert.listener.ts @@ -14,6 +14,3 @@ export class RiskAlertListener { }); } } - - - diff --git a/src/growth/alerts/services/alert-dispatcher.service.spec.ts b/src/growth/alerts/services/alert-dispatcher.service.spec.ts index f6e7580..fcc1a46 100644 --- a/src/growth/alerts/services/alert-dispatcher.service.spec.ts +++ b/src/growth/alerts/services/alert-dispatcher.service.spec.ts @@ -305,6 +305,3 @@ describe("AlertDispatcherService", () => { }); }); }); - - - diff --git a/src/growth/alerts/services/alert-dispatcher.service.ts b/src/growth/alerts/services/alert-dispatcher.service.ts index 517061f..bc12147 100644 --- a/src/growth/alerts/services/alert-dispatcher.service.ts +++ b/src/growth/alerts/services/alert-dispatcher.service.ts @@ -225,6 +225,3 @@ export class AlertDispatcherService { return new Promise((resolve) => setTimeout(resolve, ms)); } } - - - diff --git a/src/growth/alerts/services/alert-evaluation.service.spec.ts b/src/growth/alerts/services/alert-evaluation.service.spec.ts index 3dcd8aa..417f10a 100644 --- a/src/growth/alerts/services/alert-evaluation.service.spec.ts +++ b/src/growth/alerts/services/alert-evaluation.service.spec.ts @@ -183,6 +183,3 @@ describe("AlertEvaluationService", () => { }); }); }); - - - diff --git a/src/growth/alerts/services/alert-evaluation.service.ts b/src/growth/alerts/services/alert-evaluation.service.ts index bbdf8f7..c90f04a 100644 --- a/src/growth/alerts/services/alert-evaluation.service.ts +++ b/src/growth/alerts/services/alert-evaluation.service.ts @@ -92,6 +92,3 @@ export class AlertEvaluationService { } } } - - - diff --git a/src/health/dto/health-response.dto.ts b/src/health/dto/health-response.dto.ts index 39eb630..fe7fdb3 100644 --- a/src/health/dto/health-response.dto.ts +++ b/src/health/dto/health-response.dto.ts @@ -40,6 +40,3 @@ export class HealthResponseDto { }) components: Record; } - - - diff --git a/src/health/health.constants.ts b/src/health/health.constants.ts index f73d8ed..7f697cb 100644 --- a/src/health/health.constants.ts +++ b/src/health/health.constants.ts @@ -1,4 +1 @@ export const HEALTH_REDIS_CLIENT = "HEALTH_REDIS_CLIENT"; - - - diff --git a/src/health/health.controller.spec.ts b/src/health/health.controller.spec.ts index 264e415..4d4d74d 100644 --- a/src/health/health.controller.spec.ts +++ b/src/health/health.controller.spec.ts @@ -160,6 +160,3 @@ describe("HealthController", () => { }); }); }); - - - diff --git a/src/health/health.controller.ts b/src/health/health.controller.ts index af5da32..b689987 100644 --- a/src/health/health.controller.ts +++ b/src/health/health.controller.ts @@ -95,6 +95,3 @@ export class HealthController { return result; } } - - - diff --git a/src/health/health.module.ts b/src/health/health.module.ts index de47a09..5728dee 100644 --- a/src/health/health.module.ts +++ b/src/health/health.module.ts @@ -26,6 +26,3 @@ import { HEALTH_REDIS_CLIENT } from "./health.constants"; ], }) export class HealthModule {} - - - diff --git a/src/health/health.service.spec.ts b/src/health/health.service.spec.ts index 3a437ab..7d5fd91 100644 --- a/src/health/health.service.spec.ts +++ b/src/health/health.service.spec.ts @@ -203,6 +203,3 @@ describe("HealthService", () => { }); }); }); - - - diff --git a/src/health/health.service.ts b/src/health/health.service.ts index 912c87a..1b551b8 100644 --- a/src/health/health.service.ts +++ b/src/health/health.service.ts @@ -135,6 +135,3 @@ export class HealthService { ); } } - - - diff --git a/src/infrastructure/ai-compute/provider-failover.spec.ts b/src/infrastructure/ai-compute/provider-failover.spec.ts index f1bd67c..92047ed 100644 --- a/src/infrastructure/ai-compute/provider-failover.spec.ts +++ b/src/infrastructure/ai-compute/provider-failover.spec.ts @@ -85,6 +85,3 @@ describe("ProviderFailover", () => { expect(Object.keys(m)).toHaveLength(3); }); }); - - - diff --git a/src/infrastructure/ai-compute/provider-failover.ts b/src/infrastructure/ai-compute/provider-failover.ts index 06da40e..fb27384 100644 --- a/src/infrastructure/ai-compute/provider-failover.ts +++ b/src/infrastructure/ai-compute/provider-failover.ts @@ -183,6 +183,3 @@ export class ProviderFailover { return out; } } - - - diff --git a/src/infrastructure/audit/audit-log.service.ts b/src/infrastructure/audit/audit-log.service.ts index 61d9f3e..ef2400a 100644 --- a/src/infrastructure/audit/audit-log.service.ts +++ b/src/infrastructure/audit/audit-log.service.ts @@ -22,6 +22,3 @@ export class AuditLogService { return this.logs.slice(-limit); } } - - - diff --git a/src/infrastructure/audit/audit.module.ts b/src/infrastructure/audit/audit.module.ts index 5960fe3..47732d4 100644 --- a/src/infrastructure/audit/audit.module.ts +++ b/src/infrastructure/audit/audit.module.ts @@ -22,6 +22,3 @@ import { AuditLogService } from "./audit-log.service"; exports: [TypeOrmModule, ProvenanceService, AuditLogService], }) export class AuditModule {} - - - diff --git a/src/infrastructure/audit/dto/create-provenance-record.dto.ts b/src/infrastructure/audit/dto/create-provenance-record.dto.ts index 2aeade4..d07dc66 100644 --- a/src/infrastructure/audit/dto/create-provenance-record.dto.ts +++ b/src/infrastructure/audit/dto/create-provenance-record.dto.ts @@ -130,6 +130,3 @@ export class CreateProvenanceRecordDto { @IsString() userAgent?: string; } - - - diff --git a/src/infrastructure/audit/dto/provenance-response.dto.ts b/src/infrastructure/audit/dto/provenance-response.dto.ts index bd05d4a..4e1a9a5 100644 --- a/src/infrastructure/audit/dto/provenance-response.dto.ts +++ b/src/infrastructure/audit/dto/provenance-response.dto.ts @@ -205,6 +205,3 @@ export class ProvenanceTimelineResponseDto { }) toDate: string; } - - - diff --git a/src/infrastructure/audit/dto/query-provenance.dto.ts b/src/infrastructure/audit/dto/query-provenance.dto.ts index 2e16029..1642193 100644 --- a/src/infrastructure/audit/dto/query-provenance.dto.ts +++ b/src/infrastructure/audit/dto/query-provenance.dto.ts @@ -132,6 +132,3 @@ export class ExportProvenanceDto { @IsString() format?: "json" | "csv" = "json"; } - - - diff --git a/src/infrastructure/audit/entities/agent-event.entity.ts b/src/infrastructure/audit/entities/agent-event.entity.ts index 0e13b5d..aeefc44 100644 --- a/src/infrastructure/audit/entities/agent-event.entity.ts +++ b/src/infrastructure/audit/entities/agent-event.entity.ts @@ -91,6 +91,3 @@ export class AgentEvent { @Index() createdAt: Date; } - - - diff --git a/src/infrastructure/audit/entities/compute-result.entity.ts b/src/infrastructure/audit/entities/compute-result.entity.ts index ecc9ef9..1486a02 100644 --- a/src/infrastructure/audit/entities/compute-result.entity.ts +++ b/src/infrastructure/audit/entities/compute-result.entity.ts @@ -130,6 +130,3 @@ export class ComputeResult { @UpdateDateColumn() updatedAt: Date; } - - - diff --git a/src/infrastructure/audit/entities/oracle-submission.entity.ts b/src/infrastructure/audit/entities/oracle-submission.entity.ts index ee6854a..933faa8 100644 --- a/src/infrastructure/audit/entities/oracle-submission.entity.ts +++ b/src/infrastructure/audit/entities/oracle-submission.entity.ts @@ -144,6 +144,3 @@ export class OracleSubmission { @Index() expiresAt: Date | null; } - - - diff --git a/src/infrastructure/audit/entities/provenance-record.entity.ts b/src/infrastructure/audit/entities/provenance-record.entity.ts index caf15ac..f7dff4d 100644 --- a/src/infrastructure/audit/entities/provenance-record.entity.ts +++ b/src/infrastructure/audit/entities/provenance-record.entity.ts @@ -159,6 +159,3 @@ export class ProvenanceRecord { @Column({ type: "text", nullable: true }) userAgent: string | null; } - - - diff --git a/src/infrastructure/audit/guards/provenance-access.guard.ts b/src/infrastructure/audit/guards/provenance-access.guard.ts index ce274dd..4ca7696 100644 --- a/src/infrastructure/audit/guards/provenance-access.guard.ts +++ b/src/infrastructure/audit/guards/provenance-access.guard.ts @@ -48,6 +48,3 @@ export class ProvenanceAccessGuard implements CanActivate { return true; } } - - - diff --git a/src/infrastructure/audit/provenance.controller.ts b/src/infrastructure/audit/provenance.controller.ts index 95523ea..abd3c58 100644 --- a/src/infrastructure/audit/provenance.controller.ts +++ b/src/infrastructure/audit/provenance.controller.ts @@ -267,6 +267,3 @@ export class ProvenanceController { res.send(csv); } } - - - diff --git a/src/infrastructure/audit/provenance.service.ts b/src/infrastructure/audit/provenance.service.ts index 865fc7e..405367c 100644 --- a/src/infrastructure/audit/provenance.service.ts +++ b/src/infrastructure/audit/provenance.service.ts @@ -450,6 +450,3 @@ export class ProvenanceService { }; } } - - - diff --git a/src/investment/portfolio/algorithms/black-litterman.ts b/src/investment/portfolio/algorithms/black-litterman.ts index 5aac924..75ee7c0 100644 --- a/src/investment/portfolio/algorithms/black-litterman.ts +++ b/src/investment/portfolio/algorithms/black-litterman.ts @@ -153,6 +153,3 @@ export class BlackLittermanModel { return matrix.map((row) => row.map((val) => (1 / val) * 0.1)); } } - - - diff --git a/src/investment/portfolio/algorithms/constraint-optimizer.ts b/src/investment/portfolio/algorithms/constraint-optimizer.ts index 1cb6915..aca53fb 100644 --- a/src/investment/portfolio/algorithms/constraint-optimizer.ts +++ b/src/investment/portfolio/algorithms/constraint-optimizer.ts @@ -133,6 +133,3 @@ export class ConstraintOptimizer { return w; } } - - - diff --git a/src/investment/portfolio/algorithms/modern-portfolio-theory.ts b/src/investment/portfolio/algorithms/modern-portfolio-theory.ts index d26c73b..168eddf 100644 --- a/src/investment/portfolio/algorithms/modern-portfolio-theory.ts +++ b/src/investment/portfolio/algorithms/modern-portfolio-theory.ts @@ -482,6 +482,3 @@ export class ModernPortfolioTheory { } } } - - - diff --git a/src/investment/portfolio/algorithms/performance-calculations.spec.ts b/src/investment/portfolio/algorithms/performance-calculations.spec.ts index d3677c8..76416e6 100644 --- a/src/investment/portfolio/algorithms/performance-calculations.spec.ts +++ b/src/investment/portfolio/algorithms/performance-calculations.spec.ts @@ -298,6 +298,3 @@ describe("PerformanceCalculations", () => { }); }); }); - - - diff --git a/src/investment/portfolio/algorithms/performance-calculations.ts b/src/investment/portfolio/algorithms/performance-calculations.ts index 6802d94..eaef23a 100644 --- a/src/investment/portfolio/algorithms/performance-calculations.ts +++ b/src/investment/portfolio/algorithms/performance-calculations.ts @@ -309,6 +309,3 @@ export class PerformanceCalculations { }; } } - - - diff --git a/src/investment/portfolio/algorithms/portfolio-validation.spec.ts b/src/investment/portfolio/algorithms/portfolio-validation.spec.ts index 7d1a2c1..0c43db6 100644 --- a/src/investment/portfolio/algorithms/portfolio-validation.spec.ts +++ b/src/investment/portfolio/algorithms/portfolio-validation.spec.ts @@ -230,6 +230,3 @@ describe("PortfolioValidation", () => { }); }); }); - - - diff --git a/src/investment/portfolio/algorithms/portfolio-validation.ts b/src/investment/portfolio/algorithms/portfolio-validation.ts index 8c47e69..ebe1d73 100644 --- a/src/investment/portfolio/algorithms/portfolio-validation.ts +++ b/src/investment/portfolio/algorithms/portfolio-validation.ts @@ -267,6 +267,3 @@ export class PortfolioValidation { return { ...result, issues, valid }; } } - - - diff --git a/src/investment/portfolio/controllers/transaction.controller.ts b/src/investment/portfolio/controllers/transaction.controller.ts new file mode 100644 index 0000000..b49d720 --- /dev/null +++ b/src/investment/portfolio/controllers/transaction.controller.ts @@ -0,0 +1,62 @@ +import { + Controller, + Post, + Body, + Get, + Query, + Param, + Patch, + Res, +} from "@nestjs/common"; +import { TradingTransactionService } from "../services/trading-transaction.service"; +import { + CreateTransactionDto, + FilterTransactionDto, +} from "../dto/transaction.dto"; +import { Response } from "express"; + +@Controller("portfolios/:portfolioId/transactions") +export class TransactionController { + constructor(private readonly transactionService: TradingTransactionService) {} + + @Post() + createTransaction(@Body() createTransactionDto: CreateTransactionDto) { + return this.transactionService.createTransaction(createTransactionDto); + } + + @Get() + getTransactions( + @Param("portfolioId") portfolioId: string, + @Query() filterDto: FilterTransactionDto, + ) { + return this.transactionService.getTransactions(portfolioId, filterDto); + } + + @Patch(":id/archive") + archiveTransaction(@Param("id") id: string) { + return this.transactionService.archiveTransaction(id); + } + + @Get("export") + async exportTransactions( + @Param("portfolioId") portfolioId: string, + @Query() filterDto: FilterTransactionDto, + @Res() res: Response, + ) { + const stream = await this.transactionService.exportTransactionsToCsv( + portfolioId, + filterDto, + ); + res.setHeader("Content-Type", "text/csv"); + res.setHeader( + "Content-Disposition", + "attachment; filename=transactions.csv", + ); + stream.pipe(res); + } + + @Get("assets/:assetId/cost-basis") + calculateCostBasis(@Param("assetId") assetId: string) { + return this.transactionService.calculateCostBasis(assetId); + } +} diff --git a/src/investment/portfolio/dto/api-error.dto.ts b/src/investment/portfolio/dto/api-error.dto.ts index 90c0487..a7fc7cc 100644 --- a/src/investment/portfolio/dto/api-error.dto.ts +++ b/src/investment/portfolio/dto/api-error.dto.ts @@ -23,6 +23,3 @@ export class ApiErrorDto { @ApiProperty({ example: "/api/v1/portfolio/:id" }) path: string; } - - - diff --git a/src/investment/portfolio/dto/backtest.dto.ts b/src/investment/portfolio/dto/backtest.dto.ts index 286dc22..acc494c 100644 --- a/src/investment/portfolio/dto/backtest.dto.ts +++ b/src/investment/portfolio/dto/backtest.dto.ts @@ -65,6 +65,3 @@ export class BacktestResultResponseDto { createdAt: Date; completedAt?: Date; } - - - diff --git a/src/investment/portfolio/dto/optimization.dto.ts b/src/investment/portfolio/dto/optimization.dto.ts index 5a7dd25..d214084 100644 --- a/src/investment/portfolio/dto/optimization.dto.ts +++ b/src/investment/portfolio/dto/optimization.dto.ts @@ -82,6 +82,3 @@ export class OptimizationHistoryResponseDto { completedAt?: Date; implementedAt?: Date; } - - - diff --git a/src/investment/portfolio/dto/performance.dto.ts b/src/investment/portfolio/dto/performance.dto.ts index c4e3651..d685b21 100644 --- a/src/investment/portfolio/dto/performance.dto.ts +++ b/src/investment/portfolio/dto/performance.dto.ts @@ -177,6 +177,3 @@ export class ComparisonResponseDto { timestamp: Date; calculationDate: Date; } - - - diff --git a/src/investment/portfolio/dto/portfolio-asset.dto.ts b/src/investment/portfolio/dto/portfolio-asset.dto.ts index 0fb2892..fdb7655 100644 --- a/src/investment/portfolio/dto/portfolio-asset.dto.ts +++ b/src/investment/portfolio/dto/portfolio-asset.dto.ts @@ -127,6 +127,3 @@ export class PortfolioAssetResponseDto { unrealizedGain?: number; updatedAt: Date; } - - - diff --git a/src/investment/portfolio/dto/portfolio-management.dto.ts b/src/investment/portfolio/dto/portfolio-management.dto.ts index 9ce629e..7698e28 100644 --- a/src/investment/portfolio/dto/portfolio-management.dto.ts +++ b/src/investment/portfolio/dto/portfolio-management.dto.ts @@ -137,6 +137,3 @@ export class PortfolioListResponseDto { @ApiProperty({ type: [PortfolioResponseDto] }) portfolios: PortfolioResponseDto[]; } - - - diff --git a/src/investment/portfolio/dto/portfolio.dto.ts b/src/investment/portfolio/dto/portfolio.dto.ts index 9e081af..986134f 100644 --- a/src/investment/portfolio/dto/portfolio.dto.ts +++ b/src/investment/portfolio/dto/portfolio.dto.ts @@ -81,6 +81,3 @@ export class PortfolioResponseDto { createdAt: Date; updatedAt: Date; } - - - diff --git a/src/investment/portfolio/dto/rebalancing.dto.ts b/src/investment/portfolio/dto/rebalancing.dto.ts index 8584915..9080c60 100644 --- a/src/investment/portfolio/dto/rebalancing.dto.ts +++ b/src/investment/portfolio/dto/rebalancing.dto.ts @@ -82,6 +82,3 @@ export class RebalancingEventResponseDto { executedAt?: Date; completedAt?: Date; } - - - diff --git a/src/investment/portfolio/dto/risk-profile.dto.ts b/src/investment/portfolio/dto/risk-profile.dto.ts index e307c81..cf44c0f 100644 --- a/src/investment/portfolio/dto/risk-profile.dto.ts +++ b/src/investment/portfolio/dto/risk-profile.dto.ts @@ -134,6 +134,3 @@ export class RiskProfileResponseDto { createdAt: Date; updatedAt: Date; } - - - diff --git a/src/investment/portfolio/dto/transaction.dto.ts b/src/investment/portfolio/dto/transaction.dto.ts new file mode 100644 index 0000000..defd7c7 --- /dev/null +++ b/src/investment/portfolio/dto/transaction.dto.ts @@ -0,0 +1,67 @@ +import { + IsEnum, + IsOptional, + IsDateString, + IsNumber, + Min, +} from "class-validator"; +import { + TransactionType, + TransactionStatus, +} from "../entities/transaction.entity"; + +export class CreateTransactionDto { + @IsEnum(TransactionType) + type: TransactionType; + + @IsDateString() + date: string; + + @IsNumber() + @Min(0) + amount: number; + + @IsOptional() + @IsNumber() + @Min(0) + price?: number; + + @IsOptional() + @IsNumber() + @Min(0) + fees?: number; + + @IsOptional() + @IsNumber() + @Min(0) + gasFees?: number; + + portfolioId: string; + + @IsOptional() + portfolioAssetId?: string; + + @IsOptional() + notes?: string; + + @IsOptional() + metadata?: Record; +} + +export class FilterTransactionDto { + @IsOptional() + @IsEnum(TransactionType) + type?: TransactionType; + + @IsOptional() + @IsDateString() + startDate?: string; + + @IsOptional() + @IsDateString() + endDate?: string; + + @IsOptional() + @IsEnum(TransactionStatus) + status?: TransactionStatus; +} diff --git a/src/investment/portfolio/entities/backtest-result.entity.ts b/src/investment/portfolio/entities/backtest-result.entity.ts index 00e6f5e..3d24761 100644 --- a/src/investment/portfolio/entities/backtest-result.entity.ts +++ b/src/investment/portfolio/entities/backtest-result.entity.ts @@ -191,6 +191,3 @@ export class BacktestResult { @Column("uuid") userId: string; } - - - diff --git a/src/investment/portfolio/entities/optimization-history.entity.ts b/src/investment/portfolio/entities/optimization-history.entity.ts index 2ca7555..0d95ea8 100644 --- a/src/investment/portfolio/entities/optimization-history.entity.ts +++ b/src/investment/portfolio/entities/optimization-history.entity.ts @@ -142,6 +142,3 @@ export class OptimizationHistory { @Column("uuid") portfolioId: string; } - - - diff --git a/src/investment/portfolio/entities/performance-metric.entity.ts b/src/investment/portfolio/entities/performance-metric.entity.ts index 407f25b..09b4496 100644 --- a/src/investment/portfolio/entities/performance-metric.entity.ts +++ b/src/investment/portfolio/entities/performance-metric.entity.ts @@ -131,6 +131,3 @@ export class PerformanceMetric { @Column("uuid") portfolioId: string; } - - - diff --git a/src/investment/portfolio/entities/portfolio-asset.entity.ts b/src/investment/portfolio/entities/portfolio-asset.entity.ts index 75bea9b..1282a03 100644 --- a/src/investment/portfolio/entities/portfolio-asset.entity.ts +++ b/src/investment/portfolio/entities/portfolio-asset.entity.ts @@ -133,6 +133,3 @@ export class PortfolioAsset { @Column("uuid") portfolioId: string; } - - - diff --git a/src/investment/portfolio/entities/portfolio.entity.ts b/src/investment/portfolio/entities/portfolio.entity.ts index 3bbe818..602c73b 100644 --- a/src/investment/portfolio/entities/portfolio.entity.ts +++ b/src/investment/portfolio/entities/portfolio.entity.ts @@ -133,6 +133,3 @@ export class Portfolio { }) performanceMetrics: PerformanceMetric[]; } - - - diff --git a/src/investment/portfolio/entities/rebalancing-event.entity.ts b/src/investment/portfolio/entities/rebalancing-event.entity.ts index bdd488f..fb95a8a 100644 --- a/src/investment/portfolio/entities/rebalancing-event.entity.ts +++ b/src/investment/portfolio/entities/rebalancing-event.entity.ts @@ -125,6 +125,3 @@ export class RebalancingEvent { @Column("uuid") portfolioId: string; } - - - diff --git a/src/investment/portfolio/entities/risk-profile.entity.ts b/src/investment/portfolio/entities/risk-profile.entity.ts index 4a6da11..78e5ac0 100644 --- a/src/investment/portfolio/entities/risk-profile.entity.ts +++ b/src/investment/portfolio/entities/risk-profile.entity.ts @@ -138,6 +138,3 @@ export class RiskProfile { @Column("uuid") userId: string; } - - - diff --git a/src/investment/portfolio/entities/transaction.entity.ts b/src/investment/portfolio/entities/transaction.entity.ts index 255e8d7..b44823b 100644 --- a/src/investment/portfolio/entities/transaction.entity.ts +++ b/src/investment/portfolio/entities/transaction.entity.ts @@ -22,9 +22,16 @@ export enum TransactionType { REBALANCE = "rebalance", DIVIDEND = "dividend", INTEREST = "interest", + STAKE = "stake", + UNSTAKE = "unstake", OTHER = "other", } +export enum TransactionStatus { + ACTIVE = "active", + ARCHIVED = "archived", +} + @Entity("transactions") @Index(["portfolioId", "createdAt"]) @Index(["portfolioId", "type"]) @@ -52,6 +59,15 @@ export class Transaction { @Column({ type: "decimal", precision: 18, scale: 2, nullable: true }) fees: number; + @Column({ + type: "decimal", + precision: 18, + scale: 8, + nullable: true, + comment: "Gas fees, specific to blockchain transactions", + }) + gasFees: number; + @Column({ type: "enum", enum: Chain, @@ -65,6 +81,13 @@ export class Transaction { @Column({ type: "jsonb", nullable: true }) metadata: Record; + @Column({ + type: "enum", + enum: TransactionStatus, + default: TransactionStatus.ACTIVE, + }) + status: TransactionStatus; + @CreateDateColumn() createdAt: Date; @@ -85,6 +108,3 @@ export class Transaction { @Column("uuid", { nullable: true }) portfolioAssetId: string | null; } - - - diff --git a/src/investment/portfolio/guards/portfolio-owner.guard.ts b/src/investment/portfolio/guards/portfolio-owner.guard.ts index 5c87efb..a7f11b9 100644 --- a/src/investment/portfolio/guards/portfolio-owner.guard.ts +++ b/src/investment/portfolio/guards/portfolio-owner.guard.ts @@ -49,6 +49,3 @@ export class PortfolioOwnerGuard implements CanActivate { return true; } } - - - diff --git a/src/investment/portfolio/ml-models/predictor.ts b/src/investment/portfolio/ml-models/predictor.ts index a3c86a5..e3c0eec 100644 --- a/src/investment/portfolio/ml-models/predictor.ts +++ b/src/investment/portfolio/ml-models/predictor.ts @@ -437,6 +437,3 @@ export function calculateConfidence(metrics: TrainingMetrics): number { return (r2Confidence + mapeConfidence) / 2; } - - - diff --git a/src/investment/portfolio/portfolio-management.controller.ts b/src/investment/portfolio/portfolio-management.controller.ts index 028856e..08d08c2 100644 --- a/src/investment/portfolio/portfolio-management.controller.ts +++ b/src/investment/portfolio/portfolio-management.controller.ts @@ -157,6 +157,3 @@ export class PortfolioManagementController { return this.portfolioService.archivePortfolio(id, PortfolioStatus.ARCHIVED); } } - - - diff --git a/src/investment/portfolio/portfolio.controller.ts b/src/investment/portfolio/portfolio.controller.ts index 498e158..073834e 100644 --- a/src/investment/portfolio/portfolio.controller.ts +++ b/src/investment/portfolio/portfolio.controller.ts @@ -480,6 +480,3 @@ export class PortfolioController { ); } } - - - diff --git a/src/investment/portfolio/portfolio.module.ts b/src/investment/portfolio/portfolio.module.ts index d76a0f1..61551a5 100644 --- a/src/investment/portfolio/portfolio.module.ts +++ b/src/investment/portfolio/portfolio.module.ts @@ -25,6 +25,7 @@ import { TradingTransactionService } from "./services/trading-transaction.servic // Controllers import { PortfolioController } from "./portfolio.controller"; import { PortfolioManagementController } from "./portfolio-management.controller"; +import { TransactionController } from "./controllers/transaction.controller"; import { PortfolioOwnerGuard } from "./guards/portfolio-owner.guard"; @Module({ @@ -68,7 +69,11 @@ import { PortfolioOwnerGuard } from "./guards/portfolio-owner.guard"; TradingTransactionService, PortfolioOwnerGuard, ], - controllers: [PortfolioController, PortfolioManagementController], + controllers: [ + PortfolioController, + PortfolioManagementController, + TransactionController, + ], exports: [ PortfolioService, @@ -81,6 +86,3 @@ import { PortfolioOwnerGuard } from "./guards/portfolio-owner.guard"; ], }) export class PortfolioModule {} - - - diff --git a/src/investment/portfolio/services/backtesting.service.ts b/src/investment/portfolio/services/backtesting.service.ts index eb280b1..a5cb4de 100644 --- a/src/investment/portfolio/services/backtesting.service.ts +++ b/src/investment/portfolio/services/backtesting.service.ts @@ -352,6 +352,3 @@ export class BacktestingService { }; } } - - - diff --git a/src/investment/portfolio/services/ml-prediction.service.ts b/src/investment/portfolio/services/ml-prediction.service.ts index 150bbf1..b8779de 100644 --- a/src/investment/portfolio/services/ml-prediction.service.ts +++ b/src/investment/portfolio/services/ml-prediction.service.ts @@ -180,6 +180,3 @@ export class MLPredictionService { }; } } - - - diff --git a/src/investment/portfolio/services/performance-analytics.service.spec.ts b/src/investment/portfolio/services/performance-analytics.service.spec.ts index d328617..1166834 100644 --- a/src/investment/portfolio/services/performance-analytics.service.spec.ts +++ b/src/investment/portfolio/services/performance-analytics.service.spec.ts @@ -352,6 +352,3 @@ describe("PerformanceAnalyticsService", () => { }); }); }); - - - diff --git a/src/investment/portfolio/services/performance-analytics.service.ts b/src/investment/portfolio/services/performance-analytics.service.ts index 7df15a3..b30b66a 100644 --- a/src/investment/portfolio/services/performance-analytics.service.ts +++ b/src/investment/portfolio/services/performance-analytics.service.ts @@ -611,6 +611,3 @@ export class PerformanceAnalyticsService { }; } } - - - diff --git a/src/investment/portfolio/services/portfolio-constraint.service.spec.ts b/src/investment/portfolio/services/portfolio-constraint.service.spec.ts index 9b2c41f..f53ac98 100644 --- a/src/investment/portfolio/services/portfolio-constraint.service.spec.ts +++ b/src/investment/portfolio/services/portfolio-constraint.service.spec.ts @@ -246,6 +246,3 @@ describe("PortfolioConstraintService", () => { ).toBe(true); }); }); - - - diff --git a/src/investment/portfolio/services/portfolio-constraint.service.ts b/src/investment/portfolio/services/portfolio-constraint.service.ts index 7ba6b34..3038660 100644 --- a/src/investment/portfolio/services/portfolio-constraint.service.ts +++ b/src/investment/portfolio/services/portfolio-constraint.service.ts @@ -241,6 +241,3 @@ export class PortfolioConstraintService { return maxAllocation > 0.5 ? (maxAllocation - 0.5) * 40 : 0; } } - - - diff --git a/src/investment/portfolio/services/portfolio.service.ts b/src/investment/portfolio/services/portfolio.service.ts index 2ee0250..f91a0e7 100644 --- a/src/investment/portfolio/services/portfolio.service.ts +++ b/src/investment/portfolio/services/portfolio.service.ts @@ -635,6 +635,3 @@ export class PortfolioService { await this.portfolioRepository.delete(portfolioId); } } - - - diff --git a/src/investment/portfolio/services/rebalancing.service.ts b/src/investment/portfolio/services/rebalancing.service.ts index 843f7ea..e5b08f2 100644 --- a/src/investment/portfolio/services/rebalancing.service.ts +++ b/src/investment/portfolio/services/rebalancing.service.ts @@ -350,6 +350,3 @@ export class RebalancingService { } } } - - - diff --git a/src/investment/portfolio/services/trading-transaction.service.ts b/src/investment/portfolio/services/trading-transaction.service.ts index febc992..40026f8 100644 --- a/src/investment/portfolio/services/trading-transaction.service.ts +++ b/src/investment/portfolio/services/trading-transaction.service.ts @@ -1,23 +1,192 @@ -import { Injectable, Logger } from "@nestjs/common"; +import { Injectable, Logger, NotFoundException } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { + Repository, + Between, + MoreThanOrEqual, + LessThanOrEqual, + FindOptionsWhere, +} from "typeorm"; +import { + Transaction, + TransactionStatus, + TransactionType, +} from "../entities/transaction.entity"; +import { + CreateTransactionDto, + FilterTransactionDto, +} from "../dto/transaction.dto"; +import { PortfolioAsset } from "../entities/portfolio-asset.entity"; +import { Portfolio } from "../entities/portfolio.entity"; +import * as fastcsv from "fast-csv"; +import { Readable } from "stream"; @Injectable() export class TradingTransactionService { private readonly logger = new Logger(TradingTransactionService.name); + constructor( + @InjectRepository(Transaction) + private readonly transactionRepository: Repository, + @InjectRepository(Portfolio) + private readonly portfolioRepository: Repository, + @InjectRepository(PortfolioAsset) + private readonly portfolioAssetRepository: Repository, + ) {} + + async createTransaction( + createTransactionDto: CreateTransactionDto, + ): Promise { + const { portfolioId, portfolioAssetId, ...transactionData } = + createTransactionDto; + + const portfolio = await this.portfolioRepository.findOne({ + where: { id: portfolioId }, + }); + if (!portfolio) { + throw new NotFoundException(`Portfolio with ID ${portfolioId} not found`); + } + + let portfolioAsset: PortfolioAsset | null = null; + if (portfolioAssetId) { + portfolioAsset = await this.portfolioAssetRepository.findOne({ + where: { id: portfolioAssetId }, + }); + if (!portfolioAsset) { + throw new NotFoundException( + `PortfolioAsset with ID ${portfolioAssetId} not found`, + ); + } + } + + const transaction = this.transactionRepository.create({ + ...transactionData, + portfolio, + portfolioAsset, + }); + + await this.validateTransaction(transaction); + + return this.transactionRepository.save(transaction); + } + + async getTransactions( + portfolioId: string, + filterDto: FilterTransactionDto, + ): Promise { + const { type, startDate, endDate, status } = filterDto; + + const where: FindOptionsWhere = { portfolioId }; + + if (type) { + where.type = type; + } + + if (startDate && endDate) { + where.date = Between(new Date(startDate), new Date(endDate)); + } else if (startDate) { + where.date = MoreThanOrEqual(new Date(startDate)); + } else if (endDate) { + where.date = LessThanOrEqual(new Date(endDate)); + } + + if (status) { + where.status = status; + } else { + where.status = TransactionStatus.ACTIVE; + } + + return this.transactionRepository.find({ + where, + order: { date: "DESC" }, + }); + } + + async archiveTransaction(id: string): Promise { + const transaction = await this.transactionRepository.findOne({ + where: { id }, + }); + if (!transaction) { + throw new NotFoundException(`Transaction with ID ${id} not found`); + } + + transaction.status = TransactionStatus.ARCHIVED; + return this.transactionRepository.save(transaction); + } + + async exportTransactionsToCsv( + portfolioId: string, + filterDto: FilterTransactionDto, + ): Promise { + const transactions = await this.getTransactions(portfolioId, filterDto); + return fastcsv.write(transactions, { headers: true }); + } + + async calculateCostBasis(portfolioAssetId: string): Promise<{ + averageCost: number; + totalCost: number; + totalQuantity: number; + }> { + const transactions = await this.transactionRepository.find({ + where: { + portfolioAssetId, + type: TransactionType.BUY, + status: TransactionStatus.ACTIVE, + }, + }); + + let totalCost = 0; + let totalQuantity = 0; + + for (const transaction of transactions) { + totalCost += transaction.amount * transaction.price; + totalQuantity += transaction.amount; + } + + const averageCost = totalQuantity > 0 ? totalCost / totalQuantity : 0; + + return { averageCost, totalCost, totalQuantity }; + } + async executeTrade( portfolioId: string, ticker: string, action: "buy" | "sell", quantity: number, price: number, - ): Promise { - this.logger.log( - `Executing ${action} trade for ${quantity} of ${ticker} at $${price} for portfolio ${portfolioId}`, - ); - // TODO: Implement actual trade execution logic here - return Promise.resolve(); - } -} + ): Promise { + const portfolioAsset = await this.portfolioAssetRepository.findOne({ + where: { portfolioId, ticker }, + }); + if (!portfolioAsset) { + throw new NotFoundException( + `Asset with ticker ${ticker} not found in portfolio ${portfolioId}`, + ); + } + const createTransactionDto: CreateTransactionDto = { + portfolioId, + portfolioAssetId: portfolioAsset.id, + type: action === "buy" ? TransactionType.BUY : TransactionType.SELL, + amount: quantity, + price: price, + date: new Date().toISOString(), + }; + return this.createTransaction(createTransactionDto); + } + private async validateTransaction(transaction: Transaction): Promise { + if ( + transaction.type === TransactionType.SELL || + transaction.type === TransactionType.UNSTAKE + ) { + const { totalQuantity } = await this.calculateCostBasis( + transaction.portfolioAssetId, + ); + if (transaction.amount > totalQuantity) { + throw new Error("Insufficient balance for this transaction"); + } + } + } +} diff --git a/src/investment/risk-management/circuit-breaker.service.spec.ts b/src/investment/risk-management/circuit-breaker.service.spec.ts index fe5984d..3c714fc 100644 --- a/src/investment/risk-management/circuit-breaker.service.spec.ts +++ b/src/investment/risk-management/circuit-breaker.service.spec.ts @@ -174,6 +174,3 @@ describe("CircuitBreakerService", () => { }); }); }); - - - diff --git a/src/investment/risk-management/circuit-breaker.service.ts b/src/investment/risk-management/circuit-breaker.service.ts index 7db5542..74305e3 100644 --- a/src/investment/risk-management/circuit-breaker.service.ts +++ b/src/investment/risk-management/circuit-breaker.service.ts @@ -215,6 +215,3 @@ export class CircuitBreakerService { } } } - - - diff --git a/src/investment/risk-management/dto/risk.dto.ts b/src/investment/risk-management/dto/risk.dto.ts index 901c7e3..22cc3c9 100644 --- a/src/investment/risk-management/dto/risk.dto.ts +++ b/src/investment/risk-management/dto/risk.dto.ts @@ -85,6 +85,3 @@ export class PositionSizeDto { @Min(0) volatility: number; } - - - diff --git a/src/investment/risk-management/risk-management.controller.ts b/src/investment/risk-management/risk-management.controller.ts index 0f33871..8bcf2b0 100644 --- a/src/investment/risk-management/risk-management.controller.ts +++ b/src/investment/risk-management/risk-management.controller.ts @@ -112,6 +112,3 @@ export class RiskManagementController { return { triggered, asset, userId }; } } - - - diff --git a/src/investment/risk-management/risk-management.health.ts b/src/investment/risk-management/risk-management.health.ts index 44e4ff7..2a43f7b 100644 --- a/src/investment/risk-management/risk-management.health.ts +++ b/src/investment/risk-management/risk-management.health.ts @@ -29,6 +29,3 @@ export class RiskManagementHealthIndicator extends HealthIndicator { ); } } - - - diff --git a/src/investment/risk-management/risk-management.module.ts b/src/investment/risk-management/risk-management.module.ts index c6a9524..0040ca1 100644 --- a/src/investment/risk-management/risk-management.module.ts +++ b/src/investment/risk-management/risk-management.module.ts @@ -20,6 +20,3 @@ import { RiskManagementHealthIndicator } from "./risk-management.health"; ], }) export class RiskManagementModule {} - - - diff --git a/src/investment/risk-management/risk-management.service.ts b/src/investment/risk-management/risk-management.service.ts index 5b9b6c9..e4563d2 100644 --- a/src/investment/risk-management/risk-management.service.ts +++ b/src/investment/risk-management/risk-management.service.ts @@ -296,6 +296,3 @@ export class RiskManagementService { return alerts; } } - - - diff --git a/src/main.ts b/src/main.ts index 6f18c7b..afd202c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -166,6 +166,3 @@ process.on("unhandledRejection", (reason: any) => { logger.error({ error, stack: error.stack }, "Unhandled Rejection"); process.exit(1); }); - - - diff --git a/src/observability/database-timing.interceptor.ts b/src/observability/database-timing.interceptor.ts index e6003c3..dc77920 100644 --- a/src/observability/database-timing.interceptor.ts +++ b/src/observability/database-timing.interceptor.ts @@ -38,6 +38,3 @@ export class DatabaseTimingInterceptor implements NestInterceptor { return next.handle(); } } - - - diff --git a/src/observability/monitoring-dashboard.spec.ts b/src/observability/monitoring-dashboard.spec.ts index cfa6575..4bd853b 100644 --- a/src/observability/monitoring-dashboard.spec.ts +++ b/src/observability/monitoring-dashboard.spec.ts @@ -144,6 +144,3 @@ describe("Grafana application-overview dashboard (issue #25)", () => { expect(joined).toContain("histogram_quantile(0.99"); }); }); - - - diff --git a/src/observability/observability.controller.spec.ts b/src/observability/observability.controller.spec.ts index 157be27..ea8393e 100644 --- a/src/observability/observability.controller.spec.ts +++ b/src/observability/observability.controller.spec.ts @@ -86,6 +86,3 @@ describe("ObservabilityController - Prometheus /metrics endpoint (issue #25)", ( ); }); }); - - - diff --git a/src/observability/observability.controller.ts b/src/observability/observability.controller.ts index 8151bcc..3ec1a3a 100644 --- a/src/observability/observability.controller.ts +++ b/src/observability/observability.controller.ts @@ -131,6 +131,3 @@ export class ObservabilityController { return register.metrics(); } } - - - diff --git a/src/observability/observability.module.ts b/src/observability/observability.module.ts index 31411b0..64216f4 100644 --- a/src/observability/observability.module.ts +++ b/src/observability/observability.module.ts @@ -30,6 +30,3 @@ export class ObservabilityModule implements NestModule { consumer.apply(RequestTimingMiddleware).forRoutes("*"); } } - - - diff --git a/src/observability/performance-baseline.service.ts b/src/observability/performance-baseline.service.ts index 6861978..6d6c2b3 100644 --- a/src/observability/performance-baseline.service.ts +++ b/src/observability/performance-baseline.service.ts @@ -218,6 +218,3 @@ export class PerformanceBaselineService { this.logger.log("Performance baselines have been reset"); } } - - - diff --git a/src/observability/profiling.controller.ts b/src/observability/profiling.controller.ts index cf64ec6..8446ac3 100644 --- a/src/observability/profiling.controller.ts +++ b/src/observability/profiling.controller.ts @@ -129,6 +129,3 @@ export class ProfilingController { return this.profilingService.checkMemoryHealth(); } } - - - diff --git a/src/observability/profiling.service.ts b/src/observability/profiling.service.ts index 3cdd919..0213e98 100644 --- a/src/observability/profiling.service.ts +++ b/src/observability/profiling.service.ts @@ -267,6 +267,3 @@ export class ProfilingService { }; } } - - - diff --git a/src/observability/request-timing.middleware.spec.ts b/src/observability/request-timing.middleware.spec.ts index aa6aacc..772c583 100644 --- a/src/observability/request-timing.middleware.spec.ts +++ b/src/observability/request-timing.middleware.spec.ts @@ -194,6 +194,3 @@ describe("RequestTimingMiddleware Prometheus integration (issue #25)", () => { expect(remaining).toBe(0); }); }); - - - diff --git a/src/observability/request-timing.middleware.ts b/src/observability/request-timing.middleware.ts index a064c49..5f13734 100644 --- a/src/observability/request-timing.middleware.ts +++ b/src/observability/request-timing.middleware.ts @@ -273,6 +273,3 @@ export class RequestTimingMiddleware implements NestMiddleware { return Array.from(this.activeRequests.values()); } } - - - diff --git a/src/portfolio/controllers/portfolio.controller.ts b/src/portfolio/controllers/portfolio.controller.ts index 197a756..79ec5ea 100644 --- a/src/portfolio/controllers/portfolio.controller.ts +++ b/src/portfolio/controllers/portfolio.controller.ts @@ -36,6 +36,3 @@ export class PortfolioController { ); } } - - - diff --git a/src/portfolio/portfolio.module.ts b/src/portfolio/portfolio.module.ts index c7c51dc..be0d161 100644 --- a/src/portfolio/portfolio.module.ts +++ b/src/portfolio/portfolio.module.ts @@ -7,6 +7,3 @@ import { PortfolioController } from "./controllers/portfolio.controller"; providers: [RebalancingService], }) export class PortfolioModule {} - - - diff --git a/src/portfolio/services/rebalancing.service.ts b/src/portfolio/services/rebalancing.service.ts index f6de75f..a69dc6d 100644 --- a/src/portfolio/services/rebalancing.service.ts +++ b/src/portfolio/services/rebalancing.service.ts @@ -50,6 +50,3 @@ export class RebalancingService { return { success: true }; } } - - - diff --git a/src/profiling/profiling.controller.ts b/src/profiling/profiling.controller.ts index dadf223..c9e8a8e 100644 --- a/src/profiling/profiling.controller.ts +++ b/src/profiling/profiling.controller.ts @@ -278,6 +278,3 @@ export class ProfilingController { res.send(html); } } - - - diff --git a/src/profiling/profiling.middleware.ts b/src/profiling/profiling.middleware.ts index 421e9ce..9af7e2f 100644 --- a/src/profiling/profiling.middleware.ts +++ b/src/profiling/profiling.middleware.ts @@ -37,6 +37,3 @@ export class ProfilingMiddleware implements NestMiddleware { }); } } - - - diff --git a/src/profiling/profiling.module.ts b/src/profiling/profiling.module.ts index 7c4b486..ffb3ba7 100644 --- a/src/profiling/profiling.module.ts +++ b/src/profiling/profiling.module.ts @@ -8,6 +8,3 @@ import { ProfilingController } from "./profiling.controller"; exports: [ProfilingService], }) export class ProfilingModule {} - - - diff --git a/src/profiling/profiling.service.ts b/src/profiling/profiling.service.ts index b85d714..1a77502 100644 --- a/src/profiling/profiling.service.ts +++ b/src/profiling/profiling.service.ts @@ -346,6 +346,3 @@ export class ProfilingService { return false; } } - - - diff --git a/src/types/jest-globals.d.ts b/src/types/jest-globals.d.ts index df935a0..a520c45 100644 --- a/src/types/jest-globals.d.ts +++ b/src/types/jest-globals.d.ts @@ -6,6 +6,3 @@ declare const it: any; declare const beforeAll: any; declare const afterAll: any; declare const expect: any; - - - diff --git a/src/types/supertest.d.ts b/src/types/supertest.d.ts index 88a2607..8ce7c32 100644 --- a/src/types/supertest.d.ts +++ b/src/types/supertest.d.ts @@ -2,6 +2,3 @@ declare module "supertest" { const anyExport: any; export default anyExport; } - - - diff --git a/test/audit/provenance.e2e-spec.ts b/test/audit/provenance.e2e-spec.ts index 8d50560..f957703 100644 --- a/test/audit/provenance.e2e-spec.ts +++ b/test/audit/provenance.e2e-spec.ts @@ -1,19 +1,19 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { ConfigModule } from '@nestjs/config'; -import { JwtService } from '@nestjs/jwt'; -import { AuditModule } from '../../src/infrastructure/audit/audit.module'; -import { ProvenanceService } from '../../src/infrastructure/audit/provenance.service'; +import { Test, TestingModule } from "@nestjs/testing"; +import { INestApplication } from "@nestjs/common"; +import * as request from "supertest"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { ConfigModule } from "@nestjs/config"; +import { JwtService } from "@nestjs/jwt"; +import { AuditModule } from "../../src/infrastructure/audit/audit.module"; +import { ProvenanceService } from "../../src/infrastructure/audit/provenance.service"; import { ProvenanceRecord, ProvenanceStatus, ProvenanceAction, -} from '../../src/infrastructure/audit/entities/provenance-record.entity'; -import { User, UserRole } from '../../src/core/user/entities/user.entity'; +} from "../../src/infrastructure/audit/entities/provenance-record.entity"; +import { User, UserRole } from "../../src/core/user/entities/user.entity"; -describe('ProvenanceController (e2e)', () => { +describe("ProvenanceController (e2e)", () => { let app: INestApplication; let provenanceService: ProvenanceService; let jwtService: JwtService; @@ -27,11 +27,11 @@ describe('ProvenanceController (e2e)', () => { imports: [ ConfigModule.forRoot({ isGlobal: true, - envFilePath: '.env.test', + envFilePath: ".env.test", }), TypeOrmModule.forRoot({ - type: 'sqlite', - database: ':memory:', + type: "sqlite", + database: ":memory:", entities: [ProvenanceRecord, User], synchronize: true, }), @@ -45,18 +45,18 @@ describe('ProvenanceController (e2e)', () => { jwtService = moduleFixture.get(JwtService); // Create test tokens - testUserId = '550e8400-e29b-41d4-a716-446655440001'; - testAdminId = '550e8400-e29b-41d4-a716-446655440002'; + testUserId = "550e8400-e29b-41d4-a716-446655440001"; + testAdminId = "550e8400-e29b-41d4-a716-446655440002"; userToken = jwtService.sign({ sub: testUserId, - email: 'user@test.com', + email: "user@test.com", role: UserRole.USER, }); adminToken = jwtService.sign({ sub: testAdminId, - email: 'admin@test.com', + email: "admin@test.com", role: UserRole.ADMIN, }); @@ -67,19 +67,19 @@ describe('ProvenanceController (e2e)', () => { await app.close(); }); - describe('POST /provenance (via service)', () => { - it('should create a provenance record with valid data', async () => { + describe("POST /provenance (via service)", () => { + it("should create a provenance record with valid data", async () => { const record = await provenanceService.createProvenanceRecord({ - agentId: 'agent-123', + agentId: "agent-123", userId: testUserId, action: ProvenanceAction.REQUEST_RECEIVED, - input: { query: 'test query' }, + input: { query: "test query" }, status: ProvenanceStatus.SUCCESS, }); expect(record).toBeDefined(); expect(record.id).toBeDefined(); - expect(record.agentId).toBe('agent-123'); + expect(record.agentId).toBe("agent-123"); expect(record.userId).toBe(testUserId); expect(record.action).toBe(ProvenanceAction.REQUEST_RECEIVED); expect(record.status).toBe(ProvenanceStatus.SUCCESS); @@ -87,49 +87,49 @@ describe('ProvenanceController (e2e)', () => { expect(record.recordHash).toBeDefined(); }); - it('should create a provenance record with error status', async () => { + it("should create a provenance record with error status", async () => { const record = await provenanceService.createProvenanceRecord({ - agentId: 'agent-456', + agentId: "agent-456", userId: testUserId, action: ProvenanceAction.PROVIDER_CALL, - input: { query: 'test query' }, + input: { query: "test query" }, status: ProvenanceStatus.FAILED, - error: 'Rate limit exceeded', - provider: 'openai', + error: "Rate limit exceeded", + provider: "openai", }); expect(record).toBeDefined(); expect(record.status).toBe(ProvenanceStatus.FAILED); - expect(record.error).toBe('Rate limit exceeded'); - expect(record.provider).toBe('openai'); + expect(record.error).toBe("Rate limit exceeded"); + expect(record.provider).toBe("openai"); }); }); - describe('GET /provenance', () => { + describe("GET /provenance", () => { beforeAll(async () => { // Create test records await provenanceService.createProvenanceRecord({ - agentId: 'agent-test', + agentId: "agent-test", userId: testUserId, action: ProvenanceAction.REQUEST_RECEIVED, - input: { query: 'test 1' }, + input: { query: "test 1" }, status: ProvenanceStatus.SUCCESS, }); await provenanceService.createProvenanceRecord({ - agentId: 'agent-test', + agentId: "agent-test", userId: testUserId, action: ProvenanceAction.PROVIDER_CALL, - input: { query: 'test 2' }, + input: { query: "test 2" }, status: ProvenanceStatus.SUCCESS, - provider: 'openai', + provider: "openai", }); }); - it('should return provenance records with authentication', () => { + it("should return provenance records with authentication", () => { return request(app.getHttpServer()) - .get('/provenance') - .set('Authorization', `Bearer ${userToken}`) + .get("/provenance") + .set("Authorization", `Bearer ${userToken}`) .expect(200) .expect((res) => { expect(res.body.data).toBeDefined(); @@ -140,35 +140,37 @@ describe('ProvenanceController (e2e)', () => { }); }); - it('should return 401 without authentication', () => { - return request(app.getHttpServer()) - .get('/provenance') - .expect(401); + it("should return 401 without authentication", () => { + return request(app.getHttpServer()).get("/provenance").expect(401); }); - it('should filter by agentId', async () => { + it("should filter by agentId", async () => { const response = await request(app.getHttpServer()) - .get('/provenance?agentId=agent-test') - .set('Authorization', `Bearer ${userToken}`) + .get("/provenance?agentId=agent-test") + .set("Authorization", `Bearer ${userToken}`) .expect(200); expect(response.body.data.length).toBeGreaterThan(0); - expect(response.body.data.every((r: any) => r.agentId === 'agent-test')).toBe(true); + expect( + response.body.data.every((r: any) => r.agentId === "agent-test"), + ).toBe(true); }); - it('should filter by status', async () => { + it("should filter by status", async () => { const response = await request(app.getHttpServer()) - .get('/provenance?status=success') - .set('Authorization', `Bearer ${userToken}`) + .get("/provenance?status=success") + .set("Authorization", `Bearer ${userToken}`) .expect(200); - expect(response.body.data.every((r: any) => r.status === 'success')).toBe(true); + expect(response.body.data.every((r: any) => r.status === "success")).toBe( + true, + ); }); - it('should paginate results', async () => { + it("should paginate results", async () => { const response = await request(app.getHttpServer()) - .get('/provenance?page=1&limit=1') - .set('Authorization', `Bearer ${userToken}`) + .get("/provenance?page=1&limit=1") + .set("Authorization", `Bearer ${userToken}`) .expect(200); expect(response.body.data.length).toBeLessThanOrEqual(1); @@ -176,96 +178,96 @@ describe('ProvenanceController (e2e)', () => { }); }); - describe('GET /provenance/:id', () => { + describe("GET /provenance/:id", () => { let testRecordId: string; beforeAll(async () => { const record = await provenanceService.createProvenanceRecord({ - agentId: 'agent-specific', + agentId: "agent-specific", userId: testUserId, action: ProvenanceAction.SUBMISSION, - input: { query: 'specific test' }, - output: { result: 'success' }, + input: { query: "specific test" }, + output: { result: "success" }, status: ProvenanceStatus.SUCCESS, - onChainTxHash: '0x1234567890abcdef', + onChainTxHash: "0x1234567890abcdef", }); testRecordId = record.id; }); - it('should return a specific provenance record', async () => { + it("should return a specific provenance record", async () => { const response = await request(app.getHttpServer()) .get(`/provenance/${testRecordId}`) - .set('Authorization', `Bearer ${userToken}`) + .set("Authorization", `Bearer ${userToken}`) .expect(200); expect(response.body.id).toBe(testRecordId); - expect(response.body.agentId).toBe('agent-specific'); - expect(response.body.onChainTxHash).toBe('0x1234567890abcdef'); + expect(response.body.agentId).toBe("agent-specific"); + expect(response.body.onChainTxHash).toBe("0x1234567890abcdef"); }); - it('should return 404 for non-existent record', () => { + it("should return 404 for non-existent record", () => { return request(app.getHttpServer()) - .get('/provenance/550e8400-e29b-41d4-a716-446655440999') - .set('Authorization', `Bearer ${userToken}`) + .get("/provenance/550e8400-e29b-41d4-a716-446655440999") + .set("Authorization", `Bearer ${userToken}`) .expect(404); }); }); - describe('GET /provenance/:id/export', () => { + describe("GET /provenance/:id/export", () => { let testRecordId: string; beforeAll(async () => { const record = await provenanceService.createProvenanceRecord({ - agentId: 'agent-export', + agentId: "agent-export", userId: testUserId, action: ProvenanceAction.RESULT_NORMALIZATION, - input: { raw: 'data' }, - output: { normalized: 'data' }, + input: { raw: "data" }, + output: { normalized: "data" }, status: ProvenanceStatus.SUCCESS, }); testRecordId = record.id; }); - it('should export record as JSON', async () => { + it("should export record as JSON", async () => { const response = await request(app.getHttpServer()) .get(`/provenance/${testRecordId}/export?format=json`) - .set('Authorization', `Bearer ${userToken}`) + .set("Authorization", `Bearer ${userToken}`) .expect(200); - expect(response.headers['content-type']).toContain('application/json'); + expect(response.headers["content-type"]).toContain("application/json"); const data = JSON.parse(response.text); expect(data.id).toBe(testRecordId); }); - it('should export record as CSV', async () => { + it("should export record as CSV", async () => { const response = await request(app.getHttpServer()) .get(`/provenance/${testRecordId}/export?format=csv`) - .set('Authorization', `Bearer ${userToken}`) + .set("Authorization", `Bearer ${userToken}`) .expect(200); - expect(response.headers['content-type']).toContain('text/csv'); - expect(response.text).toContain('id,agentId,userId'); + expect(response.headers["content-type"]).toContain("text/csv"); + expect(response.text).toContain("id,agentId,userId"); }); }); - describe('POST /provenance/:id/verify', () => { + describe("POST /provenance/:id/verify", () => { let testRecordId: string; beforeAll(async () => { const record = await provenanceService.createProvenanceRecord({ - agentId: 'agent-verify', + agentId: "agent-verify", userId: testUserId, action: ProvenanceAction.REQUEST_RECEIVED, - input: { test: 'data' }, + input: { test: "data" }, status: ProvenanceStatus.SUCCESS, }); testRecordId = record.id; }); - it('should verify a valid signature', async () => { + it("should verify a valid signature", async () => { const response = await request(app.getHttpServer()) .post(`/provenance/${testRecordId}/verify`) - .set('Authorization', `Bearer ${userToken}`) + .set("Authorization", `Bearer ${userToken}`) .expect(200); expect(response.body.isValid).toBeDefined(); @@ -274,35 +276,37 @@ describe('ProvenanceController (e2e)', () => { }); }); - describe('GET /provenance/agents/:agentId', () => { + describe("GET /provenance/agents/:agentId", () => { beforeAll(async () => { await provenanceService.createProvenanceRecord({ - agentId: 'agent-specific-2', + agentId: "agent-specific-2", userId: testUserId, action: ProvenanceAction.REQUEST_RECEIVED, - input: { query: 'agent test' }, + input: { query: "agent test" }, status: ProvenanceStatus.SUCCESS, }); }); - it('should return provenance for a specific agent', async () => { + it("should return provenance for a specific agent", async () => { const response = await request(app.getHttpServer()) - .get('/provenance/agents/agent-specific-2') - .set('Authorization', `Bearer ${userToken}`) + .get("/provenance/agents/agent-specific-2") + .set("Authorization", `Bearer ${userToken}`) .expect(200); expect(response.body.data).toBeDefined(); expect(Array.isArray(response.body.data)).toBe(true); - expect(response.body.data.every((r: any) => r.agentId === 'agent-specific-2')).toBe(true); + expect( + response.body.data.every((r: any) => r.agentId === "agent-specific-2"), + ).toBe(true); }); }); - describe('GET /provenance/agents/:agentId/timeline', () => { + describe("GET /provenance/agents/:agentId/timeline", () => { beforeAll(async () => { // Create multiple records for timeline for (let i = 0; i < 3; i++) { await provenanceService.createProvenanceRecord({ - agentId: 'agent-timeline', + agentId: "agent-timeline", userId: testUserId, action: ProvenanceAction.REQUEST_RECEIVED, input: { iteration: i }, @@ -311,28 +315,28 @@ describe('ProvenanceController (e2e)', () => { } }); - it('should return chronological timeline for agent', async () => { + it("should return chronological timeline for agent", async () => { const response = await request(app.getHttpServer()) - .get('/provenance/agents/agent-timeline/timeline') - .set('Authorization', `Bearer ${userToken}`) + .get("/provenance/agents/agent-timeline/timeline") + .set("Authorization", `Bearer ${userToken}`) .expect(200); - expect(response.body.agentId).toBe('agent-timeline'); + expect(response.body.agentId).toBe("agent-timeline"); expect(response.body.timeline).toBeDefined(); expect(Array.isArray(response.body.timeline)).toBe(true); expect(response.body.total).toBeGreaterThanOrEqual(3); }); }); - describe('Authorization', () => { + describe("Authorization", () => { let otherUserId: string; let otherUserToken: string; beforeAll(() => { - otherUserId = '550e8400-e29b-41d4-a716-446655440003'; + otherUserId = "550e8400-e29b-41d4-a716-446655440003"; otherUserToken = jwtService.sign({ sub: otherUserId, - email: 'other@test.com', + email: "other@test.com", role: UserRole.USER, }); }); @@ -340,85 +344,88 @@ describe('ProvenanceController (e2e)', () => { beforeAll(async () => { // Create record for other user await provenanceService.createProvenanceRecord({ - agentId: 'agent-other', + agentId: "agent-other", userId: otherUserId, action: ProvenanceAction.REQUEST_RECEIVED, - input: { query: 'other user' }, + input: { query: "other user" }, status: ProvenanceStatus.SUCCESS, }); }); - it('should allow admin to access any user provenance', async () => { + it("should allow admin to access any user provenance", async () => { const response = await request(app.getHttpServer()) .get(`/provenance/users/${otherUserId}`) - .set('Authorization', `Bearer ${adminToken}`) + .set("Authorization", `Bearer ${adminToken}`) .expect(200); expect(response.body.data).toBeDefined(); }); - it('should allow user to access their own provenance', async () => { + it("should allow user to access their own provenance", async () => { const response = await request(app.getHttpServer()) .get(`/provenance/users/${testUserId}`) - .set('Authorization', `Bearer ${userToken}`) + .set("Authorization", `Bearer ${userToken}`) .expect(200); expect(response.body.data).toBeDefined(); }); - it('should forbid user from accessing other user provenance', () => { + it("should forbid user from accessing other user provenance", () => { return request(app.getHttpServer()) .get(`/provenance/users/${otherUserId}`) - .set('Authorization', `Bearer ${userToken}`) + .set("Authorization", `Bearer ${userToken}`) .expect(403); }); }); - describe('ProvenanceService', () => { - it('should update a provenance record', async () => { + describe("ProvenanceService", () => { + it("should update a provenance record", async () => { const record = await provenanceService.createProvenanceRecord({ - agentId: 'agent-update', + agentId: "agent-update", userId: testUserId, action: ProvenanceAction.PROVIDER_CALL, - input: { query: 'update test' }, + input: { query: "update test" }, status: ProvenanceStatus.PENDING, }); - const updated = await provenanceService.updateProvenanceRecord(record.id, { - status: ProvenanceStatus.SUCCESS, - output: { result: 'completed' }, - processingDurationMs: 1500, - }); + const updated = await provenanceService.updateProvenanceRecord( + record.id, + { + status: ProvenanceStatus.SUCCESS, + output: { result: "completed" }, + processingDurationMs: 1500, + }, + ); expect(updated.status).toBe(ProvenanceStatus.SUCCESS); - expect(updated.output).toEqual({ result: 'completed' }); + expect(updated.output).toEqual({ result: "completed" }); expect(updated.processingDurationMs).toBe(1500); }); - it('should export multiple records to CSV', async () => { + it("should export multiple records to CSV", async () => { // Create test records await provenanceService.createProvenanceRecord({ - agentId: 'agent-csv', + agentId: "agent-csv", userId: testUserId, action: ProvenanceAction.REQUEST_RECEIVED, - input: { query: 'csv 1' }, + input: { query: "csv 1" }, status: ProvenanceStatus.SUCCESS, }); await provenanceService.createProvenanceRecord({ - agentId: 'agent-csv', + agentId: "agent-csv", userId: testUserId, action: ProvenanceAction.REQUEST_RECEIVED, - input: { query: 'csv 2' }, + input: { query: "csv 2" }, status: ProvenanceStatus.SUCCESS, }); const csv = await provenanceService.exportProvenanceToCsv({ - agentId: 'agent-csv', + agentId: "agent-csv", }); - expect(csv).toContain('id,agentId,userId'); - expect(csv).toContain('agent-csv'); + expect(csv).toContain("id,agentId,userId"); + expect(csv).toContain("agent-csv"); }); }); }); diff --git a/test/auth/wallet-advanced.e2e-spec.ts b/test/auth/wallet-advanced.e2e-spec.ts index 62ff127..1afb75a 100644 --- a/test/auth/wallet-advanced.e2e-spec.ts +++ b/test/auth/wallet-advanced.e2e-spec.ts @@ -1,16 +1,20 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { ConfigModule } from '@nestjs/config'; -import { JwtService } from '@nestjs/jwt'; -import { Wallet } from 'ethers'; -import { AuthModule } from '../../src/core/auth/auth.module'; -import { User, UserRole } from '../../src/core/user/entities/user.entity'; -import { Wallet as WalletEntity, WalletStatus, WalletType } from '../../src/core/auth/entities/wallet.entity'; -import { EmailVerification } from '../../src/core/auth/entities/email-verification.entity'; - -describe('Advanced Wallet Authentication (e2e)', () => { +import { Test, TestingModule } from "@nestjs/testing"; +import { INestApplication } from "@nestjs/common"; +import * as request from "supertest"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { ConfigModule } from "@nestjs/config"; +import { JwtService } from "@nestjs/jwt"; +import { Wallet } from "ethers"; +import { AuthModule } from "../../src/core/auth/auth.module"; +import { User, UserRole } from "../../src/core/user/entities/user.entity"; +import { + Wallet as WalletEntity, + WalletStatus, + WalletType, +} from "../../src/core/auth/entities/wallet.entity"; +import { EmailVerification } from "../../src/core/auth/entities/email-verification.entity"; + +describe("Advanced Wallet Authentication (e2e)", () => { let app: INestApplication; let jwtService: JwtService; let userToken: string; @@ -23,11 +27,11 @@ describe('Advanced Wallet Authentication (e2e)', () => { imports: [ ConfigModule.forRoot({ isGlobal: true, - envFilePath: '.env.test', + envFilePath: ".env.test", }), TypeOrmModule.forRoot({ - type: 'sqlite', - database: ':memory:', + type: "sqlite", + database: ":memory:", entities: [User, WalletEntity, EmailVerification], synchronize: true, }), @@ -43,7 +47,7 @@ describe('Advanced Wallet Authentication (e2e)', () => { primaryWallet = Wallet.createRandom(); secondaryWallet = Wallet.createRandom(); - testUserId = '550e8400-e29b-41d4-a716-446655440001'; + testUserId = "550e8400-e29b-41d4-a716-446655440001"; userToken = jwtService.sign({ sub: testUserId, @@ -58,11 +62,11 @@ describe('Advanced Wallet Authentication (e2e)', () => { await app.close(); }); - describe('Multi-Wallet Support', () => { - it('should link a primary wallet', async () => { + describe("Multi-Wallet Support", () => { + it("should link a primary wallet", async () => { // First get a challenge const challengeRes = await request(app.getHttpServer()) - .post('/auth/challenge') + .post("/auth/challenge") .send({ address: primaryWallet.address }) .expect(200); @@ -70,25 +74,27 @@ describe('Advanced Wallet Authentication (e2e)', () => { const signature = await primaryWallet.signMessage(message); const response = await request(app.getHttpServer()) - .post('/auth/link-wallet') - .set('Authorization', `Bearer ${userToken}`) + .post("/auth/link-wallet") + .set("Authorization", `Bearer ${userToken}`) .send({ walletAddress: primaryWallet.address, message, signature, - walletName: 'Primary Wallet', + walletName: "Primary Wallet", }) .expect(201); expect(response.body.walletId).toBeDefined(); - expect(response.body.walletAddress).toBe(primaryWallet.address.toLowerCase()); + expect(response.body.walletAddress).toBe( + primaryWallet.address.toLowerCase(), + ); expect(response.body.type).toBe(WalletType.PRIMARY); }); - it('should link a secondary wallet', async () => { + it("should link a secondary wallet", async () => { // Get challenge for secondary wallet const challengeRes = await request(app.getHttpServer()) - .post('/auth/challenge') + .post("/auth/challenge") .send({ address: secondaryWallet.address }) .expect(200); @@ -96,13 +102,13 @@ describe('Advanced Wallet Authentication (e2e)', () => { const signature = await secondaryWallet.signMessage(message); const response = await request(app.getHttpServer()) - .post('/auth/link-wallet') - .set('Authorization', `Bearer ${userToken}`) + .post("/auth/link-wallet") + .set("Authorization", `Bearer ${userToken}`) .send({ walletAddress: secondaryWallet.address, message, signature, - walletName: 'Secondary Wallet', + walletName: "Secondary Wallet", }) .expect(201); @@ -110,9 +116,9 @@ describe('Advanced Wallet Authentication (e2e)', () => { expect(response.body.type).toBe(WalletType.SECONDARY); }); - it('should prevent linking duplicate wallet', async () => { + it("should prevent linking duplicate wallet", async () => { const challengeRes = await request(app.getHttpServer()) - .post('/auth/challenge') + .post("/auth/challenge") .send({ address: primaryWallet.address }) .expect(200); @@ -120,8 +126,8 @@ describe('Advanced Wallet Authentication (e2e)', () => { const signature = await primaryWallet.signMessage(message); await request(app.getHttpServer()) - .post('/auth/link-wallet') - .set('Authorization', `Bearer ${userToken}`) + .post("/auth/link-wallet") + .set("Authorization", `Bearer ${userToken}`) .send({ walletAddress: primaryWallet.address, message, @@ -130,21 +136,21 @@ describe('Advanced Wallet Authentication (e2e)', () => { .expect(409); }); - it('should get all user wallets', async () => { + it("should get all user wallets", async () => { const response = await request(app.getHttpServer()) - .get('/auth/wallets') - .set('Authorization', `Bearer ${userToken}`) + .get("/auth/wallets") + .set("Authorization", `Bearer ${userToken}`) .expect(200); expect(Array.isArray(response.body)).toBe(true); expect(response.body.length).toBeGreaterThanOrEqual(2); }); - it('should set a wallet as primary', async () => { + it("should set a wallet as primary", async () => { // First get wallets to find secondary wallet ID const walletsRes = await request(app.getHttpServer()) - .get('/auth/wallets') - .set('Authorization', `Bearer ${userToken}`) + .get("/auth/wallets") + .set("Authorization", `Bearer ${userToken}`) .expect(200); const secondaryWalletData = walletsRes.body.find( @@ -154,19 +160,19 @@ describe('Advanced Wallet Authentication (e2e)', () => { if (secondaryWalletData) { const response = await request(app.getHttpServer()) .post(`/auth/wallets/${secondaryWalletData.id}/set-primary`) - .set('Authorization', `Bearer ${userToken}`) + .set("Authorization", `Bearer ${userToken}`) .expect(201); - expect(response.body.message).toContain('Primary wallet updated'); + expect(response.body.message).toContain("Primary wallet updated"); } }); - it('should unlink a wallet', async () => { + it("should unlink a wallet", async () => { // Create a third wallet to unlink const thirdWallet = Wallet.createRandom(); const challengeRes = await request(app.getHttpServer()) - .post('/auth/challenge') + .post("/auth/challenge") .send({ address: thirdWallet.address }) .expect(200); @@ -174,13 +180,13 @@ describe('Advanced Wallet Authentication (e2e)', () => { const signature = await thirdWallet.signMessage(message); const linkRes = await request(app.getHttpServer()) - .post('/auth/link-wallet') - .set('Authorization', `Bearer ${userToken}`) + .post("/auth/link-wallet") + .set("Authorization", `Bearer ${userToken}`) .send({ walletAddress: thirdWallet.address, message, signature, - walletName: 'Wallet to Unlink', + walletName: "Wallet to Unlink", }) .expect(201); @@ -188,16 +194,16 @@ describe('Advanced Wallet Authentication (e2e)', () => { // Now unlink it const response = await request(app.getHttpServer()) - .post('/auth/unlink-wallet') - .set('Authorization', `Bearer ${userToken}`) + .post("/auth/unlink-wallet") + .set("Authorization", `Bearer ${userToken}`) .send({ walletId }) .expect(201); - expect(response.body.message).toContain('successfully unlinked'); + expect(response.body.message).toContain("successfully unlinked"); }); }); - describe('Session Recovery', () => { + describe("Session Recovery", () => { let recoveryWallet: Wallet; let recoveryWalletId: string; @@ -206,7 +212,7 @@ describe('Advanced Wallet Authentication (e2e)', () => { // Link recovery wallet const challengeRes = await request(app.getHttpServer()) - .post('/auth/challenge') + .post("/auth/challenge") .send({ address: recoveryWallet.address }) .expect(200); @@ -214,23 +220,23 @@ describe('Advanced Wallet Authentication (e2e)', () => { const signature = await recoveryWallet.signMessage(message); const linkRes = await request(app.getHttpServer()) - .post('/auth/link-wallet') - .set('Authorization', `Bearer ${userToken}`) + .post("/auth/link-wallet") + .set("Authorization", `Bearer ${userToken}`) .send({ walletAddress: recoveryWallet.address, message, signature, - walletName: 'Recovery Test Wallet', + walletName: "Recovery Test Wallet", }) .expect(201); recoveryWalletId = linkRes.body.walletId; }); - it('should generate backup codes', async () => { + it("should generate backup codes", async () => { const response = await request(app.getHttpServer()) - .post('/auth/recovery/backup-code/generate') - .set('Authorization', `Bearer ${userToken}`) + .post("/auth/recovery/backup-code/generate") + .set("Authorization", `Bearer ${userToken}`) .send({ walletId: recoveryWalletId }) .expect(201); @@ -238,28 +244,28 @@ describe('Advanced Wallet Authentication (e2e)', () => { expect(response.body.codes.length).toBe(10); }); - it('should get recovery status', async () => { + it("should get recovery status", async () => { const response = await request(app.getHttpServer()) .get(`/auth/recovery/status/${recoveryWalletId}`) - .set('Authorization', `Bearer ${userToken}`) + .set("Authorization", `Bearer ${userToken}`) .expect(200); expect(response.body.recoveryEnabled).toBeDefined(); expect(response.body.methods).toBeDefined(); }); - it('should initiate email recovery', async () => { + it("should initiate email recovery", async () => { const response = await request(app.getHttpServer()) - .post('/auth/recovery/email/initiate') - .send({ email: 'test@example.com' }) + .post("/auth/recovery/email/initiate") + .send({ email: "test@example.com" }) .expect(201); expect(response.body.sessionId).toBeDefined(); - expect(response.body.message).toContain('Recovery email sent'); + expect(response.body.message).toContain("Recovery email sent"); }); }); - describe('Delegated Signing', () => { + describe("Delegated Signing", () => { let delegatorWallet: Wallet; let delegateWallet: Wallet; let delegatorWalletId: string; @@ -270,7 +276,7 @@ describe('Advanced Wallet Authentication (e2e)', () => { // Link delegator wallet const challengeRes = await request(app.getHttpServer()) - .post('/auth/challenge') + .post("/auth/challenge") .send({ address: delegatorWallet.address }) .expect(200); @@ -278,28 +284,30 @@ describe('Advanced Wallet Authentication (e2e)', () => { const signature = await delegatorWallet.signMessage(message); const linkRes = await request(app.getHttpServer()) - .post('/auth/link-wallet') - .set('Authorization', `Bearer ${userToken}`) + .post("/auth/link-wallet") + .set("Authorization", `Bearer ${userToken}`) .send({ walletAddress: delegatorWallet.address, message, signature, - walletName: 'Delegator Wallet', + walletName: "Delegator Wallet", }) .expect(201); delegatorWalletId = linkRes.body.walletId; }); - it('should request delegation', async () => { + it("should request delegation", async () => { const response = await request(app.getHttpServer()) - .post('/auth/delegation/request') - .set('Authorization', `Bearer ${userToken}`) + .post("/auth/delegation/request") + .set("Authorization", `Bearer ${userToken}`) .send({ delegatorWalletId, delegateAddress: delegateWallet.address, - permissions: ['sign_messages', 'authenticate'], - expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), + permissions: ["sign_messages", "authenticate"], + expiresAt: new Date( + Date.now() + 7 * 24 * 60 * 60 * 1000, + ).toISOString(), }) .expect(201); @@ -307,37 +315,37 @@ describe('Advanced Wallet Authentication (e2e)', () => { expect(response.body.challenge).toBeDefined(); }); - it('should get user delegations', async () => { + it("should get user delegations", async () => { const response = await request(app.getHttpServer()) - .get('/auth/delegations') - .set('Authorization', `Bearer ${userToken}`) + .get("/auth/delegations") + .set("Authorization", `Bearer ${userToken}`) .expect(200); expect(response.body.granted).toBeDefined(); expect(response.body.received).toBeDefined(); }); - it('should get wallet delegations', async () => { + it("should get wallet delegations", async () => { const response = await request(app.getHttpServer()) .get(`/auth/delegations/wallet/${delegatorWalletId}`) - .set('Authorization', `Bearer ${userToken}`) + .set("Authorization", `Bearer ${userToken}`) .expect(200); expect(Array.isArray(response.body)).toBe(true); }); }); - describe('Security Tests', () => { - it('should reject unauthorized wallet access', async () => { + describe("Security Tests", () => { + it("should reject unauthorized wallet access", async () => { const otherUserToken = jwtService.sign({ - sub: '550e8400-e29b-41d4-a716-446655440999', - address: '0x9999999999999999999999999999999999999999', + sub: "550e8400-e29b-41d4-a716-446655440999", + address: "0x9999999999999999999999999999999999999999", role: UserRole.USER, }); await request(app.getHttpServer()) - .get('/auth/wallets') - .set('Authorization', `Bearer ${otherUserToken}`) + .get("/auth/wallets") + .set("Authorization", `Bearer ${otherUserToken}`) .expect(200) .expect((res) => { // Should return empty or different wallets @@ -345,11 +353,11 @@ describe('Advanced Wallet Authentication (e2e)', () => { }); }); - it('should prevent replay attacks with consumed challenges', async () => { + it("should prevent replay attacks with consumed challenges", async () => { const newWallet = Wallet.createRandom(); const challengeRes = await request(app.getHttpServer()) - .post('/auth/challenge') + .post("/auth/challenge") .send({ address: newWallet.address }) .expect(200); @@ -358,8 +366,8 @@ describe('Advanced Wallet Authentication (e2e)', () => { // First attempt should succeed await request(app.getHttpServer()) - .post('/auth/link-wallet') - .set('Authorization', `Bearer ${userToken}`) + .post("/auth/link-wallet") + .set("Authorization", `Bearer ${userToken}`) .send({ walletAddress: newWallet.address, message, @@ -369,8 +377,8 @@ describe('Advanced Wallet Authentication (e2e)', () => { // Second attempt with same challenge should fail await request(app.getHttpServer()) - .post('/auth/link-wallet') - .set('Authorization', `Bearer ${userToken}`) + .post("/auth/link-wallet") + .set("Authorization", `Bearer ${userToken}`) .send({ walletAddress: newWallet.address, message, @@ -379,15 +387,17 @@ describe('Advanced Wallet Authentication (e2e)', () => { .expect(401); }); - it('should enforce rate limiting on sensitive endpoints', async () => { + it("should enforce rate limiting on sensitive endpoints", async () => { const newWallet = Wallet.createRandom(); // Make multiple rapid requests - const requests = Array(15).fill(null).map(() => - request(app.getHttpServer()) - .post('/auth/challenge') - .send({ address: newWallet.address }), - ); + const requests = Array(15) + .fill(null) + .map(() => + request(app.getHttpServer()) + .post("/auth/challenge") + .send({ address: newWallet.address }), + ); const responses = await Promise.all(requests); diff --git a/test/email-linking.spec.ts b/test/email-linking.spec.ts index 62b4964..25d0981 100644 --- a/test/email-linking.spec.ts +++ b/test/email-linking.spec.ts @@ -1,21 +1,26 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { getRepositoryToken } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { BadRequestException, ConflictException, NotFoundException, UnauthorizedException } from '@nestjs/common'; -import { EmailLinkingService } from '../src/core/auth/email-linking.service'; -import { EmailService } from '../src/core/auth/email.service'; -import { User } from '../src/core/user/entities/user.entity'; -import { EmailVerification } from '../src/core/auth/entities/email-verification.entity'; - -describe('EmailLinkingService', () => { +import { Test, TestingModule } from "@nestjs/testing"; +import { getRepositoryToken } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; +import { + BadRequestException, + ConflictException, + NotFoundException, + UnauthorizedException, +} from "@nestjs/common"; +import { EmailLinkingService } from "../src/core/auth/email-linking.service"; +import { EmailService } from "../src/core/auth/email.service"; +import { User } from "../src/core/user/entities/user.entity"; +import { EmailVerification } from "../src/core/auth/entities/email-verification.entity"; + +describe("EmailLinkingService", () => { let service: EmailLinkingService; let userRepository: Repository; let emailVerificationRepository: Repository; let emailService: EmailService; const mockUser: User = { - id: '123', - walletAddress: '0x1234567890123456789012345678901234567890', + id: "123", + walletAddress: "0x1234567890123456789012345678901234567890", email: null, emailVerified: false, createdAt: new Date(), @@ -24,8 +29,8 @@ describe('EmailLinkingService', () => { const mockEmailService = { sendVerificationEmail: jest.fn().mockResolvedValue({ - messageId: 'test-message-id', - previewUrl: 'https://ethereal.email/message/test', + messageId: "test-message-id", + previewUrl: "https://ethereal.email/message/test", }), }; @@ -66,163 +71,188 @@ describe('EmailLinkingService', () => { emailService = module.get(EmailService); }); - describe('initiateEmailLinking', () => { - it('should initiate email linking for new user', async () => { - jest.spyOn(userRepository, 'findOne').mockResolvedValue(null); - jest.spyOn(userRepository, 'create').mockReturnValue(mockUser); - jest.spyOn(userRepository, 'save').mockResolvedValue(mockUser); - jest.spyOn(emailVerificationRepository, 'delete').mockResolvedValue(undefined); - jest.spyOn(emailVerificationRepository, 'create').mockReturnValue({} as EmailVerification); - jest.spyOn(emailVerificationRepository, 'save').mockResolvedValue({} as EmailVerification); + describe("initiateEmailLinking", () => { + it("should initiate email linking for new user", async () => { + jest.spyOn(userRepository, "findOne").mockResolvedValue(null); + jest.spyOn(userRepository, "create").mockReturnValue(mockUser); + jest.spyOn(userRepository, "save").mockResolvedValue(mockUser); + jest + .spyOn(emailVerificationRepository, "delete") + .mockResolvedValue(undefined); + jest + .spyOn(emailVerificationRepository, "create") + .mockReturnValue({} as EmailVerification); + jest + .spyOn(emailVerificationRepository, "save") + .mockResolvedValue({} as EmailVerification); const result = await service.initiateEmailLinking( - '0x1234567890123456789012345678901234567890', - 'test@example.com', + "0x1234567890123456789012345678901234567890", + "test@example.com", ); - expect(result.message).toContain('Verification email sent'); + expect(result.message).toContain("Verification email sent"); expect(emailService.sendVerificationEmail).toHaveBeenCalled(); }); - it('should reject invalid email format', async () => { + it("should reject invalid email format", async () => { await expect( service.initiateEmailLinking( - '0x1234567890123456789012345678901234567890', - 'invalid-email', + "0x1234567890123456789012345678901234567890", + "invalid-email", ), ).rejects.toThrow(BadRequestException); }); - it('should reject email already linked to another wallet', async () => { - const existingUser = { ...mockUser, walletAddress: '0xdifferent' }; - jest.spyOn(userRepository, 'findOne').mockResolvedValue(existingUser); + it("should reject email already linked to another wallet", async () => { + const existingUser = { ...mockUser, walletAddress: "0xdifferent" }; + jest.spyOn(userRepository, "findOne").mockResolvedValue(existingUser); await expect( service.initiateEmailLinking( - '0x1234567890123456789012345678901234567890', - 'test@example.com', + "0x1234567890123456789012345678901234567890", + "test@example.com", ), ).rejects.toThrow(ConflictException); }); - it('should reject if email already verified for this wallet', async () => { + it("should reject if email already verified for this wallet", async () => { const verifiedUser = { ...mockUser, - email: 'test@example.com', + email: "test@example.com", emailVerified: true, }; - jest.spyOn(userRepository, 'findOne') + jest + .spyOn(userRepository, "findOne") .mockResolvedValueOnce(null) // First call for email check .mockResolvedValueOnce(verifiedUser); // Second call for wallet check await expect( service.initiateEmailLinking( - '0x1234567890123456789012345678901234567890', - 'test@example.com', + "0x1234567890123456789012345678901234567890", + "test@example.com", ), ).rejects.toThrow(ConflictException); }); }); - describe('verifyEmailAndLink', () => { - it('should verify email and link to wallet', async () => { + describe("verifyEmailAndLink", () => { + it("should verify email and link to wallet", async () => { const verification: EmailVerification = { - id: '1', - email: 'test@example.com', - token: 'a'.repeat(64), - walletAddress: '0x1234567890123456789012345678901234567890', + id: "1", + email: "test@example.com", + token: "a".repeat(64), + walletAddress: "0x1234567890123456789012345678901234567890", expiresAt: new Date(Date.now() + 10 * 60 * 1000), createdAt: new Date(), }; - jest.spyOn(emailVerificationRepository, 'findOne').mockResolvedValue(verification); - jest.spyOn(userRepository, 'findOne').mockResolvedValue(mockUser); - jest.spyOn(userRepository, 'save').mockResolvedValue({ + jest + .spyOn(emailVerificationRepository, "findOne") + .mockResolvedValue(verification); + jest.spyOn(userRepository, "findOne").mockResolvedValue(mockUser); + jest.spyOn(userRepository, "save").mockResolvedValue({ ...mockUser, - email: 'test@example.com', + email: "test@example.com", emailVerified: true, }); - jest.spyOn(emailVerificationRepository, 'delete').mockResolvedValue(undefined); + jest + .spyOn(emailVerificationRepository, "delete") + .mockResolvedValue(undefined); - const result = await service.verifyEmailAndLink('a'.repeat(64)); + const result = await service.verifyEmailAndLink("a".repeat(64)); - expect(result.message).toContain('successfully verified'); - expect(result.email).toBe('test@example.com'); + expect(result.message).toContain("successfully verified"); + expect(result.email).toBe("test@example.com"); }); - it('should reject invalid token', async () => { - jest.spyOn(emailVerificationRepository, 'findOne').mockResolvedValue(null); + it("should reject invalid token", async () => { + jest + .spyOn(emailVerificationRepository, "findOne") + .mockResolvedValue(null); - await expect(service.verifyEmailAndLink('invalid-token')).rejects.toThrow( + await expect(service.verifyEmailAndLink("invalid-token")).rejects.toThrow( NotFoundException, ); }); - it('should reject expired token', async () => { + it("should reject expired token", async () => { const expiredVerification: EmailVerification = { - id: '1', - email: 'test@example.com', - token: 'a'.repeat(64), - walletAddress: '0x1234567890123456789012345678901234567890', + id: "1", + email: "test@example.com", + token: "a".repeat(64), + walletAddress: "0x1234567890123456789012345678901234567890", expiresAt: new Date(Date.now() - 1000), // Expired createdAt: new Date(), }; - jest.spyOn(emailVerificationRepository, 'findOne').mockResolvedValue(expiredVerification); - jest.spyOn(emailVerificationRepository, 'delete').mockResolvedValue(undefined); + jest + .spyOn(emailVerificationRepository, "findOne") + .mockResolvedValue(expiredVerification); + jest + .spyOn(emailVerificationRepository, "delete") + .mockResolvedValue(undefined); - await expect(service.verifyEmailAndLink('a'.repeat(64))).rejects.toThrow( + await expect(service.verifyEmailAndLink("a".repeat(64))).rejects.toThrow( UnauthorizedException, ); }); }); - describe('getAccountInfo', () => { - it('should return account info for wallet with linked email', async () => { + describe("getAccountInfo", () => { + it("should return account info for wallet with linked email", async () => { const userWithEmail = { ...mockUser, - email: 'test@example.com', + email: "test@example.com", emailVerified: true, }; - jest.spyOn(userRepository, 'findOne').mockResolvedValue(userWithEmail); + jest.spyOn(userRepository, "findOne").mockResolvedValue(userWithEmail); - const result = await service.getAccountInfo('0x1234567890123456789012345678901234567890'); + const result = await service.getAccountInfo( + "0x1234567890123456789012345678901234567890", + ); - expect(result.email).toBe('test@example.com'); + expect(result.email).toBe("test@example.com"); expect(result.emailVerified).toBe(true); }); - it('should return null email for wallet without linked email', async () => { - jest.spyOn(userRepository, 'findOne').mockResolvedValue(null); + it("should return null email for wallet without linked email", async () => { + jest.spyOn(userRepository, "findOne").mockResolvedValue(null); - const result = await service.getAccountInfo('0x1234567890123456789012345678901234567890'); + const result = await service.getAccountInfo( + "0x1234567890123456789012345678901234567890", + ); expect(result.email).toBeNull(); expect(result.emailVerified).toBe(false); }); }); - describe('unlinkEmail', () => { - it('should unlink email from wallet', async () => { + describe("unlinkEmail", () => { + it("should unlink email from wallet", async () => { const userWithEmail = { ...mockUser, - email: 'test@example.com', + email: "test@example.com", emailVerified: true, }; - jest.spyOn(userRepository, 'findOne').mockResolvedValue(userWithEmail); - jest.spyOn(userRepository, 'save').mockResolvedValue(mockUser); - jest.spyOn(emailVerificationRepository, 'delete').mockResolvedValue(undefined); - - const result = await service.unlinkEmail('0x1234567890123456789012345678901234567890'); + jest.spyOn(userRepository, "findOne").mockResolvedValue(userWithEmail); + jest.spyOn(userRepository, "save").mockResolvedValue(mockUser); + jest + .spyOn(emailVerificationRepository, "delete") + .mockResolvedValue(undefined); + + const result = await service.unlinkEmail( + "0x1234567890123456789012345678901234567890", + ); - expect(result.message).toContain('successfully unlinked'); + expect(result.message).toContain("successfully unlinked"); }); - it('should throw error if no email linked', async () => { - jest.spyOn(userRepository, 'findOne').mockResolvedValue(mockUser); + it("should throw error if no email linked", async () => { + jest.spyOn(userRepository, "findOne").mockResolvedValue(mockUser); await expect( - service.unlinkEmail('0x1234567890123456789012345678901234567890'), + service.unlinkEmail("0x1234567890123456789012345678901234567890"), ).rejects.toThrow(NotFoundException); }); }); diff --git a/test/oracle-e2e.spec.ts b/test/oracle-e2e.spec.ts index dab93be..60e334c 100644 --- a/test/oracle-e2e.spec.ts +++ b/test/oracle-e2e.spec.ts @@ -1,20 +1,20 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication, ValidationPipe } from '@nestjs/common'; -import * as request from 'supertest'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { ConfigModule } from '@nestjs/config'; -import { JwtModule } from '@nestjs/jwt'; -import { Wallet } from 'ethers'; -import { OracleModule } from '../src/blockchain/oracle/oracle.module'; -import { AuthModule } from '../src/core/auth/auth.module'; -import { UserModule } from '../src/core/user/user.module'; -import { SignedPayload } from '../src/blockchain/oracle/entities/signed-payload.entity'; -import { SubmissionNonce } from '../src/blockchain/oracle/entities/submission-nonce.entity'; -import { User } from '../src/core/user/entities/user.entity'; -import { EmailVerification } from '../src/core/auth/entities/email-verification.entity'; -import { PayloadType } from '../src/blockchain/oracle/entities/signed-payload.entity'; - -describe('Oracle E2E Tests', () => { +import { Test, TestingModule } from "@nestjs/testing"; +import { INestApplication, ValidationPipe } from "@nestjs/common"; +import * as request from "supertest"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { ConfigModule } from "@nestjs/config"; +import { JwtModule } from "@nestjs/jwt"; +import { Wallet } from "ethers"; +import { OracleModule } from "../src/blockchain/oracle/oracle.module"; +import { AuthModule } from "../src/core/auth/auth.module"; +import { UserModule } from "../src/core/user/user.module"; +import { SignedPayload } from "../src/blockchain/oracle/entities/signed-payload.entity"; +import { SubmissionNonce } from "../src/blockchain/oracle/entities/submission-nonce.entity"; +import { User } from "../src/core/user/entities/user.entity"; +import { EmailVerification } from "../src/core/auth/entities/email-verification.entity"; +import { PayloadType } from "../src/blockchain/oracle/entities/signed-payload.entity"; + +describe("Oracle E2E Tests", () => { let app: INestApplication; let jwtToken: string; let testWallet: Wallet; @@ -29,22 +29,22 @@ describe('Oracle E2E Tests', () => { imports: [ ConfigModule.forRoot({ isGlobal: true, - envFilePath: '.env.test', + envFilePath: ".env.test", }), TypeOrmModule.forRoot({ - type: 'postgres', - host: process.env.DB_HOST || 'localhost', - port: parseInt(process.env.DB_PORT || '5432'), - username: process.env.DB_USERNAME || 'alian-structure', - password: process.env.DB_PASSWORD || 'password', - database: process.env.DB_NAME || 'alian-structure_test', + type: "postgres", + host: process.env.DB_HOST || "localhost", + port: parseInt(process.env.DB_PORT || "5432"), + username: process.env.DB_USERNAME || "alian-structure", + password: process.env.DB_PASSWORD || "password", + database: process.env.DB_NAME || "alian-structure_test", entities: [SignedPayload, SubmissionNonce, User, EmailVerification], synchronize: true, dropSchema: true, }), JwtModule.register({ - secret: 'test-secret', - signOptions: { expiresIn: '24h' }, + secret: "test-secret", + signOptions: { expiresIn: "24h" }, }), OracleModule, AuthModule, @@ -57,7 +57,7 @@ describe('Oracle E2E Tests', () => { await app.init(); // Create JWT token for testing authenticated endpoints - const jwtService = app.get('JwtService'); + const jwtService = app.get("JwtService"); jwtToken = jwtService.sign({ address: userAddress.toLowerCase() }); }); @@ -65,46 +65,46 @@ describe('Oracle E2E Tests', () => { await app.close(); }); - describe('/oracle/health (GET)', () => { - it('should return health status', () => { + describe("/oracle/health (GET)", () => { + it("should return health status", () => { return request(app.getHttpServer()) - .get('/oracle/health') + .get("/oracle/health") .expect(200) .expect((res) => { - expect(res.body.status).toBe('healthy'); - expect(res.body.service).toBe('oracle'); + expect(res.body.status).toBe("healthy"); + expect(res.body.service).toBe("oracle"); }); }); }); - describe('/oracle/nonce/:address (GET)', () => { - it('should return nonce for an address', () => { + describe("/oracle/nonce/:address (GET)", () => { + it("should return nonce for an address", () => { return request(app.getHttpServer()) .get(`/oracle/nonce/${userAddress}`) .expect(200) .expect((res) => { expect(res.body.address).toBe(userAddress); expect(res.body.nonce).toBeDefined(); - expect(typeof res.body.nonce).toBe('string'); + expect(typeof res.body.nonce).toBe("string"); }); }); - it('should return 0 for new address', () => { + it("should return 0 for new address", () => { const newAddress = Wallet.createRandom().address; return request(app.getHttpServer()) .get(`/oracle/nonce/${newAddress}`) .expect(200) .expect((res) => { - expect(res.body.nonce).toBe('0'); + expect(res.body.nonce).toBe("0"); }); }); }); - describe('/oracle/my-nonce (GET)', () => { - it('should return nonce for authenticated user', () => { + describe("/oracle/my-nonce (GET)", () => { + it("should return nonce for authenticated user", () => { return request(app.getHttpServer()) - .get('/oracle/my-nonce') - .set('Authorization', `Bearer ${jwtToken}`) + .get("/oracle/my-nonce") + .set("Authorization", `Bearer ${jwtToken}`) .expect(200) .expect((res) => { expect(res.body.address).toBe(userAddress.toLowerCase()); @@ -112,22 +112,22 @@ describe('Oracle E2E Tests', () => { }); }); - it('should reject without authentication', () => { - return request(app.getHttpServer()).get('/oracle/my-nonce').expect(401); + it("should reject without authentication", () => { + return request(app.getHttpServer()).get("/oracle/my-nonce").expect(401); }); }); - describe('/oracle/payloads (POST)', () => { - it('should create a new payload', () => { + describe("/oracle/payloads (POST)", () => { + it("should create a new payload", () => { const createPayloadDto = { payloadType: PayloadType.ORACLE_UPDATE, payload: { value: 100, timestamp: Date.now() }, - metadata: { source: 'test' }, + metadata: { source: "test" }, }; return request(app.getHttpServer()) - .post('/oracle/payloads') - .set('Authorization', `Bearer ${jwtToken}`) + .post("/oracle/payloads") + .set("Authorization", `Bearer ${jwtToken}`) .send(createPayloadDto) .expect(201) .expect((res) => { @@ -136,13 +136,13 @@ describe('Oracle E2E Tests', () => { expect(res.body.signerAddress).toBe(userAddress.toLowerCase()); expect(res.body.nonce).toBeDefined(); expect(res.body.payloadHash).toMatch(/^0x[a-fA-F0-9]{64}$/); - expect(res.body.status).toBe('pending'); + expect(res.body.status).toBe("pending"); }); }); - it('should reject without authentication', () => { + it("should reject without authentication", () => { return request(app.getHttpServer()) - .post('/oracle/payloads') + .post("/oracle/payloads") .send({ payloadType: PayloadType.ORACLE_UPDATE, payload: { value: 100 }, @@ -150,21 +150,21 @@ describe('Oracle E2E Tests', () => { .expect(401); }); - it('should reject invalid payload type', () => { + it("should reject invalid payload type", () => { return request(app.getHttpServer()) - .post('/oracle/payloads') - .set('Authorization', `Bearer ${jwtToken}`) + .post("/oracle/payloads") + .set("Authorization", `Bearer ${jwtToken}`) .send({ - payloadType: 'invalid_type', + payloadType: "invalid_type", payload: { value: 100 }, }) .expect(400); }); - it('should reject missing payload data', () => { + it("should reject missing payload data", () => { return request(app.getHttpServer()) - .post('/oracle/payloads') - .set('Authorization', `Bearer ${jwtToken}`) + .post("/oracle/payloads") + .set("Authorization", `Bearer ${jwtToken}`) .send({ payloadType: PayloadType.ORACLE_UPDATE, }) @@ -172,14 +172,14 @@ describe('Oracle E2E Tests', () => { }); }); - describe('/oracle/payloads/:id (GET)', () => { + describe("/oracle/payloads/:id (GET)", () => { let payloadId: string; beforeAll(async () => { // Create a test payload const res = await request(app.getHttpServer()) - .post('/oracle/payloads') - .set('Authorization', `Bearer ${jwtToken}`) + .post("/oracle/payloads") + .set("Authorization", `Bearer ${jwtToken}`) .send({ payloadType: PayloadType.ORACLE_UPDATE, payload: { value: 200 }, @@ -187,10 +187,10 @@ describe('Oracle E2E Tests', () => { payloadId = res.body.id; }); - it('should get a payload by ID', () => { + it("should get a payload by ID", () => { return request(app.getHttpServer()) .get(`/oracle/payloads/${payloadId}`) - .set('Authorization', `Bearer ${jwtToken}`) + .set("Authorization", `Bearer ${jwtToken}`) .expect(200) .expect((res) => { expect(res.body.id).toBe(payloadId); @@ -198,21 +198,21 @@ describe('Oracle E2E Tests', () => { }); }); - it('should return 404 for non-existent payload', () => { + it("should return 404 for non-existent payload", () => { return request(app.getHttpServer()) - .get('/oracle/payloads/00000000-0000-0000-0000-000000000000') - .set('Authorization', `Bearer ${jwtToken}`) + .get("/oracle/payloads/00000000-0000-0000-0000-000000000000") + .set("Authorization", `Bearer ${jwtToken}`) .expect(404); }); }); - describe('/oracle/my-payloads (GET)', () => { + describe("/oracle/my-payloads (GET)", () => { beforeAll(async () => { // Create multiple payloads for (let i = 0; i < 3; i++) { await request(app.getHttpServer()) - .post('/oracle/payloads') - .set('Authorization', `Bearer ${jwtToken}`) + .post("/oracle/payloads") + .set("Authorization", `Bearer ${jwtToken}`) .send({ payloadType: PayloadType.AGENT_RESULT, payload: { resultId: i }, @@ -220,10 +220,10 @@ describe('Oracle E2E Tests', () => { } }); - it('should get all payloads for authenticated user', () => { + it("should get all payloads for authenticated user", () => { return request(app.getHttpServer()) - .get('/oracle/my-payloads') - .set('Authorization', `Bearer ${jwtToken}`) + .get("/oracle/my-payloads") + .set("Authorization", `Bearer ${jwtToken}`) .expect(200) .expect((res) => { expect(Array.isArray(res.body)).toBe(true); @@ -234,23 +234,23 @@ describe('Oracle E2E Tests', () => { }); }); - it('should filter by status', () => { + it("should filter by status", () => { return request(app.getHttpServer()) - .get('/oracle/my-payloads?status=pending') - .set('Authorization', `Bearer ${jwtToken}`) + .get("/oracle/my-payloads?status=pending") + .set("Authorization", `Bearer ${jwtToken}`) .expect(200) .expect((res) => { expect(Array.isArray(res.body)).toBe(true); res.body.forEach((payload: any) => { - expect(payload.status).toBe('pending'); + expect(payload.status).toBe("pending"); }); }); }); - it('should respect limit parameter', () => { + it("should respect limit parameter", () => { return request(app.getHttpServer()) - .get('/oracle/my-payloads?limit=2') - .set('Authorization', `Bearer ${jwtToken}`) + .get("/oracle/my-payloads?limit=2") + .set("Authorization", `Bearer ${jwtToken}`) .expect(200) .expect((res) => { expect(res.body.length).toBeLessThanOrEqual(2); @@ -258,29 +258,29 @@ describe('Oracle E2E Tests', () => { }); }); - describe('/oracle/stats (GET)', () => { - it('should return oracle statistics', () => { + describe("/oracle/stats (GET)", () => { + it("should return oracle statistics", () => { return request(app.getHttpServer()) - .get('/oracle/stats') + .get("/oracle/stats") .expect(200) .expect((res) => { expect(res.body.payloads).toBeDefined(); expect(res.body.nonces).toBeDefined(); expect(res.body.submissions).toBeDefined(); - expect(typeof res.body.payloads.pending).toBe('number'); - expect(typeof res.body.nonces.totalAddresses).toBe('number'); + expect(typeof res.body.payloads.pending).toBe("number"); + expect(typeof res.body.nonces.totalAddresses).toBe("number"); }); }); }); - describe('/oracle/payloads/:id/sign (POST)', () => { + describe("/oracle/payloads/:id/sign (POST)", () => { let payloadId: string; beforeEach(async () => { // Create a new payload for signing const res = await request(app.getHttpServer()) - .post('/oracle/payloads') - .set('Authorization', `Bearer ${jwtToken}`) + .post("/oracle/payloads") + .set("Authorization", `Bearer ${jwtToken}`) .send({ payloadType: PayloadType.PRICE_FEED, payload: { price: 1000 }, @@ -288,10 +288,10 @@ describe('Oracle E2E Tests', () => { payloadId = res.body.id; }); - it('should sign a payload', () => { + it("should sign a payload", () => { return request(app.getHttpServer()) .post(`/oracle/payloads/${payloadId}/sign`) - .set('Authorization', `Bearer ${jwtToken}`) + .set("Authorization", `Bearer ${jwtToken}`) .send({ payloadId, privateKey: testWallet.privateKey, @@ -300,15 +300,15 @@ describe('Oracle E2E Tests', () => { .expect((res) => { expect(res.body.signature).toBeDefined(); expect(res.body.signature).toMatch(/^0x[a-fA-F0-9]{130}$/); - expect(res.body.status).toBe('pending'); + expect(res.body.status).toBe("pending"); }); }); - it('should reject signing with wrong private key', () => { + it("should reject signing with wrong private key", () => { const wrongWallet = Wallet.createRandom(); return request(app.getHttpServer()) .post(`/oracle/payloads/${payloadId}/sign`) - .set('Authorization', `Bearer ${jwtToken}`) + .set("Authorization", `Bearer ${jwtToken}`) .send({ payloadId, privateKey: wrongWallet.privateKey, @@ -316,20 +316,20 @@ describe('Oracle E2E Tests', () => { .expect(400); }); - it('should reject invalid private key format', () => { + it("should reject invalid private key format", () => { return request(app.getHttpServer()) .post(`/oracle/payloads/${payloadId}/sign`) - .set('Authorization', `Bearer ${jwtToken}`) + .set("Authorization", `Bearer ${jwtToken}`) .send({ payloadId, - privateKey: 'invalid-key', + privateKey: "invalid-key", }) .expect(400); }); }); - describe('Nonce increment on payload creation', () => { - it('should increment nonce with each payload creation', async () => { + describe("Nonce increment on payload creation", () => { + it("should increment nonce with each payload creation", async () => { const initialNonceRes = await request(app.getHttpServer()) .get(`/oracle/nonce/${userAddress}`) .expect(200); @@ -338,11 +338,11 @@ describe('Oracle E2E Tests', () => { // Create a payload await request(app.getHttpServer()) - .post('/oracle/payloads') - .set('Authorization', `Bearer ${jwtToken}`) + .post("/oracle/payloads") + .set("Authorization", `Bearer ${jwtToken}`) .send({ payloadType: PayloadType.COMPUTE_PROOF, - payload: { computeId: 'test-123' }, + payload: { computeId: "test-123" }, }) .expect(201); diff --git a/test/oracle/payload-signing.service.spec.ts b/test/oracle/payload-signing.service.spec.ts index e6bd94c..eeec7e2 100644 --- a/test/oracle/payload-signing.service.spec.ts +++ b/test/oracle/payload-signing.service.spec.ts @@ -1,9 +1,9 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { ConfigService } from '@nestjs/config'; -import { Wallet } from 'ethers'; -import { PayloadSigningService } from '../../src/blockchain/oracle/services/payload-signing.service'; +import { Test, TestingModule } from "@nestjs/testing"; +import { ConfigService } from "@nestjs/config"; +import { Wallet } from "ethers"; +import { PayloadSigningService } from "../../src/blockchain/oracle/services/payload-signing.service"; -describe('PayloadSigningService', () => { +describe("PayloadSigningService", () => { let service: PayloadSigningService; let testWallet: Wallet; let configService: ConfigService; @@ -16,8 +16,8 @@ describe('PayloadSigningService', () => { const mockConfigService = { get: jest.fn((key: string, defaultValue?: any) => { const config: Record = { - CHAIN_ID: '1', - ORACLE_CONTRACT_ADDRESS: '0x1234567890123456789012345678901234567890', + CHAIN_ID: "1", + ORACLE_CONTRACT_ADDRESS: "0x1234567890123456789012345678901234567890", }; return config[key] || defaultValue; }), @@ -37,13 +37,13 @@ describe('PayloadSigningService', () => { configService = module.get(ConfigService); }); - it('should be defined', () => { + it("should be defined", () => { expect(service).toBeDefined(); }); - describe('hashPayload', () => { - it('should hash a payload consistently', () => { - const payload = { foo: 'bar', baz: 123 }; + describe("hashPayload", () => { + it("should hash a payload consistently", () => { + const payload = { foo: "bar", baz: 123 }; const hash1 = service.hashPayload(payload); const hash2 = service.hashPayload(payload); @@ -51,9 +51,9 @@ describe('PayloadSigningService', () => { expect(hash1).toMatch(/^0x[a-fA-F0-9]{64}$/); }); - it('should produce different hashes for different payloads', () => { - const payload1 = { foo: 'bar' }; - const payload2 = { foo: 'baz' }; + it("should produce different hashes for different payloads", () => { + const payload1 = { foo: "bar" }; + const payload2 = { foo: "baz" }; const hash1 = service.hashPayload(payload1); const hash2 = service.hashPayload(payload2); @@ -62,11 +62,11 @@ describe('PayloadSigningService', () => { }); }); - describe('createStructuredData', () => { - it('should create EIP-712 structured data', () => { - const payloadType = 'oracle_update'; - const payloadHash = '0x' + '1'.repeat(64); - const nonce = '0'; + describe("createStructuredData", () => { + it("should create EIP-712 structured data", () => { + const payloadType = "oracle_update"; + const payloadHash = "0x" + "1".repeat(64); + const nonce = "0"; const expiresAt = Math.floor(Date.now() / 1000) + 3600; const data = { value: 100 }; @@ -79,8 +79,8 @@ describe('PayloadSigningService', () => { ); expect(structuredData.domain).toBeDefined(); - expect(structuredData.domain.name).toBe('alian-structure Oracle'); - expect(structuredData.domain.version).toBe('1'); + expect(structuredData.domain.name).toBe("alian-structure Oracle"); + expect(structuredData.domain.version).toBe("1"); expect(structuredData.domain.chainId).toBe(1); expect(structuredData.types).toBeDefined(); expect(structuredData.value).toBeDefined(); @@ -89,12 +89,12 @@ describe('PayloadSigningService', () => { }); }); - describe('signPayload', () => { - it('should sign a payload and return signature', async () => { - const payloadType = 'oracle_update'; + describe("signPayload", () => { + it("should sign a payload and return signature", async () => { + const payloadType = "oracle_update"; const payload = { value: 100 }; const payloadHash = service.hashPayload(payload); - const nonce = '0'; + const nonce = "0"; const expiresAt = Math.floor(Date.now() / 1000) + 3600; const result = await service.signPayload( @@ -111,11 +111,11 @@ describe('PayloadSigningService', () => { expect(result.signerAddress).toBe(testWallet.address); }); - it('should produce different signatures for different payloads', async () => { - const payloadType = 'oracle_update'; + it("should produce different signatures for different payloads", async () => { + const payloadType = "oracle_update"; const payload1 = { value: 100 }; const payload2 = { value: 200 }; - const nonce = '0'; + const nonce = "0"; const expiresAt = Math.floor(Date.now() / 1000) + 3600; const result1 = await service.signPayload( @@ -140,12 +140,12 @@ describe('PayloadSigningService', () => { }); }); - describe('verifySignature', () => { - it('should verify a valid signature', async () => { - const payloadType = 'oracle_update'; + describe("verifySignature", () => { + it("should verify a valid signature", async () => { + const payloadType = "oracle_update"; const payload = { value: 100 }; const payloadHash = service.hashPayload(payload); - const nonce = '0'; + const nonce = "0"; const expiresAt = Math.floor(Date.now() / 1000) + 3600; const { signature, signerAddress } = await service.signPayload( @@ -170,13 +170,13 @@ describe('PayloadSigningService', () => { expect(isValid).toBe(true); }); - it('should reject an invalid signature', () => { - const payloadType = 'oracle_update'; + it("should reject an invalid signature", () => { + const payloadType = "oracle_update"; const payload = { value: 100 }; const payloadHash = service.hashPayload(payload); - const nonce = '0'; + const nonce = "0"; const expiresAt = Math.floor(Date.now() / 1000) + 3600; - const fakeSignature = '0x' + '1'.repeat(130); + const fakeSignature = "0x" + "1".repeat(130); const isValid = service.verifySignature( fakeSignature, @@ -191,11 +191,11 @@ describe('PayloadSigningService', () => { expect(isValid).toBe(false); }); - it('should reject signature from different signer', async () => { - const payloadType = 'oracle_update'; + it("should reject signature from different signer", async () => { + const payloadType = "oracle_update"; const payload = { value: 100 }; const payloadHash = service.hashPayload(payload); - const nonce = '0'; + const nonce = "0"; const expiresAt = Math.floor(Date.now() / 1000) + 3600; const { signature } = await service.signPayload( @@ -222,11 +222,11 @@ describe('PayloadSigningService', () => { expect(isValid).toBe(false); }); - it('should reject signature for modified payload', async () => { - const payloadType = 'oracle_update'; + it("should reject signature for modified payload", async () => { + const payloadType = "oracle_update"; const payload = { value: 100 }; const payloadHash = service.hashPayload(payload); - const nonce = '0'; + const nonce = "0"; const expiresAt = Math.floor(Date.now() / 1000) + 3600; const { signature, signerAddress } = await service.signPayload( @@ -256,30 +256,30 @@ describe('PayloadSigningService', () => { }); }); - describe('getDomain', () => { - it('should return the EIP-712 domain', () => { + describe("getDomain", () => { + it("should return the EIP-712 domain", () => { const domain = service.getDomain(); - expect(domain.name).toBe('alian-structure Oracle'); - expect(domain.version).toBe('1'); + expect(domain.name).toBe("alian-structure Oracle"); + expect(domain.version).toBe("1"); expect(domain.chainId).toBe(1); expect(domain.verifyingContract).toBe( - '0x1234567890123456789012345678901234567890', + "0x1234567890123456789012345678901234567890", ); }); }); - describe('getTypes', () => { - it('should return the EIP-712 types', () => { + describe("getTypes", () => { + it("should return the EIP-712 types", () => { const types = service.getTypes(); expect(types.OraclePayload).toBeDefined(); expect(types.OraclePayload).toHaveLength(5); - expect(types.OraclePayload[0].name).toBe('payloadType'); - expect(types.OraclePayload[1].name).toBe('payloadHash'); - expect(types.OraclePayload[2].name).toBe('nonce'); - expect(types.OraclePayload[3].name).toBe('expiresAt'); - expect(types.OraclePayload[4].name).toBe('data'); + expect(types.OraclePayload[0].name).toBe("payloadType"); + expect(types.OraclePayload[1].name).toBe("payloadHash"); + expect(types.OraclePayload[2].name).toBe("nonce"); + expect(types.OraclePayload[3].name).toBe("expiresAt"); + expect(types.OraclePayload[4].name).toBe("data"); }); }); }); diff --git a/test/portfolio/mpt.spec.ts b/test/portfolio/mpt.spec.ts index f162b7c..6d19533 100644 --- a/test/portfolio/mpt.spec.ts +++ b/test/portfolio/mpt.spec.ts @@ -1,28 +1,26 @@ -import { ModernPortfolioTheory } from '../../src/investment/portfolio/algorithms/modern-portfolio-theory'; +import { ModernPortfolioTheory } from "../../src/investment/portfolio/algorithms/modern-portfolio-theory"; -describe('ModernPortfolioTheory', () => { - describe('calculatePortfolioMetrics', () => { - it('should calculate portfolio metrics correctly', () => { +describe("ModernPortfolioTheory", () => { + describe("calculatePortfolioMetrics", () => { + it("should calculate portfolio metrics correctly", () => { const weights = [0.6, 0.4]; const expectedReturns = [0.08, 0.12]; const correlationMatrix = [ [1, 0.5], [0.5, 1], ]; - const volatilities = [0.15, 0.20]; + const volatilities = [0.15, 0.2]; - const covarianceMatrix = - ModernPortfolioTheory.calculateCovarianceMatrix( - volatilities, - correlationMatrix, - ); + const covarianceMatrix = ModernPortfolioTheory.calculateCovarianceMatrix( + volatilities, + correlationMatrix, + ); - const metrics = - ModernPortfolioTheory.calculatePortfolioMetrics( - weights, - expectedReturns, - covarianceMatrix, - ); + const metrics = ModernPortfolioTheory.calculatePortfolioMetrics( + weights, + expectedReturns, + covarianceMatrix, + ); expect(metrics.weights).toEqual(weights); expect(metrics.expectedReturn).toBeCloseTo(0.096); @@ -31,19 +29,18 @@ describe('ModernPortfolioTheory', () => { }); }); - describe('calculateCovarianceMatrix', () => { - it('should calculate covariance matrix from correlation and volatilities', () => { - const volatilities = [0.15, 0.20]; + describe("calculateCovarianceMatrix", () => { + it("should calculate covariance matrix from correlation and volatilities", () => { + const volatilities = [0.15, 0.2]; const correlationMatrix = [ [1, 0.5], [0.5, 1], ]; - const covMatrix = - ModernPortfolioTheory.calculateCovarianceMatrix( - volatilities, - correlationMatrix, - ); + const covMatrix = ModernPortfolioTheory.calculateCovarianceMatrix( + volatilities, + correlationMatrix, + ); expect(covMatrix[0][0]).toBeCloseTo(0.0225); // 0.15^2 expect(covMatrix[1][1]).toBeCloseTo(0.04); // 0.20^2 @@ -51,49 +48,45 @@ describe('ModernPortfolioTheory', () => { }); }); - describe('pearsonCorrelation', () => { - it('should calculate Pearson correlation correctly', () => { + describe("pearsonCorrelation", () => { + it("should calculate Pearson correlation correctly", () => { const x = [1, 2, 3, 4, 5]; const y = [2, 4, 6, 8, 10]; - const correlation = - ModernPortfolioTheory.pearsonCorrelation(x, y); + const correlation = ModernPortfolioTheory.pearsonCorrelation(x, y); expect(correlation).toBeCloseTo(1, 5); // Perfect positive correlation }); - it('should handle negative correlation', () => { + it("should handle negative correlation", () => { const x = [1, 2, 3, 4, 5]; const y = [10, 8, 6, 4, 2]; - const correlation = - ModernPortfolioTheory.pearsonCorrelation(x, y); + const correlation = ModernPortfolioTheory.pearsonCorrelation(x, y); expect(correlation).toBeCloseTo(-1, 5); // Perfect negative correlation }); }); - describe('meanVarianceOptimization', () => { - it('should optimize portfolio using mean-variance', () => { - const expectedReturns = [0.08, 0.10, 0.12]; + describe("meanVarianceOptimization", () => { + it("should optimize portfolio using mean-variance", () => { + const expectedReturns = [0.08, 0.1, 0.12]; const correlationMatrix = [ [1, 0.5, 0.3], [0.5, 1, 0.4], [0.3, 0.4, 1], ]; - const volatilities = [0.15, 0.18, 0.20]; + const volatilities = [0.15, 0.18, 0.2]; - const covarianceMatrix = - ModernPortfolioTheory.calculateCovarianceMatrix( - volatilities, - correlationMatrix, - ); + const covarianceMatrix = ModernPortfolioTheory.calculateCovarianceMatrix( + volatilities, + correlationMatrix, + ); - const weights = - ModernPortfolioTheory.meanVarianceOptimization( - expectedReturns, - covarianceMatrix, - ); + const weights = ModernPortfolioTheory.meanVarianceOptimization( + expectedReturns, + covarianceMatrix, + ); const sum = weights.reduce((a, b) => a + b); expect(sum).toBeCloseTo(1, 5); @@ -101,24 +94,21 @@ describe('ModernPortfolioTheory', () => { }); }); - describe('minVarianceOptimization', () => { - it('should find minimum variance portfolio', () => { + describe("minVarianceOptimization", () => { + it("should find minimum variance portfolio", () => { const correlationMatrix = [ [1, 0.5], [0.5, 1], ]; - const volatilities = [0.15, 0.20]; + const volatilities = [0.15, 0.2]; - const covarianceMatrix = - ModernPortfolioTheory.calculateCovarianceMatrix( - volatilities, - correlationMatrix, - ); + const covarianceMatrix = ModernPortfolioTheory.calculateCovarianceMatrix( + volatilities, + correlationMatrix, + ); const weights = - ModernPortfolioTheory.minVarianceOptimization( - covarianceMatrix, - ); + ModernPortfolioTheory.minVarianceOptimization(covarianceMatrix); const sum = weights.reduce((a, b) => a + b); expect(sum).toBeCloseTo(1, 5); @@ -126,24 +116,21 @@ describe('ModernPortfolioTheory', () => { }); }); - describe('riskParityOptimization', () => { - it('should create risk parity portfolio', () => { + describe("riskParityOptimization", () => { + it("should create risk parity portfolio", () => { const correlationMatrix = [ [1, 0.3], [0.3, 1], ]; - const volatilities = [0.10, 0.20]; + const volatilities = [0.1, 0.2]; - const covarianceMatrix = - ModernPortfolioTheory.calculateCovarianceMatrix( - volatilities, - correlationMatrix, - ); + const covarianceMatrix = ModernPortfolioTheory.calculateCovarianceMatrix( + volatilities, + correlationMatrix, + ); const weights = - ModernPortfolioTheory.riskParityOptimization( - covarianceMatrix, - ); + ModernPortfolioTheory.riskParityOptimization(covarianceMatrix); const sum = weights.reduce((a, b) => a + b); expect(sum).toBeCloseTo(1, 5); @@ -153,43 +140,40 @@ describe('ModernPortfolioTheory', () => { }); }); - describe('calculateValueAtRisk', () => { - it('should calculate VaR correctly', () => { - const var95 = - ModernPortfolioTheory.calculateValueAtRisk( - 0.05, - 0.15, - 0.95, - 100000, - ); + describe("calculateValueAtRisk", () => { + it("should calculate VaR correctly", () => { + const var95 = ModernPortfolioTheory.calculateValueAtRisk( + 0.05, + 0.15, + 0.95, + 100000, + ); expect(var95).toBeGreaterThan(0); expect(var95).toBeLessThan(100000); }); }); - describe('efficientFrontier', () => { - it('should generate efficient frontier', () => { - const expectedReturns = [0.08, 0.10, 0.12]; + describe("efficientFrontier", () => { + it("should generate efficient frontier", () => { + const expectedReturns = [0.08, 0.1, 0.12]; const correlationMatrix = [ [1, 0.5, 0.3], [0.5, 1, 0.4], [0.3, 0.4, 1], ]; - const volatilities = [0.15, 0.18, 0.20]; + const volatilities = [0.15, 0.18, 0.2]; - const covarianceMatrix = - ModernPortfolioTheory.calculateCovarianceMatrix( - volatilities, - correlationMatrix, - ); + const covarianceMatrix = ModernPortfolioTheory.calculateCovarianceMatrix( + volatilities, + correlationMatrix, + ); - const frontier = - ModernPortfolioTheory.efficientFrontier( - expectedReturns, - covarianceMatrix, - 10, - ); + const frontier = ModernPortfolioTheory.efficientFrontier( + expectedReturns, + covarianceMatrix, + 10, + ); expect(frontier.length).toBe(10); expect(frontier[0].volatility).toBeLessThanOrEqual( @@ -198,19 +182,18 @@ describe('ModernPortfolioTheory', () => { }); }); - describe('applyConstraints', () => { - it('should apply weight constraints', () => { + describe("applyConstraints", () => { + it("should apply weight constraints", () => { const weights = [0.3, 0.5, 0.2]; const constraints = { minWeight: 0.1, maxWeight: 0.6, }; - const constrained = - ModernPortfolioTheory.applyConstraints( - weights, - constraints, - ); + const constrained = ModernPortfolioTheory.applyConstraints( + weights, + constraints, + ); for (const w of constrained) { expect(w).toBeGreaterThanOrEqual(0.1); diff --git a/test/portfolio/portfolio-management.e2e-spec.ts b/test/portfolio/portfolio-management.e2e-spec.ts index 37d6492..b93534b 100644 --- a/test/portfolio/portfolio-management.e2e-spec.ts +++ b/test/portfolio/portfolio-management.e2e-spec.ts @@ -116,4 +116,3 @@ describe("Portfolio Management REST API", () => { expect([400, 404, 401]).toContain(res.status); }, 15_000); }); - diff --git a/test/portfolio/portfolio.service.spec.ts b/test/portfolio/portfolio.service.spec.ts index b8df338..5842ec9 100644 --- a/test/portfolio/portfolio.service.spec.ts +++ b/test/portfolio/portfolio.service.spec.ts @@ -1,15 +1,22 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { getRepositoryToken } from '@nestjs/typeorm'; -import { PortfolioService } from '../../src/investment/portfolio/services/portfolio.service'; -import { Portfolio } from '../../src/investment/portfolio/entities/portfolio.entity'; -import { PortfolioAsset, Chain, AssetType } from '../../src/investment/portfolio/entities/portfolio-asset.entity'; -import { OptimizationHistory } from '../../src/investment/portfolio/entities/optimization-history.entity'; -import { RiskProfile } from '../../src/investment/portfolio/entities/risk-profile.entity'; -import { CreatePortfolioDto } from '../../src/investment/portfolio/dto/portfolio.dto'; -import { OptimizationMethod } from '../../src/investment/portfolio/entities/optimization-history.entity'; -import { AddHoldingDto, UpdateHoldingDto } from '../../src/investment/portfolio/dto/portfolio-asset.dto'; - -describe('PortfolioService', () => { +import { Test, TestingModule } from "@nestjs/testing"; +import { getRepositoryToken } from "@nestjs/typeorm"; +import { PortfolioService } from "../../src/investment/portfolio/services/portfolio.service"; +import { Portfolio } from "../../src/investment/portfolio/entities/portfolio.entity"; +import { + PortfolioAsset, + Chain, + AssetType, +} from "../../src/investment/portfolio/entities/portfolio-asset.entity"; +import { OptimizationHistory } from "../../src/investment/portfolio/entities/optimization-history.entity"; +import { RiskProfile } from "../../src/investment/portfolio/entities/risk-profile.entity"; +import { CreatePortfolioDto } from "../../src/investment/portfolio/dto/portfolio.dto"; +import { OptimizationMethod } from "../../src/investment/portfolio/entities/optimization-history.entity"; +import { + AddHoldingDto, + UpdateHoldingDto, +} from "../../src/investment/portfolio/dto/portfolio-asset.dto"; + +describe("PortfolioService", () => { let service: PortfolioService; let portfolioRepository: any; let assetRepository: any; @@ -17,10 +24,10 @@ describe('PortfolioService', () => { let riskProfileRepository: any; const mockPortfolio = { - id: 'test-portfolio-1', - userId: 'test-user-1', - name: 'Test Portfolio', - status: 'active', + id: "test-portfolio-1", + userId: "test-user-1", + name: "Test Portfolio", + status: "active", totalValue: 100000, currentAllocation: { AAPL: 30, MSFT: 70 }, targetAllocation: null, @@ -31,15 +38,15 @@ describe('PortfolioService', () => { }; const mockAsset = { - id: 'asset-1', - ticker: 'AAPL', - name: 'Apple', + id: "asset-1", + ticker: "AAPL", + name: "Apple", chain: Chain.OTHER, quantity: 100, currentPrice: 150, value: 15000, allocationPercentage: 15, - portfolioId: 'test-portfolio-1', + portfolioId: "test-portfolio-1", save: jest.fn(), }; @@ -94,26 +101,23 @@ describe('PortfolioService', () => { service = module.get(PortfolioService); }); - it('should be defined', () => { + it("should be defined", () => { expect(service).toBeDefined(); }); - describe('createPortfolio', () => { - it('should create a new portfolio', async () => { + describe("createPortfolio", () => { + it("should create a new portfolio", async () => { const dto: CreatePortfolioDto = { - name: 'Test Portfolio', - description: 'Test description', + name: "Test Portfolio", + description: "Test description", }; - const result = await service.createPortfolio( - 'test-user-1', - dto, - ); + const result = await service.createPortfolio("test-user-1", dto); expect(portfolioRepository.create).toHaveBeenCalledWith( expect.objectContaining({ name: dto.name, - userId: 'test-user-1', + userId: "test-user-1", }), ); expect(portfolioRepository.save).toHaveBeenCalled(); @@ -121,36 +125,32 @@ describe('PortfolioService', () => { }); }); - describe('getPortfolio', () => { - it('should return a portfolio by ID', async () => { - const result = await service.getPortfolio( - 'test-portfolio-1', - ); + describe("getPortfolio", () => { + it("should return a portfolio by ID", async () => { + const result = await service.getPortfolio("test-portfolio-1"); expect(portfolioRepository.findOne).toHaveBeenCalledWith({ - where: { id: 'test-portfolio-1' }, + where: { id: "test-portfolio-1" }, relations: expect.any(Array), }); expect(result).toEqual(mockPortfolio); }); - it('should throw error if portfolio not found', async () => { + it("should throw error if portfolio not found", async () => { portfolioRepository.findOne.mockResolvedValue(null); - await expect( - service.getPortfolio('non-existent'), - ).rejects.toThrow('Portfolio not found'); + await expect(service.getPortfolio("non-existent")).rejects.toThrow( + "Portfolio not found", + ); }); }); - describe('getUserPortfolios', () => { - it('should return all portfolios for a user', async () => { - const result = await service.getUserPortfolios( - 'test-user-1', - ); + describe("getUserPortfolios", () => { + it("should return all portfolios for a user", async () => { + const result = await service.getUserPortfolios("test-user-1"); expect(portfolioRepository.find).toHaveBeenCalledWith({ - where: { userId: 'test-user-1' }, + where: { userId: "test-user-1" }, relations: expect.any(Array), order: expect.any(Object), }); @@ -158,12 +158,12 @@ describe('PortfolioService', () => { }); }); - describe('addAsset', () => { - it('should add an asset to portfolio', async () => { + describe("addAsset", () => { + it("should add an asset to portfolio", async () => { const result = await service.addAsset( - 'test-portfolio-1', - 'AAPL', - 'Apple', + "test-portfolio-1", + "AAPL", + "Apple", 100, 150, 0, @@ -174,28 +174,25 @@ describe('PortfolioService', () => { }); }); - describe('updateAssetPrice', () => { - it('should update asset price', async () => { + describe("updateAssetPrice", () => { + it("should update asset price", async () => { const newPrice = 160; - const result = await service.updateAssetPrice( - 'asset-1', - newPrice, - ); + const result = await service.updateAssetPrice("asset-1", newPrice); expect(assetRepository.findOne).toHaveBeenCalledWith({ - where: { id: 'asset-1' }, + where: { id: "asset-1" }, }); expect(result.currentPrice).toBeDefined(); }); }); - describe('runOptimization', () => { - it('should run portfolio optimization', async () => { + describe("runOptimization", () => { + it("should run portfolio optimization", async () => { optimizationRepository.create.mockReturnValue({ - portfolioId: 'test-portfolio-1', + portfolioId: "test-portfolio-1", method: OptimizationMethod.MEAN_VARIANCE, - status: 'pending', + status: "pending", parameters: {}, suggestedAllocation: {}, currentAllocation: mockPortfolio.currentAllocation, @@ -204,18 +201,18 @@ describe('PortfolioService', () => { optimizationRepository.save .mockResolvedValueOnce({ - id: 'opt-1', - portfolioId: 'test-portfolio-1', + id: "opt-1", + portfolioId: "test-portfolio-1", method: OptimizationMethod.MEAN_VARIANCE, - status: 'in_progress', + status: "in_progress", suggestedAllocation: {}, parameters: {}, currentAllocation: mockPortfolio.currentAllocation, save: jest.fn(), }) .mockResolvedValueOnce({ - id: 'opt-1', - status: 'completed', + id: "opt-1", + status: "completed", suggestedAllocation: { AAPL: 40, MSFT: 60 }, expectedReturn: 0.08, expectedVolatility: 0.15, @@ -226,23 +223,20 @@ describe('PortfolioService', () => { assetRepository.save.mockResolvedValue([mockAsset]); - const result = await service.runOptimization( - 'test-portfolio-1', - { - method: OptimizationMethod.MEAN_VARIANCE, - portfolioId: 'test-portfolio-1', - }, - ); + const result = await service.runOptimization("test-portfolio-1", { + method: OptimizationMethod.MEAN_VARIANCE, + portfolioId: "test-portfolio-1", + }); - expect(result.status).toBe('completed'); + expect(result.status).toBe("completed"); }); }); - describe('addHolding', () => { - it('should add a new holding to portfolio', async () => { + describe("addHolding", () => { + it("should add a new holding to portfolio", async () => { const dto: AddHoldingDto = { - ticker: 'ETH', - name: 'Ethereum', + ticker: "ETH", + name: "Ethereum", chain: Chain.ETHEREUM, quantity: 10, currentPrice: 2000, @@ -253,10 +247,14 @@ describe('PortfolioService', () => { assetRepository.create.mockReturnValue({ ...mockAsset, ...dto }); assetRepository.save.mockResolvedValue({ ...mockAsset, ...dto }); - const result = await service.addHolding('test-portfolio-1', dto); + const result = await service.addHolding("test-portfolio-1", dto); expect(assetRepository.findOne).toHaveBeenCalledWith({ - where: { portfolioId: 'test-portfolio-1', ticker: dto.ticker, chain: dto.chain }, + where: { + portfolioId: "test-portfolio-1", + ticker: dto.ticker, + chain: dto.chain, + }, }); expect(assetRepository.create).toHaveBeenCalled(); expect(assetRepository.save).toHaveBeenCalled(); @@ -264,10 +262,10 @@ describe('PortfolioService', () => { expect(result.chain).toBe(dto.chain); }); - it('should throw error if holding already exists', async () => { + it("should throw error if holding already exists", async () => { const dto: AddHoldingDto = { - ticker: 'ETH', - name: 'Ethereum', + ticker: "ETH", + name: "Ethereum", chain: Chain.ETHEREUM, quantity: 10, currentPrice: 2000, @@ -276,14 +274,14 @@ describe('PortfolioService', () => { assetRepository.findOne.mockResolvedValue(mockAsset); - await expect( - service.addHolding('test-portfolio-1', dto), - ).rejects.toThrow('Holding with same ticker and chain already exists'); + await expect(service.addHolding("test-portfolio-1", dto)).rejects.toThrow( + "Holding with same ticker and chain already exists", + ); }); }); - describe('updateHolding', () => { - it('should update holding in portfolio', async () => { + describe("updateHolding", () => { + it("should update holding in portfolio", async () => { const dto: UpdateHoldingDto = { quantity: 20, currentPrice: 2500, @@ -293,43 +291,47 @@ describe('PortfolioService', () => { assetRepository.findOne.mockResolvedValue(mockAsset); assetRepository.save.mockResolvedValue(updatedAsset); - const result = await service.updateHolding('test-portfolio-1', 'asset-1', dto); + const result = await service.updateHolding( + "test-portfolio-1", + "asset-1", + dto, + ); expect(assetRepository.findOne).toHaveBeenCalledWith({ - where: { id: 'asset-1', portfolioId: 'test-portfolio-1' }, + where: { id: "asset-1", portfolioId: "test-portfolio-1" }, }); expect(assetRepository.save).toHaveBeenCalled(); }); - it('should throw error if holding not found', async () => { + it("should throw error if holding not found", async () => { const dto: UpdateHoldingDto = { quantity: 20 }; assetRepository.findOne.mockResolvedValue(null); await expect( - service.updateHolding('test-portfolio-1', 'non-existent', dto), - ).rejects.toThrow('Holding not found'); + service.updateHolding("test-portfolio-1", "non-existent", dto), + ).rejects.toThrow("Holding not found"); }); }); - describe('removeHolding', () => { - it('should remove holding from portfolio', async () => { + describe("removeHolding", () => { + it("should remove holding from portfolio", async () => { assetRepository.findOne.mockResolvedValue(mockAsset); assetRepository.remove.mockResolvedValue(null); - await service.removeHolding('test-portfolio-1', 'asset-1'); + await service.removeHolding("test-portfolio-1", "asset-1"); expect(assetRepository.findOne).toHaveBeenCalledWith({ - where: { id: 'asset-1', portfolioId: 'test-portfolio-1' }, + where: { id: "asset-1", portfolioId: "test-portfolio-1" }, }); expect(assetRepository.remove).toHaveBeenCalledWith(mockAsset); }); - it('should throw error if holding not found', async () => { + it("should throw error if holding not found", async () => { assetRepository.findOne.mockResolvedValue(null); await expect( - service.removeHolding('test-portfolio-1', 'non-existent'), - ).rejects.toThrow('Holding not found'); + service.removeHolding("test-portfolio-1", "non-existent"), + ).rejects.toThrow("Holding not found"); }); }); }); diff --git a/test/recovery.spec.ts b/test/recovery.spec.ts index e712f29..2df4fbb 100644 --- a/test/recovery.spec.ts +++ b/test/recovery.spec.ts @@ -1,21 +1,21 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { NotFoundException } from '@nestjs/common'; -import { RecoveryService } from '../src/core/auth/recovery.service'; -import { EmailLinkingService } from '../src/core/auth/email-linking.service'; -import { EmailService } from '../src/core/auth/email.service'; -import { ChallengeService } from '../src/core/auth/challenge.service'; -import { User } from '../src/core/user/entities/user.entity'; - -describe('RecoveryService', () => { +import { Test, TestingModule } from "@nestjs/testing"; +import { NotFoundException } from "@nestjs/common"; +import { RecoveryService } from "../src/core/auth/recovery.service"; +import { EmailLinkingService } from "../src/core/auth/email-linking.service"; +import { EmailService } from "../src/core/auth/email.service"; +import { ChallengeService } from "../src/core/auth/challenge.service"; +import { User } from "../src/core/user/entities/user.entity"; + +describe("RecoveryService", () => { let service: RecoveryService; let emailLinkingService: EmailLinkingService; let emailService: EmailService; let challengeService: ChallengeService; const mockUser: User = { - id: '123', - walletAddress: '0x1234567890123456789012345678901234567890', - email: 'test@example.com', + id: "123", + walletAddress: "0x1234567890123456789012345678901234567890", + email: "test@example.com", emailVerified: true, createdAt: new Date(), updatedAt: new Date(), @@ -27,15 +27,15 @@ describe('RecoveryService', () => { const mockEmailService = { sendRecoveryEmail: jest.fn().mockResolvedValue({ - messageId: 'test-message-id', - previewUrl: 'https://ethereal.email/message/test', + messageId: "test-message-id", + previewUrl: "https://ethereal.email/message/test", }), }; const mockChallengeService = { - issueChallengeForAddress: jest.fn().mockReturnValue( - 'Sign this message to authenticate: abc123', - ), + issueChallengeForAddress: jest + .fn() + .mockReturnValue("Sign this message to authenticate: abc123"), }; beforeEach(async () => { @@ -63,54 +63,63 @@ describe('RecoveryService', () => { challengeService = module.get(ChallengeService); }); - describe('requestRecovery', () => { - it('should send recovery email for verified account', async () => { - jest.spyOn(emailLinkingService, 'getUserByEmail').mockResolvedValue(mockUser); + describe("requestRecovery", () => { + it("should send recovery email for verified account", async () => { + jest + .spyOn(emailLinkingService, "getUserByEmail") + .mockResolvedValue(mockUser); - const result = await service.requestRecovery('test@example.com'); + const result = await service.requestRecovery("test@example.com"); - expect(result.message).toContain('Recovery information sent'); + expect(result.message).toContain("Recovery information sent"); expect(emailService.sendRecoveryEmail).toHaveBeenCalledWith( - 'test@example.com', + "test@example.com", mockUser.walletAddress, ); }); - it('should throw error for non-existent email', async () => { - jest.spyOn(emailLinkingService, 'getUserByEmail').mockResolvedValue(null); + it("should throw error for non-existent email", async () => { + jest.spyOn(emailLinkingService, "getUserByEmail").mockResolvedValue(null); - await expect(service.requestRecovery('nonexistent@example.com')).rejects.toThrow( - NotFoundException, - ); + await expect( + service.requestRecovery("nonexistent@example.com"), + ).rejects.toThrow(NotFoundException); }); - it('should normalize email to lowercase', async () => { - jest.spyOn(emailLinkingService, 'getUserByEmail').mockResolvedValue(mockUser); + it("should normalize email to lowercase", async () => { + jest + .spyOn(emailLinkingService, "getUserByEmail") + .mockResolvedValue(mockUser); - await service.requestRecovery('TEST@EXAMPLE.COM'); + await service.requestRecovery("TEST@EXAMPLE.COM"); - expect(emailLinkingService.getUserByEmail).toHaveBeenCalledWith('test@example.com'); + expect(emailLinkingService.getUserByEmail).toHaveBeenCalledWith( + "test@example.com", + ); }); }); - describe('verifyRecoveryAndGetChallenge', () => { - it('should return challenge for verified email', async () => { - jest.spyOn(emailLinkingService, 'getUserByEmail').mockResolvedValue(mockUser); + describe("verifyRecoveryAndGetChallenge", () => { + it("should return challenge for verified email", async () => { + jest + .spyOn(emailLinkingService, "getUserByEmail") + .mockResolvedValue(mockUser); - const result = await service.verifyRecoveryAndGetChallenge('test@example.com'); + const result = + await service.verifyRecoveryAndGetChallenge("test@example.com"); - expect(result.message).toContain('Sign this message'); + expect(result.message).toContain("Sign this message"); expect(result.walletAddress).toBe(mockUser.walletAddress); expect(challengeService.issueChallengeForAddress).toHaveBeenCalledWith( mockUser.walletAddress, ); }); - it('should throw error for non-existent email', async () => { - jest.spyOn(emailLinkingService, 'getUserByEmail').mockResolvedValue(null); + it("should throw error for non-existent email", async () => { + jest.spyOn(emailLinkingService, "getUserByEmail").mockResolvedValue(null); await expect( - service.verifyRecoveryAndGetChallenge('nonexistent@example.com'), + service.verifyRecoveryAndGetChallenge("nonexistent@example.com"), ).rejects.toThrow(NotFoundException); }); }); diff --git a/test/traditional-auth.e2e-spec.ts b/test/traditional-auth.e2e-spec.ts index 66c4cb8..fe7375d 100644 --- a/test/traditional-auth.e2e-spec.ts +++ b/test/traditional-auth.e2e-spec.ts @@ -1,9 +1,9 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication, ValidationPipe } from '@nestjs/common'; -import * as request from 'supertest'; -import { AuthModule } from '../src/core/auth/auth.module'; +import { Test, TestingModule } from "@nestjs/testing"; +import { INestApplication, ValidationPipe } from "@nestjs/common"; +import * as request from "supertest"; +import { AuthModule } from "../src/core/auth/auth.module"; -describe('Traditional Authentication (e2e)', () => { +describe("Traditional Authentication (e2e)", () => { let app: INestApplication; beforeAll(async () => { @@ -20,188 +20,180 @@ describe('Traditional Authentication (e2e)', () => { await app.close(); }); - describe('/auth/register (POST)', () => { - it('should register a new user', () => { + describe("/auth/register (POST)", () => { + it("should register a new user", () => { return request(app.getHttpServer()) - .post('/auth/register') + .post("/auth/register") .send({ - email: 'test@example.com', - password: 'password123', - username: 'testuser', + email: "test@example.com", + password: "password123", + username: "testuser", }) .expect(201) .expect((res) => { - expect(res.body).toHaveProperty('token'); - expect(res.body).toHaveProperty('user'); - expect(res.body.user.email).toBe('test@example.com'); - expect(res.body.user.username).toBe('testuser'); + expect(res.body).toHaveProperty("token"); + expect(res.body).toHaveProperty("user"); + expect(res.body.user.email).toBe("test@example.com"); + expect(res.body.user.username).toBe("testuser"); }); }); - it('should reject registration with existing email', async () => { + it("should reject registration with existing email", async () => { // First register a user - await request(app.getHttpServer()) - .post('/auth/register') - .send({ - email: 'duplicate@example.com', - password: 'password123', - username: 'user1', - }); + await request(app.getHttpServer()).post("/auth/register").send({ + email: "duplicate@example.com", + password: "password123", + username: "user1", + }); // Try to register again with same email return request(app.getHttpServer()) - .post('/auth/register') + .post("/auth/register") .send({ - email: 'duplicate@example.com', - password: 'password456', - username: 'user2', + email: "duplicate@example.com", + password: "password456", + username: "user2", }) .expect(409); }); - it('should reject registration with invalid email', () => { + it("should reject registration with invalid email", () => { return request(app.getHttpServer()) - .post('/auth/register') + .post("/auth/register") .send({ - email: 'invalid-email', - password: 'password123', - username: 'testuser', + email: "invalid-email", + password: "password123", + username: "testuser", }) .expect(400); }); - it('should reject registration with short password', () => { + it("should reject registration with short password", () => { return request(app.getHttpServer()) - .post('/auth/register') + .post("/auth/register") .send({ - email: 'test@example.com', - password: '123', - username: 'testuser', + email: "test@example.com", + password: "123", + username: "testuser", }) .expect(400); }); }); - describe('/auth/login (POST)', () => { + describe("/auth/login (POST)", () => { beforeEach(async () => { // Register a test user - await request(app.getHttpServer()) - .post('/auth/register') - .send({ - email: 'login@example.com', - password: 'password123', - username: 'logintest', - }); + await request(app.getHttpServer()).post("/auth/register").send({ + email: "login@example.com", + password: "password123", + username: "logintest", + }); }); - it('should login with correct credentials', () => { + it("should login with correct credentials", () => { return request(app.getHttpServer()) - .post('/auth/login') + .post("/auth/login") .send({ - email: 'login@example.com', - password: 'password123', + email: "login@example.com", + password: "password123", }) .expect(201) .expect((res) => { - expect(res.body).toHaveProperty('token'); - expect(res.body).toHaveProperty('user'); - expect(res.body.user.email).toBe('login@example.com'); + expect(res.body).toHaveProperty("token"); + expect(res.body).toHaveProperty("user"); + expect(res.body.user.email).toBe("login@example.com"); }); }); - it('should reject login with wrong password', () => { + it("should reject login with wrong password", () => { return request(app.getHttpServer()) - .post('/auth/login') + .post("/auth/login") .send({ - email: 'login@example.com', - password: 'wrongpassword', + email: "login@example.com", + password: "wrongpassword", }) .expect(401); }); - it('should reject login with non-existent email', () => { + it("should reject login with non-existent email", () => { return request(app.getHttpServer()) - .post('/auth/login') + .post("/auth/login") .send({ - email: 'nonexistent@example.com', - password: 'password123', + email: "nonexistent@example.com", + password: "password123", }) .expect(401); }); }); - describe('/auth/status (GET)', () => { + describe("/auth/status (GET)", () => { let token: string; beforeEach(async () => { // Register and login to get token const registerResponse = await request(app.getHttpServer()) - .post('/auth/register') + .post("/auth/register") .send({ - email: 'status@example.com', - password: 'password123', - username: 'statustest', + email: "status@example.com", + password: "password123", + username: "statustest", }); token = registerResponse.body.token; }); - it('should return auth status for authenticated user', () => { + it("should return auth status for authenticated user", () => { return request(app.getHttpServer()) - .get('/auth/status') - .set('Authorization', `Bearer ${token}`) + .get("/auth/status") + .set("Authorization", `Bearer ${token}`) .expect(200) .expect((res) => { expect(res.body.isAuthenticated).toBe(true); - expect(res.body.user.email).toBe('status@example.com'); - expect(res.body.user.username).toBe('statustest'); + expect(res.body.user.email).toBe("status@example.com"); + expect(res.body.user.username).toBe("statustest"); }); }); - it('should reject request without token', () => { - return request(app.getHttpServer()) - .get('/auth/status') - .expect(401); + it("should reject request without token", () => { + return request(app.getHttpServer()).get("/auth/status").expect(401); }); - it('should reject request with invalid token', () => { + it("should reject request with invalid token", () => { return request(app.getHttpServer()) - .get('/auth/status') - .set('Authorization', 'Bearer invalid-token') + .get("/auth/status") + .set("Authorization", "Bearer invalid-token") .expect(401); }); }); - describe('/auth/logout (POST)', () => { + describe("/auth/logout (POST)", () => { let token: string; beforeEach(async () => { // Register and login to get token const registerResponse = await request(app.getHttpServer()) - .post('/auth/register') + .post("/auth/register") .send({ - email: 'logout@example.com', - password: 'password123', - username: 'logouttest', + email: "logout@example.com", + password: "password123", + username: "logouttest", }); token = registerResponse.body.token; }); - it('should logout successfully', () => { + it("should logout successfully", () => { return request(app.getHttpServer()) - .post('/auth/logout') - .set('Authorization', `Bearer ${token}`) + .post("/auth/logout") + .set("Authorization", `Bearer ${token}`) .expect(201) .expect((res) => { - expect(res.body.message).toBe('Logged out successfully'); + expect(res.body.message).toBe("Logged out successfully"); }); }); - it('should reject logout without token', () => { - return request(app.getHttpServer()) - .post('/auth/logout') - .expect(401); + it("should reject logout without token", () => { + return request(app.getHttpServer()).post("/auth/logout").expect(401); }); }); -}); \ No newline at end of file +}); diff --git a/test/wallet-auth.spec.ts b/test/wallet-auth.spec.ts index 9336037..934f753 100644 --- a/test/wallet-auth.spec.ts +++ b/test/wallet-auth.spec.ts @@ -1,11 +1,11 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { JwtService } from '@nestjs/jwt'; -import { UnauthorizedException } from '@nestjs/common'; -import { WalletAuthService } from '../src/core/auth/wallet-auth.service'; -import { ChallengeService } from '../src/core/auth/challenge.service'; -import { Wallet } from 'ethers'; - -describe('Wallet Authentication', () => { +import { Test, TestingModule } from "@nestjs/testing"; +import { JwtService } from "@nestjs/jwt"; +import { UnauthorizedException } from "@nestjs/common"; +import { WalletAuthService } from "../src/core/auth/wallet-auth.service"; +import { ChallengeService } from "../src/core/auth/challenge.service"; +import { Wallet } from "ethers"; + +describe("Wallet Authentication", () => { let walletAuthService: WalletAuthService; let challengeService: ChallengeService; let jwtService: JwtService; @@ -20,13 +20,13 @@ describe('Wallet Authentication', () => { provide: JwtService, useValue: { sign: (payload) => { - return 'test-token-' + JSON.stringify(payload); + return "test-token-" + JSON.stringify(payload); }, verify: (token) => { - if (token.startsWith('test-token-')) { - return JSON.parse(token.replace('test-token-', '')); + if (token.startsWith("test-token-")) { + return JSON.parse(token.replace("test-token-", "")); } - throw new Error('Invalid token'); + throw new Error("Invalid token"); }, }, }, @@ -41,16 +41,16 @@ describe('Wallet Authentication', () => { testWallet = Wallet.createRandom(); }); - describe('Challenge Issuance', () => { - it('should issue a valid challenge for an address', () => { + describe("Challenge Issuance", () => { + it("should issue a valid challenge for an address", () => { const address = testWallet.address; const message = challengeService.issueChallengeForAddress(address); - expect(message).toContain('Sign this message to authenticate:'); + expect(message).toContain("Sign this message to authenticate:"); expect(message).toBeTruthy(); }); - it('should extract challenge ID from message', () => { + it("should extract challenge ID from message", () => { const address = testWallet.address; const message = challengeService.issueChallengeForAddress(address); const challengeId = challengeService.extractChallengeId(message); @@ -59,7 +59,7 @@ describe('Wallet Authentication', () => { expect(challengeId).toHaveLength(64); // 32 bytes hex }); - it('should store challenge with expiration', () => { + it("should store challenge with expiration", () => { const address = testWallet.address; const message = challengeService.issueChallengeForAddress(address); const challengeId = challengeService.extractChallengeId(message); @@ -72,8 +72,8 @@ describe('Wallet Authentication', () => { }); }); - describe('Signature Verification', () => { - it('should verify a valid signature and return JWT token', async () => { + describe("Signature Verification", () => { + it("should verify a valid signature and return JWT token", async () => { const address = testWallet.address; const message = challengeService.issueChallengeForAddress(address); @@ -89,20 +89,23 @@ describe('Wallet Authentication', () => { expect(result.address).toBe(address.toLowerCase()); }); - it('should reject an invalid signature', async () => { + it("should reject an invalid signature", async () => { const address = testWallet.address; const message = challengeService.issueChallengeForAddress(address); // Create an invalid signature const invalidSignature = - '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; + "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; await expect( - walletAuthService.verifySignatureAndIssueToken(message, invalidSignature), + walletAuthService.verifySignatureAndIssueToken( + message, + invalidSignature, + ), ).rejects.toThrow(UnauthorizedException); }); - it('should reject signature from different address', async () => { + it("should reject signature from different address", async () => { const address = testWallet.address; const message = challengeService.issueChallengeForAddress(address); @@ -115,7 +118,7 @@ describe('Wallet Authentication', () => { ).rejects.toThrow(UnauthorizedException); }); - it('should reject expired challenge', async () => { + it("should reject expired challenge", async () => { const address = testWallet.address; const message = challengeService.issueChallengeForAddress(address); const challengeId = challengeService.extractChallengeId(message); @@ -129,12 +132,12 @@ describe('Wallet Authentication', () => { walletAuthService.verifySignatureAndIssueToken(message, signature), ).rejects.toThrow( new UnauthorizedException( - 'Challenge not found or expired. Please request a new challenge.', + "Challenge not found or expired. Please request a new challenge.", ), ); }); - it('should consume challenge after successful verification', async () => { + it("should consume challenge after successful verification", async () => { const address = testWallet.address; const message = challengeService.issueChallengeForAddress(address); const challengeId = challengeService.extractChallengeId(message); @@ -149,8 +152,8 @@ describe('Wallet Authentication', () => { }); }); - describe('JWT Token Validation', () => { - it('should validate a correct token', () => { + describe("JWT Token Validation", () => { + it("should validate a correct token", () => { const payload = { address: testWallet.address.toLowerCase(), iat: 0 }; const token = jwtService.sign(payload); @@ -159,8 +162,8 @@ describe('Wallet Authentication', () => { expect(result.address).toBe(payload.address); }); - it('should reject an invalid token', () => { - const invalidToken = 'invalid-token'; + it("should reject an invalid token", () => { + const invalidToken = "invalid-token"; expect(() => { walletAuthService.validateToken(invalidToken); @@ -168,13 +171,13 @@ describe('Wallet Authentication', () => { }); }); - describe('End-to-End Authentication Flow', () => { - it('should complete full authentication flow', async () => { + describe("End-to-End Authentication Flow", () => { + it("should complete full authentication flow", async () => { const address = testWallet.address; // 1. Request challenge const message = challengeService.issueChallengeForAddress(address); - expect(message).toContain('Sign this message to authenticate:'); + expect(message).toContain("Sign this message to authenticate:"); // 2. Sign message const signature = await testWallet.signMessage(message); diff --git a/test/wallet-management.e2e-spec.ts b/test/wallet-management.e2e-spec.ts index e43a721..7dbe39b 100644 --- a/test/wallet-management.e2e-spec.ts +++ b/test/wallet-management.e2e-spec.ts @@ -1,20 +1,20 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestApplication, ValidationPipe } from '@nestjs/common'; -import * as request from 'supertest'; -import { AppModule } from '../src/app.module'; -import { getRepositoryToken } from '@nestjs/typeorm'; -import { User, UserRole } from '../src/core/user/entities/user.entity'; -import { Repository } from 'typeorm'; - -describe('Wallet Management (e2e)', () => { +import { Test, TestingModule } from "@nestjs/testing"; +import { INestApplication, ValidationPipe } from "@nestjs/common"; +import * as request from "supertest"; +import { AppModule } from "../src/app.module"; +import { getRepositoryToken } from "@nestjs/typeorm"; +import { User, UserRole } from "../src/core/user/entities/user.entity"; +import { Repository } from "typeorm"; + +describe("Wallet Management (e2e)", () => { let app: INestApplication; let userRepository: Repository; let authToken: string; let testUser: User; - const testWalletAddress = '0x1234567890abcdef1234567890abcdef12345678'; - const newWalletAddress = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'; - const testEmail = 'test@example.com'; + const testWalletAddress = "0x1234567890abcdef1234567890abcdef12345678"; + const newWalletAddress = "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"; + const testEmail = "test@example.com"; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ @@ -40,13 +40,13 @@ describe('Wallet Management (e2e)', () => { // Get auth token const challengeResponse = await request(app.getHttpServer()) - .post('/auth/challenge') + .post("/auth/challenge") .send({ address: testWalletAddress }) .expect(201); // In real test, you would sign the challenge // For now, we'll mock the token - authToken = 'mock-jwt-token'; + authToken = "mock-jwt-token"; }); afterAll(async () => { @@ -57,53 +57,53 @@ describe('Wallet Management (e2e)', () => { await app.close(); }); - describe('POST /auth/link-wallet', () => { - it('should require authentication', async () => { + describe("POST /auth/link-wallet", () => { + it("should require authentication", async () => { return request(app.getHttpServer()) - .post('/auth/link-wallet') + .post("/auth/link-wallet") .send({ walletAddress: newWalletAddress, - message: 'test message', - signature: '0x' + '1'.repeat(130), + message: "test message", + signature: "0x" + "1".repeat(130), }) .expect(401); }); - it('should validate wallet address format', async () => { + it("should validate wallet address format", async () => { return request(app.getHttpServer()) - .post('/auth/link-wallet') - .set('Authorization', `Bearer ${authToken}`) + .post("/auth/link-wallet") + .set("Authorization", `Bearer ${authToken}`) .send({ - walletAddress: 'invalid-address', - message: 'test message', - signature: '0x' + '1'.repeat(130), + walletAddress: "invalid-address", + message: "test message", + signature: "0x" + "1".repeat(130), }) .expect(400); }); - it('should validate signature length', async () => { + it("should validate signature length", async () => { return request(app.getHttpServer()) - .post('/auth/link-wallet') - .set('Authorization', `Bearer ${authToken}`) + .post("/auth/link-wallet") + .set("Authorization", `Bearer ${authToken}`) .send({ walletAddress: newWalletAddress, - message: 'test message', - signature: '0x123', // Too short + message: "test message", + signature: "0x123", // Too short }) .expect(400); }); - it('should enforce rate limiting', async () => { + it("should enforce rate limiting", async () => { const requests = []; for (let i = 0; i < 6; i++) { requests.push( request(app.getHttpServer()) - .post('/auth/link-wallet') - .set('Authorization', `Bearer ${authToken}`) + .post("/auth/link-wallet") + .set("Authorization", `Bearer ${authToken}`) .send({ walletAddress: newWalletAddress, - message: 'test message', - signature: '0x' + '1'.repeat(130), + message: "test message", + signature: "0x" + "1".repeat(130), }), ); } @@ -114,51 +114,51 @@ describe('Wallet Management (e2e)', () => { }); }); - describe('POST /auth/unlink-wallet', () => { - it('should require authentication', async () => { + describe("POST /auth/unlink-wallet", () => { + it("should require authentication", async () => { return request(app.getHttpServer()) - .post('/auth/unlink-wallet') + .post("/auth/unlink-wallet") .send({ walletAddress: testWalletAddress }) .expect(401); }); - it('should validate wallet address format', async () => { + it("should validate wallet address format", async () => { return request(app.getHttpServer()) - .post('/auth/unlink-wallet') - .set('Authorization', `Bearer ${authToken}`) - .send({ walletAddress: 'invalid-address' }) + .post("/auth/unlink-wallet") + .set("Authorization", `Bearer ${authToken}`) + .send({ walletAddress: "invalid-address" }) .expect(400); }); - it('should require verified email before unlinking', async () => { + it("should require verified email before unlinking", async () => { // Create user without verified email const unverifiedUser = userRepository.create({ - walletAddress: '0x9999999999999999999999999999999999999999', - email: 'unverified@example.com', + walletAddress: "0x9999999999999999999999999999999999999999", + email: "unverified@example.com", emailVerified: false, role: UserRole.USER, }); await userRepository.save(unverifiedUser); const response = await request(app.getHttpServer()) - .post('/auth/unlink-wallet') - .set('Authorization', `Bearer ${authToken}`) + .post("/auth/unlink-wallet") + .set("Authorization", `Bearer ${authToken}`) .send({ walletAddress: unverifiedUser.walletAddress }) .expect(400); - expect(response.body.message).toContain('Email must be verified'); + expect(response.body.message).toContain("Email must be verified"); // Cleanup await userRepository.remove(unverifiedUser); }); - it('should enforce rate limiting', async () => { + it("should enforce rate limiting", async () => { const requests = []; for (let i = 0; i < 6; i++) { requests.push( request(app.getHttpServer()) - .post('/auth/unlink-wallet') - .set('Authorization', `Bearer ${authToken}`) + .post("/auth/unlink-wallet") + .set("Authorization", `Bearer ${authToken}`) .send({ walletAddress: testWalletAddress }), ); } @@ -169,51 +169,51 @@ describe('Wallet Management (e2e)', () => { }); }); - describe('POST /auth/recover-wallet', () => { - it('should validate email format', async () => { + describe("POST /auth/recover-wallet", () => { + it("should validate email format", async () => { return request(app.getHttpServer()) - .post('/auth/recover-wallet') + .post("/auth/recover-wallet") .send({ - email: 'invalid-email', - recoveryToken: 'a'.repeat(64), + email: "invalid-email", + recoveryToken: "a".repeat(64), }) .expect(400); }); - it('should validate recovery token length', async () => { + it("should validate recovery token length", async () => { return request(app.getHttpServer()) - .post('/auth/recover-wallet') + .post("/auth/recover-wallet") .send({ email: testEmail, - recoveryToken: 'short', + recoveryToken: "short", }) .expect(400); }); - it('should return challenge for valid recovery request', async () => { + it("should return challenge for valid recovery request", async () => { const response = await request(app.getHttpServer()) - .post('/auth/recover-wallet') + .post("/auth/recover-wallet") .send({ email: testEmail, - recoveryToken: 'a'.repeat(64), + recoveryToken: "a".repeat(64), }) .expect(201); - expect(response.body).toHaveProperty('message'); - expect(response.body).toHaveProperty('walletAddress'); - expect(response.body).toHaveProperty('challenge'); + expect(response.body).toHaveProperty("message"); + expect(response.body).toHaveProperty("walletAddress"); + expect(response.body).toHaveProperty("challenge"); expect(response.body.walletAddress).toBe(testWalletAddress.toLowerCase()); }); - it('should enforce strict rate limiting (3 requests per minute)', async () => { + it("should enforce strict rate limiting (3 requests per minute)", async () => { const requests = []; for (let i = 0; i < 4; i++) { requests.push( request(app.getHttpServer()) - .post('/auth/recover-wallet') + .post("/auth/recover-wallet") .send({ email: testEmail, - recoveryToken: 'a'.repeat(64), + recoveryToken: "a".repeat(64), }), ); } @@ -223,62 +223,62 @@ describe('Wallet Management (e2e)', () => { expect(rateLimited).toBe(true); }); - it('should return error for non-existent email', async () => { + it("should return error for non-existent email", async () => { return request(app.getHttpServer()) - .post('/auth/recover-wallet') + .post("/auth/recover-wallet") .send({ - email: 'nonexistent@example.com', - recoveryToken: 'a'.repeat(64), + email: "nonexistent@example.com", + recoveryToken: "a".repeat(64), }) .expect(400); }); }); - describe('Wallet Management Flow', () => { - it('should complete full wallet linking flow', async () => { + describe("Wallet Management Flow", () => { + it("should complete full wallet linking flow", async () => { // 1. Request challenge for new wallet const challengeResponse = await request(app.getHttpServer()) - .post('/auth/challenge') + .post("/auth/challenge") .send({ address: newWalletAddress }) .expect(201); - expect(challengeResponse.body).toHaveProperty('message'); - expect(challengeResponse.body).toHaveProperty('address'); + expect(challengeResponse.body).toHaveProperty("message"); + expect(challengeResponse.body).toHaveProperty("address"); // 2. Link new wallet (would require real signature in production) // This would fail without proper signature, but tests the endpoint structure const linkResponse = await request(app.getHttpServer()) - .post('/auth/link-wallet') - .set('Authorization', `Bearer ${authToken}`) + .post("/auth/link-wallet") + .set("Authorization", `Bearer ${authToken}`) .send({ walletAddress: newWalletAddress, message: challengeResponse.body.message, - signature: '0x' + '1'.repeat(130), + signature: "0x" + "1".repeat(130), }); // Expect either success or signature validation error expect([200, 201, 401]).toContain(linkResponse.status); }); - it('should complete full wallet recovery flow', async () => { + it("should complete full wallet recovery flow", async () => { // 1. Request recovery const recoveryResponse = await request(app.getHttpServer()) - .post('/auth/recover-wallet') + .post("/auth/recover-wallet") .send({ email: testEmail, - recoveryToken: 'a'.repeat(64), + recoveryToken: "a".repeat(64), }) .expect(201); - expect(recoveryResponse.body).toHaveProperty('challenge'); - expect(recoveryResponse.body).toHaveProperty('walletAddress'); + expect(recoveryResponse.body).toHaveProperty("challenge"); + expect(recoveryResponse.body).toHaveProperty("walletAddress"); // 2. User would sign the challenge and verify const verifyResponse = await request(app.getHttpServer()) - .post('/auth/verify') + .post("/auth/verify") .send({ message: recoveryResponse.body.challenge, - signature: '0x' + '1'.repeat(130), + signature: "0x" + "1".repeat(130), }); // Expect either success or signature validation error diff --git a/tsconfig.json b/tsconfig.json index bd20141..ba657f4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,7 @@ "target": "ES2021", "sourceMap": true, "outDir": "./dist", - "rootDir": "./src", + "rootDir": "./", "baseUrl": "./", "types": ["node", "jest"], "moduleResolution": "node", @@ -32,6 +32,6 @@ "src/*": ["src/*"] } }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "test"] + "include": ["src/**/*", "test/**/*"], + "exclude": ["node_modules", "dist"] } \ No newline at end of file