166 lines
8.2 KiB
JavaScript
166 lines
8.2 KiB
JavaScript
"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
|