feat: add entity exposure detection (v0.3.0)

Implement entity exposure detection to prevent domain entities
from leaking to API responses. Detects when controllers/routes
return domain entities instead of DTOs.

Features:
- EntityExposure value object with detailed suggestions
- IEntityExposureDetector interface in domain layer
- EntityExposureDetector implementation in infrastructure
- Integration into AnalyzeProject use case
- CLI display with helpful suggestions
- 24 comprehensive unit tests (98% coverage)
- Examples for bad and good patterns

Detection scope:
- Infrastructure layer only (controllers, routes, handlers, resolvers, gateways)
- Identifies PascalCase entities without Dto/Request/Response suffixes
- Parses async methods with Promise<T> return types
- Provides step-by-step remediation suggestions

Test coverage:
- EntityExposureDetector: 98.07%
- Overall project: 90.6% statements, 83.97% branches
- 218 tests passing

BREAKING CHANGE: Version bump to 0.3.0
This commit is contained in:
imfozilbek
2025-11-24 13:51:12 +05:00
parent a3cd71070e
commit f46048172f
14 changed files with 893 additions and 17 deletions

View File

@@ -0,0 +1,33 @@
// ❌ BAD: Exposing domain entity Order in API response
class Order {
constructor(
public id: string,
public items: OrderItem[],
public total: number,
public customerId: string,
) {}
}
class OrderItem {
constructor(
public productId: string,
public quantity: number,
public price: number,
) {}
}
class BadOrderController {
async getOrder(orderId: string): Promise<Order> {
return {
id: orderId,
items: [],
total: 100,
customerId: "customer-123",
}
}
async listOrders(): Promise<Order[]> {
return []
}
}

View File

@@ -0,0 +1,42 @@
// ✅ GOOD: Using DTOs and Mappers instead of exposing domain entities
class User {
constructor(
private readonly id: string,
private email: string,
private password: string,
) {}
getId(): string {
return this.id
}
getEmail(): string {
return this.email
}
}
class UserResponseDto {
constructor(
public readonly id: string,
public readonly email: string,
) {}
}
class UserMapper {
static toDto(user: User): UserResponseDto {
return new UserResponseDto(user.getId(), user.getEmail())
}
}
class GoodUserController {
async getUser(userId: string): Promise<UserResponseDto> {
const user = new User(userId, "user@example.com", "hashed-password")
return UserMapper.toDto(user)
}
async listUsers(): Promise<UserResponseDto[]> {
const users = [new User("1", "user1@example.com", "password")]
return users.map((user) => UserMapper.toDto(user))
}
}