mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-27 23:06:54 +05:00
676 lines
23 KiB
TypeScript
676 lines
23 KiB
TypeScript
import { describe, it, expect, beforeEach } from "vitest"
|
|
import { NamingConventionDetector } from "../../../src/infrastructure/analyzers/NamingConventionDetector"
|
|
import { LAYERS, NAMING_VIOLATION_TYPES } from "../../../src/shared/constants"
|
|
|
|
describe("NamingConventionDetector - AST-based", () => {
|
|
let detector: NamingConventionDetector
|
|
|
|
beforeEach(() => {
|
|
detector = new NamingConventionDetector()
|
|
})
|
|
|
|
describe("Excluded Files", () => {
|
|
it("should NOT detect violations for index.ts", () => {
|
|
const code = `export class User {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"index.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/index.ts",
|
|
)
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
|
|
it("should NOT detect violations for BaseUseCase.ts", () => {
|
|
const code = `export class BaseUseCase {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"BaseUseCase.ts",
|
|
LAYERS.APPLICATION,
|
|
"src/application/use-cases/BaseUseCase.ts",
|
|
)
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
|
|
it("should NOT detect violations for empty content", () => {
|
|
const result = detector.detectViolations(
|
|
"",
|
|
"User.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/User.ts",
|
|
)
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
describe("Domain Layer - Classes", () => {
|
|
it("should NOT detect violations for valid entity class names", () => {
|
|
const validClasses = ["User", "Order", "Product", "Email", "ProjectPath"]
|
|
|
|
validClasses.forEach((className) => {
|
|
const code = `export class ${className} {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
`${className}.ts`,
|
|
LAYERS.DOMAIN,
|
|
`src/domain/entities/${className}.ts`,
|
|
)
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
it("should detect violations for lowercase class names", () => {
|
|
const code = `export class user {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"user.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/entities/user.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0].violationType).toBe(NAMING_VIOLATION_TYPES.WRONG_CASE)
|
|
expect(result[0].layer).toBe(LAYERS.DOMAIN)
|
|
})
|
|
|
|
it("should detect violations for camelCase class names", () => {
|
|
const code = `export class userProfile {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"userProfile.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/entities/userProfile.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0].violationType).toBe(NAMING_VIOLATION_TYPES.WRONG_CASE)
|
|
})
|
|
|
|
it("should NOT detect violations for valid service class names", () => {
|
|
const validNames = ["UserService", "EmailService", "PaymentService"]
|
|
|
|
validNames.forEach((className) => {
|
|
const code = `export class ${className} {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
`${className}.ts`,
|
|
LAYERS.DOMAIN,
|
|
`src/domain/services/${className}.ts`,
|
|
)
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
it("should detect violations for lowercase service class names", () => {
|
|
const code = `export class userService {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"userService.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/services/userService.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0].violationType).toBe(NAMING_VIOLATION_TYPES.WRONG_CASE)
|
|
})
|
|
})
|
|
|
|
describe("Domain Layer - Interfaces", () => {
|
|
it("should NOT detect violations for valid repository interface names", () => {
|
|
const validNames = ["IUserRepository", "IOrderRepository", "IProductRepository"]
|
|
|
|
validNames.forEach((interfaceName) => {
|
|
const code = `export interface ${interfaceName} {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
`${interfaceName}.ts`,
|
|
LAYERS.DOMAIN,
|
|
`src/domain/repositories/${interfaceName}.ts`,
|
|
)
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
it("should detect violations for repository interfaces without I prefix", () => {
|
|
const code = `export interface UserRepository {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"UserRepository.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/repositories/UserRepository.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0].violationType).toBe(NAMING_VIOLATION_TYPES.WRONG_PREFIX)
|
|
})
|
|
|
|
it("should detect violations for lowercase interface names", () => {
|
|
const code = `export interface iUserRepository {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"iUserRepository.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/repositories/iUserRepository.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0].violationType).toBe(NAMING_VIOLATION_TYPES.WRONG_CASE)
|
|
})
|
|
})
|
|
|
|
describe("Application Layer - Classes", () => {
|
|
it("should NOT detect violations for valid use case class names", () => {
|
|
const validNames = [
|
|
"CreateUser",
|
|
"UpdateProfile",
|
|
"DeleteOrder",
|
|
"GetUser",
|
|
"FindProducts",
|
|
"AnalyzeProject",
|
|
"ValidateEmail",
|
|
"GenerateReport",
|
|
]
|
|
|
|
validNames.forEach((className) => {
|
|
const code = `export class ${className} {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
`${className}.ts`,
|
|
LAYERS.APPLICATION,
|
|
`src/application/use-cases/${className}.ts`,
|
|
)
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
it("should detect violations for use case classes starting with lowercase", () => {
|
|
const code = `export class createUser {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"createUser.ts",
|
|
LAYERS.APPLICATION,
|
|
"src/application/use-cases/createUser.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0].violationType).toBe(NAMING_VIOLATION_TYPES.WRONG_VERB_NOUN)
|
|
})
|
|
|
|
it("should NOT detect violations for valid DTO class names", () => {
|
|
const validNames = [
|
|
"UserDto",
|
|
"CreateUserRequest",
|
|
"UserResponseDto",
|
|
"UpdateProfileRequest",
|
|
"OrderResponse",
|
|
]
|
|
|
|
validNames.forEach((className) => {
|
|
const code = `export class ${className} {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
`${className}.ts`,
|
|
LAYERS.APPLICATION,
|
|
`src/application/dtos/${className}.ts`,
|
|
)
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
it("should detect violations for lowercase DTO class names", () => {
|
|
const code = `export class userDto {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"userDto.ts",
|
|
LAYERS.APPLICATION,
|
|
"src/application/dtos/userDto.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0].violationType).toBe(NAMING_VIOLATION_TYPES.WRONG_SUFFIX)
|
|
})
|
|
|
|
it("should NOT detect violations for valid mapper class names", () => {
|
|
const validNames = ["UserMapper", "OrderMapper", "ProductMapper"]
|
|
|
|
validNames.forEach((className) => {
|
|
const code = `export class ${className} {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
`${className}.ts`,
|
|
LAYERS.APPLICATION,
|
|
`src/application/mappers/${className}.ts`,
|
|
)
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
it("should detect violations for lowercase mapper class names", () => {
|
|
const code = `export class userMapper {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"userMapper.ts",
|
|
LAYERS.APPLICATION,
|
|
"src/application/mappers/userMapper.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0].violationType).toBe(NAMING_VIOLATION_TYPES.WRONG_SUFFIX)
|
|
})
|
|
})
|
|
|
|
describe("Infrastructure Layer - Classes", () => {
|
|
it("should NOT detect violations for valid controller class names", () => {
|
|
const validNames = ["UserController", "OrderController", "ProductController"]
|
|
|
|
validNames.forEach((className) => {
|
|
const code = `export class ${className} {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
`${className}.ts`,
|
|
LAYERS.INFRASTRUCTURE,
|
|
`src/infrastructure/controllers/${className}.ts`,
|
|
)
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
it("should detect violations for lowercase controller class names", () => {
|
|
const code = `export class userController {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"userController.ts",
|
|
LAYERS.INFRASTRUCTURE,
|
|
"src/infrastructure/controllers/userController.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0].violationType).toBe(NAMING_VIOLATION_TYPES.WRONG_SUFFIX)
|
|
})
|
|
|
|
it("should NOT detect violations for valid repository implementation class names", () => {
|
|
const validNames = [
|
|
"UserRepository",
|
|
"PrismaUserRepository",
|
|
"MongoUserRepository",
|
|
"InMemoryUserRepository",
|
|
]
|
|
|
|
validNames.forEach((className) => {
|
|
const code = `export class ${className} {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
`${className}.ts`,
|
|
LAYERS.INFRASTRUCTURE,
|
|
`src/infrastructure/repositories/${className}.ts`,
|
|
)
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
it("should detect violations for lowercase repository class names", () => {
|
|
const code = `export class userRepository {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"userRepository.ts",
|
|
LAYERS.INFRASTRUCTURE,
|
|
"src/infrastructure/repositories/userRepository.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0].violationType).toBe(NAMING_VIOLATION_TYPES.WRONG_SUFFIX)
|
|
})
|
|
|
|
it("should NOT detect violations for valid service class names", () => {
|
|
const validNames = [
|
|
"EmailService",
|
|
"S3StorageAdapter",
|
|
"PaymentService",
|
|
"LoggerAdapter",
|
|
]
|
|
|
|
validNames.forEach((className) => {
|
|
const code = `export class ${className} {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
`${className}.ts`,
|
|
LAYERS.INFRASTRUCTURE,
|
|
`src/infrastructure/services/${className}.ts`,
|
|
)
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
it("should detect violations for lowercase service class names", () => {
|
|
const code = `export class emailService {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"emailService.ts",
|
|
LAYERS.INFRASTRUCTURE,
|
|
"src/infrastructure/services/emailService.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0].violationType).toBe(NAMING_VIOLATION_TYPES.WRONG_SUFFIX)
|
|
})
|
|
})
|
|
|
|
describe("Function and Method Names", () => {
|
|
it("should NOT detect violations for camelCase function names", () => {
|
|
const code = `
|
|
export class User {
|
|
getUserById() {}
|
|
createOrder() {}
|
|
validateEmail() {}
|
|
}
|
|
`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"User.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/User.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
|
|
it("should detect violations for PascalCase method names", () => {
|
|
const code = `
|
|
export class User {
|
|
GetUserById() {}
|
|
}
|
|
`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"User.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/User.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0].violationType).toBe(NAMING_VIOLATION_TYPES.WRONG_CASE)
|
|
})
|
|
|
|
it("should detect violations for snake_case method names", () => {
|
|
const code = `
|
|
export class User {
|
|
get_user_by_id() {}
|
|
}
|
|
`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"User.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/User.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0].violationType).toBe(NAMING_VIOLATION_TYPES.WRONG_CASE)
|
|
})
|
|
|
|
it("should NOT detect violations for constructor", () => {
|
|
const code = `
|
|
export class User {
|
|
constructor() {}
|
|
}
|
|
`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"User.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/User.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
})
|
|
|
|
describe("Variable and Constant Names", () => {
|
|
it("should NOT detect violations for camelCase variables", () => {
|
|
const code = `
|
|
export class User {
|
|
getUserById() {
|
|
const userId = "123"
|
|
const userName = "John"
|
|
return { userId, userName }
|
|
}
|
|
}
|
|
`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"User.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/User.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
|
|
it("should NOT detect violations for UPPER_SNAKE_CASE constants", () => {
|
|
const code = `
|
|
export const MAX_RETRIES = 3
|
|
export const API_URL = "https://api.example.com"
|
|
export const DEFAULT_TIMEOUT = 5000
|
|
`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"constants.ts",
|
|
LAYERS.SHARED,
|
|
"src/shared/constants.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
|
|
it("should NOT detect violations for static readonly UPPER_SNAKE_CASE class constants", () => {
|
|
const code = `
|
|
export class ValuePatternMatcher {
|
|
private static readonly EMAIL_PATTERN = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
|
private static readonly IP_V4_PATTERN = /^(\\d{1,3}\\.){3}\\d{1,3}$/
|
|
private static readonly API_KEY_PATTERN = /^(sk_|pk_|api_|key_)[a-zA-Z0-9_-]{20,}$/
|
|
}
|
|
`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"ValuePatternMatcher.ts",
|
|
LAYERS.INFRASTRUCTURE,
|
|
"src/infrastructure/ValuePatternMatcher.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
|
|
it("should NOT detect violations for readonly UPPER_SNAKE_CASE class constants", () => {
|
|
const code = `
|
|
export class AstConfigObjectAnalyzer {
|
|
private readonly MIN_HARDCODED_VALUES = 2
|
|
private readonly MAX_COMPLEXITY = 15
|
|
}
|
|
`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"AstConfigObjectAnalyzer.ts",
|
|
LAYERS.INFRASTRUCTURE,
|
|
"src/infrastructure/AstConfigObjectAnalyzer.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
|
|
it("should detect violations for PascalCase variables", () => {
|
|
const code = `
|
|
export class User {
|
|
getUserById() {
|
|
const UserId = "123"
|
|
return UserId
|
|
}
|
|
}
|
|
`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"User.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/User.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0].violationType).toBe(NAMING_VIOLATION_TYPES.WRONG_CASE)
|
|
})
|
|
})
|
|
|
|
describe("Shared Layer", () => {
|
|
it("should NOT detect violations for any file in shared layer", () => {
|
|
const code = `
|
|
export class Guards {}
|
|
export class Result {}
|
|
export function helper() {}
|
|
`
|
|
const fileNames = [
|
|
"helpers.ts",
|
|
"utils.ts",
|
|
"constants.ts",
|
|
"types.ts",
|
|
"Guards.ts",
|
|
"Result.ts",
|
|
]
|
|
|
|
fileNames.forEach((fileName) => {
|
|
const result = detector.detectViolations(
|
|
code,
|
|
fileName,
|
|
LAYERS.SHARED,
|
|
`src/shared/${fileName}`,
|
|
)
|
|
expect(result.length).toBeLessThanOrEqual(code.length)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("Edge Cases", () => {
|
|
it("should return empty array when no layer is provided", () => {
|
|
const code = `export class SomeClass {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"SomeFile.ts",
|
|
undefined,
|
|
"src/SomeFile.ts",
|
|
)
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
|
|
it("should return empty array for unknown layer", () => {
|
|
const code = `export class SomeClass {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"SomeFile.ts",
|
|
"unknown-layer",
|
|
"src/unknown/SomeFile.ts",
|
|
)
|
|
expect(result.length).toBeGreaterThanOrEqual(0)
|
|
})
|
|
|
|
it("should handle classes with numbers in name", () => {
|
|
const code = `export class User2Factor {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"User2Factor.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/entities/User2Factor.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(0)
|
|
})
|
|
|
|
it("should provide helpful suggestions", () => {
|
|
const code = `export class userDto {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"userDto.ts",
|
|
LAYERS.APPLICATION,
|
|
"src/application/dtos/userDto.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0].suggestion).toBeDefined()
|
|
})
|
|
|
|
it("should include file path in violation", () => {
|
|
const code = `export class user {}`
|
|
const filePath = "src/domain/user.ts"
|
|
const result = detector.detectViolations(code, "user.ts", LAYERS.DOMAIN, filePath)
|
|
|
|
expect(result).toHaveLength(1)
|
|
expect(result[0].filePath).toContain(filePath)
|
|
})
|
|
})
|
|
|
|
describe("Complex Scenarios", () => {
|
|
it("should detect multiple violations in one file", () => {
|
|
const code = `
|
|
export class userService {
|
|
GetUser() {
|
|
const UserId = "123"
|
|
return UserId
|
|
}
|
|
}
|
|
`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"userService.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/services/userService.ts",
|
|
)
|
|
|
|
expect(result.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
it("should handle multiple classes in one file", () => {
|
|
const code = `
|
|
export class User {}
|
|
export class UserService {}
|
|
`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"User.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/entities/User.ts",
|
|
)
|
|
|
|
expect(result.length).toBeGreaterThanOrEqual(0)
|
|
})
|
|
|
|
it("should handle interfaces and classes together", () => {
|
|
const code = `
|
|
export interface IUserRepository {}
|
|
export class UserService {}
|
|
`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"UserRepository.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/repositories/UserRepository.ts",
|
|
)
|
|
|
|
expect(result.length).toBeGreaterThanOrEqual(0)
|
|
})
|
|
})
|
|
|
|
describe("getMessage()", () => {
|
|
it("should return descriptive error messages", () => {
|
|
const code = `export class userService {}`
|
|
const result = detector.detectViolations(
|
|
code,
|
|
"userService.ts",
|
|
LAYERS.DOMAIN,
|
|
"src/domain/userService.ts",
|
|
)
|
|
|
|
expect(result).toHaveLength(1)
|
|
const message = result[0].getMessage()
|
|
expect(message).toBeTruthy()
|
|
expect(typeof message).toBe("string")
|
|
expect(message.length).toBeGreaterThan(0)
|
|
})
|
|
})
|
|
})
|