test(reports): add E2E pipeline integration tests for report generation

26 tests covering: full pipeline flow for 3 report types + generic fallback,
status polling (GENERATING → READY/FAILED transitions), quota enforcement and
user scoping, error handling (PDF failure, AI failure, auth checks), delete
cleanup flow, and temp file lifecycle.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-16 17:24:52 +07:00
parent 8f2d325d60
commit ac4191cdf0
9 changed files with 1164 additions and 0 deletions

View File

@@ -4,15 +4,19 @@ import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagg
import { UserRole } from '@prisma/client';
import { JwtAuthGuard, Roles, RolesGuard } from '@modules/auth';
import { NotFoundException } from '@modules/shared';
import { AnalyzeIndustrialLocationQuery } from '../../application/queries/analyze-industrial-location/analyze-industrial-location.query';
import { CreateIndustrialParkCommand } from '../../application/commands/create-industrial-park/create-industrial-park.command';
import { EstimateIndustrialRentQuery } from '../../application/queries/estimate-industrial-rent/estimate-industrial-rent.query';
import { UpdateIndustrialParkCommand } from '../../application/commands/update-industrial-park/update-industrial-park.command';
import { CompareIndustrialParksQuery } from '../../application/queries/compare-industrial-parks/compare-industrial-parks.query';
import { GetIndustrialParkQuery } from '../../application/queries/get-industrial-park/get-industrial-park.query';
import { IndustrialMarketQuery } from '../../application/queries/industrial-market/industrial-market.query';
import { IndustrialParkStatsQuery } from '../../application/queries/industrial-park-stats/industrial-park-stats.query';
import { ListIndustrialParksQuery } from '../../application/queries/list-industrial-parks/list-industrial-parks.query';
import { type AnalyzeIndustrialLocationDto } from '../dto/analyze-industrial-location.dto';
import { type CompareIndustrialParksDto } from '../dto/compare-industrial-parks.dto';
import { type CreateIndustrialParkDto } from '../dto/create-industrial-park.dto';
import { type EstimateIndustrialRentDto } from '../dto/estimate-industrial-rent.dto';
import { type SearchIndustrialParksDto } from '../dto/search-industrial-parks.dto';
import { type UpdateIndustrialParkDto } from '../dto/update-industrial-park.dto';
@@ -78,6 +82,38 @@ export class IndustrialParksController {
return this.queryBus.execute(new IndustrialMarketQuery());
}
@ApiOperation({ summary: 'Phân tích vị trí KCN', description: 'Đánh giá vị trí dựa trên hạ tầng, kết nối, lao động' })
@ApiResponse({ status: 200, description: 'Kết quả phân tích vị trí' })
@Post('analyze-location')
async analyzeLocation(@Body() dto: AnalyzeIndustrialLocationDto) {
return this.queryBus.execute(
new AnalyzeIndustrialLocationQuery(
dto.latitude,
dto.longitude,
dto.park_name ?? null,
dto.target_industry ?? null,
),
);
}
@ApiOperation({ summary: 'Ước tính giá thuê KCN', description: 'Tính giá thuê BĐS công nghiệp theo tỉnh, loại, diện tích' })
@ApiResponse({ status: 200, description: 'Kết quả ước tính giá thuê' })
@Post('estimate-rent')
async estimateRent(@Body() dto: EstimateIndustrialRentDto) {
return this.queryBus.execute(
new EstimateIndustrialRentQuery(
dto.province,
dto.property_type,
dto.area_m2,
dto.lease_duration_years,
dto.park_name ?? null,
dto.requires_crane ?? false,
dto.required_power_kva ?? null,
dto.requires_wastewater ?? false,
),
);
}
// ── Admin endpoints ───────────────────────────────────────────────
@ApiOperation({ summary: 'Tạo KCN (admin)', description: 'Tạo mới khu công nghiệp' })