"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const testing_1 = require("@nestjs/testing"); const common_1 = require("@nestjs/common"); const supertest_1 = __importDefault(require("supertest")); const app_module_1 = require("../src/app.module"); const jwt_1 = require("@nestjs/jwt"); const client_1 = require("@prisma/client"); const http_exception_filter_1 = require("../src/common/filters/http-exception.filter"); const prisma_exception_filter_1 = require("../src/common/filters/prisma-exception.filter"); const decimal_interceptor_1 = require("../src/common/interceptors/decimal.interceptor"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); describe('Phase 4 Master Backlog E2E Verification Matrix (e2e)', () => { let app; let jwtService; let adminToken; let userToken; beforeAll(async () => { process.env.JWT_ACCESS_SECRET = 'test_access_secret_32_characters_minimum_entropy'; process.env.JWT_REFRESH_SECRET = 'test_refresh_secret_32_characters_minimum_entropy'; const moduleFixture = await testing_1.Test.createTestingModule({ imports: [app_module_1.AppModule], }).compile(); app = moduleFixture.createNestApplication(); app.setGlobalPrefix('api'); app.useGlobalPipes(new common_1.ValidationPipe({ transform: true, whitelist: true })); app.useGlobalFilters(new http_exception_filter_1.CustomHttpExceptionFilter(), new prisma_exception_filter_1.PrismaExceptionFilter()); app.useGlobalInterceptors(new decimal_interceptor_1.DecimalInterceptor()); await app.init(); jwtService = new jwt_1.JwtService(); adminToken = jwtService.sign({ sub: '12345678-1234-1234-1234-123456789012', email: 'admin@canino.ir', role: 'Admin', }, { secret: process.env.JWT_ACCESS_SECRET }); userToken = jwtService.sign({ sub: '12345678-1234-1234-1234-123456789012', email: 'user@canino.ir', role: 'User_PetOwner', }, { secret: process.env.JWT_ACCESS_SECRET }); }); afterAll(async () => { if (app) { await app.close(); } }); describe('TASK-SEC-001: Mandatory Dual-Secret Startup Enforcement', () => { it('should reject startup logic if JWT_ACCESS_SECRET is missing or under 32 characters', () => { const validateStartup = (accessSec, refreshSec) => { if (!accessSec || accessSec.trim().length < 32) { throw new Error('FATAL: JWT_ACCESS_SECRET missing or short'); } if (!refreshSec || refreshSec.trim().length < 32) { throw new Error('FATAL: JWT_REFRESH_SECRET missing or short'); } }; expect(() => validateStartup('short', process.env.JWT_REFRESH_SECRET)).toThrow('FATAL: JWT_ACCESS_SECRET missing or short'); expect(() => validateStartup(process.env.JWT_ACCESS_SECRET, 'short')).toThrow('FATAL: JWT_REFRESH_SECRET missing or short'); expect(() => validateStartup(process.env.JWT_ACCESS_SECRET, process.env.JWT_REFRESH_SECRET)).not.toThrow(); }); }); describe('TASK-SEC-002: Cryptographically Secure OTP Generation & Response Payload Hardening', () => { it('should NOT disclose OTP plaintext code in POST /api/auth/send-otp response', async () => { const httpServer = app.getHttpServer(); const response = await (0, supertest_1.default)(httpServer) .post('/api/auth/send-otp') .send({ phoneNumber: '09123456789' }); expect(response.status).toBe(200); expect(response.body).toHaveProperty('success', true); expect(response.body).toHaveProperty('message'); expect(response.body).not.toHaveProperty('code'); }); }); describe('TASK-SEC-003: Role-Based Access Control (RBAC) Enforcement on Settings API', () => { it('should forbid non-admin users (User_PetOwner) with HTTP 403 when accessing /api/settings/ui-texts', async () => { const httpServer = app.getHttpServer(); const response = await (0, supertest_1.default)(httpServer) .get('/api/settings/ui-texts') .set('Authorization', `Bearer ${userToken}`); expect(response.status).toBe(403); }); it('should allow admin users (Admin) with HTTP 200 when accessing /api/settings/ui-texts', async () => { const httpServer = app.getHttpServer(); const response = await (0, supertest_1.default)(httpServer) .get('/api/settings/ui-texts') .set('Authorization', `Bearer ${adminToken}`); expect(response.status).toBe(200); }); }); describe('TASK-FIN-001: Arbitrary-Precision Decimal Accounting & Prisma.Decimal Serialization', () => { it('should perform exact arbitrary-precision arithmetic (19.99 * 3 + 5.01 = 64.98)', () => { const item1Price = new client_1.Prisma.Decimal('19.99'); const item1Qty = 3; const item2Price = new client_1.Prisma.Decimal('5.01'); const item2Qty = 1; const subtotal1 = item1Price.mul(item1Qty); const subtotal2 = item2Price.mul(item2Qty); const total = subtotal1.add(subtotal2); expect(total.toString()).toBe('64.98'); expect(Number(total)).toBe(64.98); }); it('should transform Prisma.Decimal instances into formatted strings via DecimalInterceptor', () => { const interceptor = new decimal_interceptor_1.DecimalInterceptor(); const mockDecimal = new client_1.Prisma.Decimal('149.50'); const testPayload = { id: 'ord-1', totalAmount: mockDecimal, items: [{ price: new client_1.Prisma.Decimal('49.99') }], }; const decimalTransform = interceptor; const transformed = decimalTransform.transform(testPayload); expect(transformed.totalAmount).toBe('149.5'); expect(transformed.items[0].price).toBe('49.99'); }); }); describe('TASK-DOC-001: OpenAPI Documentation Synchronization', () => { it('should confirm root swagger.yml file exists and is synchronized', () => { const rootSwaggerPath = path.resolve(__dirname, '../../swagger.yml'); expect(fs.existsSync(rootSwaggerPath)).toBe(true); const content = fs.readFileSync(rootSwaggerPath, 'utf8'); expect(content).toContain('openapi: 3.0.0'); expect(content).toContain('/api/auth/send-otp'); expect(content).toContain('/api/settings'); }); }); }); //# sourceMappingURL=app-audit-verification.e2e-spec.js.map