diff --git a/package-lock.json b/package-lock.json index ce4c221..d4b3801 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,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", @@ -94,7 +95,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", @@ -1027,6 +1028,26 @@ "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", @@ -10790,6 +10811,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", @@ -13831,6 +13865,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", @@ -13887,6 +13933,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", diff --git a/package.json b/package.json index db6964d..cd3a484 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,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", @@ -122,7 +123,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 22db2db..210a62a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -249,5 +249,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 81ebc06..2151f72 100644 --- a/src/blockchain/oracle/oracle.module.ts +++ b/src/blockchain/oracle/oracle.module.ts @@ -46,6 +46,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 e27616e..6f12abf 100644 --- a/src/common/decorators/rate-limit.decorator.ts +++ b/src/common/decorators/rate-limit.decorator.ts @@ -48,5 +48,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/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 3e3557c..4016903 100644 --- a/src/config/env.validation.ts +++ b/src/config/env.validation.ts @@ -250,6 +250,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 7af6d70..4829f8d 100644 --- a/src/config/quota.config.ts +++ b/src/config/quota.config.ts @@ -137,4 +137,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 332f1cd..0a9ed28 100644 --- a/src/config/tracing.ts +++ b/src/config/tracing.ts @@ -130,24 +130,3 @@ export const createSpan = async ( } }); }; - -/** - * Extract trace context from an incoming carrier (HTTP headers, WS handshake - * headers, message metadata, etc.) and return an active context so that child - * spans are correctly parented to the upstream trace. - */ -export const extractContext = (carrier: Record) => { - return propagation.extract(ROOT_CONTEXT, carrier); -}; - -/** - * Inject the current trace context into an outgoing carrier so downstream - * services can continue the trace. - */ -export const injectContext = ( - carrier: Record, - ctx = context.active(), -) => { - propagation.inject(ctx, carrier); - return carrier; -}; diff --git a/src/core/auth/auth.controller.ts b/src/core/auth/auth.controller.ts index 7c8cce0..62a6cdd 100644 --- a/src/core/auth/auth.controller.ts +++ b/src/core/auth/auth.controller.ts @@ -491,4 +491,3 @@ export class AuthController { }; } } - diff --git a/src/core/auth/auth.module.ts b/src/core/auth/auth.module.ts index d6715cd..be0937d 100644 --- a/src/core/auth/auth.module.ts +++ b/src/core/auth/auth.module.ts @@ -148,6 +148,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 448b5fb..3f482db 100644 --- a/src/core/auth/auth.service.spec.ts +++ b/src/core/auth/auth.service.spec.ts @@ -262,6 +262,3 @@ describe("AuthService", () => { }); }); }); - - - diff --git a/src/core/auth/auth.service.ts b/src/core/auth/auth.service.ts index c8cc26d..9b96c63 100644 --- a/src/core/auth/auth.service.ts +++ b/src/core/auth/auth.service.ts @@ -194,4 +194,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 bdf5faf..a90ee6f 100644 --- a/src/core/auth/enhanced-auth.service.ts +++ b/src/core/auth/enhanced-auth.service.ts @@ -651,4 +651,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 ebf53ee..a619f33 100644 --- a/src/core/auth/guards/strategy-auth.guard.ts +++ b/src/core/auth/guards/strategy-auth.guard.ts @@ -138,5 +138,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 2d3a721..7861210 100644 --- a/src/core/auth/jwt.strategy.ts +++ b/src/core/auth/jwt.strategy.ts @@ -91,5 +91,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 3f8a4f9..c3cdcae 100644 --- a/src/core/auth/strategies/api-key/api-key.strategy.ts +++ b/src/core/auth/strategies/api-key/api-key.strategy.ts @@ -267,5 +267,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 2a64c3d..89dbde4 100644 --- a/src/core/auth/strategies/interfaces/auth-strategy.interface.ts +++ b/src/core/auth/strategies/interfaces/auth-strategy.interface.ts @@ -164,5 +164,3 @@ export interface ApiKeyCredentials { /** API secret */ apiSecret: string; } - - 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 f88d60b..beef61e 100644 --- a/src/core/auth/strategies/traditional/traditional.strategy.ts +++ b/src/core/auth/strategies/traditional/traditional.strategy.ts @@ -185,5 +185,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 3b268e9..683fb89 100644 --- a/src/core/auth/strategies/wallet/wallet.strategy.ts +++ b/src/core/auth/strategies/wallet/wallet.strategy.ts @@ -136,5 +136,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 d7ab0e7..4b6c2fe 100644 --- a/src/core/auth/wallet-auth.service.spec.ts +++ b/src/core/auth/wallet-auth.service.spec.ts @@ -409,6 +409,3 @@ describe("WalletAuthService", () => { }); }); }); - - - diff --git a/src/core/auth/wallet-auth.service.ts b/src/core/auth/wallet-auth.service.ts index 9176bf8..d4c4e58 100644 --- a/src/core/auth/wallet-auth.service.ts +++ b/src/core/auth/wallet-auth.service.ts @@ -554,5 +554,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/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 9b2dd1a..a1d99a5 100644 --- a/src/dashboard/websocket/dashboard.gateway.ts +++ b/src/dashboard/websocket/dashboard.gateway.ts @@ -356,6 +356,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 b8d55ab..b2ba189 100644 --- a/src/infrastructure/audit/audit-log.service.ts +++ b/src/infrastructure/audit/audit-log.service.ts @@ -189,6 +189,3 @@ export class AuditLogService { .execute(); } } - - - 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/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 0ad73f0..edf8453 100644 --- a/src/investment/portfolio/portfolio.controller.ts +++ b/src/investment/portfolio/portfolio.controller.ts @@ -480,4 +480,3 @@ export class PortfolioController { ); } } - diff --git a/src/investment/portfolio/portfolio.module.ts b/src/investment/portfolio/portfolio.module.ts index 3a50228..39959c5 100644 --- a/src/investment/portfolio/portfolio.module.ts +++ b/src/investment/portfolio/portfolio.module.ts @@ -32,6 +32,7 @@ import { RebalancingProcessor } from "./processors/rebalancing.processor"; // 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({ @@ -78,7 +79,11 @@ import { PortfolioOwnerGuard } from "./guards/portfolio-owner.guard"; PortfolioOwnerGuard, RebalancingProcessor, ], - controllers: [PortfolioController, PortfolioManagementController], + controllers: [ + PortfolioController, + PortfolioManagementController, + TransactionController, + ], exports: [ PortfolioService, 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/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 c56687f..e4bec5d 100644 --- a/src/observability/observability.module.ts +++ b/src/observability/observability.module.ts @@ -35,6 +35,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