test: improve test coverage for domain files from 46-58% to 92-100%

- Add 31 tests for SourceFile.ts (46% → 100%)
- Add 31 tests for ProjectPath.ts (50% → 100%)
- Add 18 tests for ValueObject.ts (25% → 100%)
- Add 32 tests for RepositoryViolation.ts (58% → 92.68%)
- Total test count: 345 → 457 tests (all passing)
- Overall coverage: 95.4% statements, 86.25% branches, 96.68% functions
- Update version to 0.7.7
- Update ROADMAP.md and CHANGELOG.md
This commit is contained in:
imfozilbek
2025-11-25 16:50:00 +05:00
parent 0da25d9046
commit 0b9b8564bf
7 changed files with 1394 additions and 14 deletions

View File

@@ -0,0 +1,329 @@
import { describe, it, expect } from "vitest"
import { SourceFile } from "../../../src/domain/entities/SourceFile"
import { ProjectPath } from "../../../src/domain/value-objects/ProjectPath"
import { LAYERS } from "../../../src/shared/constants/rules"
describe("SourceFile", () => {
describe("constructor", () => {
it("should create a SourceFile instance with all properties", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const content = "class User {}"
const imports = ["./BaseEntity"]
const exports = ["User"]
const id = "test-id"
const sourceFile = new SourceFile(path, content, imports, exports, id)
expect(sourceFile.path).toBe(path)
expect(sourceFile.content).toBe(content)
expect(sourceFile.imports).toEqual(imports)
expect(sourceFile.exports).toEqual(exports)
expect(sourceFile.id).toBe(id)
})
it("should create a SourceFile with empty imports and exports by default", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const content = "class User {}"
const sourceFile = new SourceFile(path, content)
expect(sourceFile.imports).toEqual([])
expect(sourceFile.exports).toEqual([])
})
it("should generate an id if not provided", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const content = "class User {}"
const sourceFile = new SourceFile(path, content)
expect(sourceFile.id).toBeDefined()
expect(typeof sourceFile.id).toBe("string")
expect(sourceFile.id.length).toBeGreaterThan(0)
})
})
describe("layer detection", () => {
it("should detect domain layer from path", () => {
const path = ProjectPath.create("/project/src/domain/entities/User.ts", "/project")
const sourceFile = new SourceFile(path, "")
expect(sourceFile.layer).toBe(LAYERS.DOMAIN)
})
it("should detect application layer from path", () => {
const path = ProjectPath.create(
"/project/src/application/use-cases/CreateUser.ts",
"/project",
)
const sourceFile = new SourceFile(path, "")
expect(sourceFile.layer).toBe(LAYERS.APPLICATION)
})
it("should detect infrastructure layer from path", () => {
const path = ProjectPath.create(
"/project/src/infrastructure/database/UserRepository.ts",
"/project",
)
const sourceFile = new SourceFile(path, "")
expect(sourceFile.layer).toBe(LAYERS.INFRASTRUCTURE)
})
it("should detect shared layer from path", () => {
const path = ProjectPath.create("/project/src/shared/utils/helpers.ts", "/project")
const sourceFile = new SourceFile(path, "")
expect(sourceFile.layer).toBe(LAYERS.SHARED)
})
it("should return undefined for unknown layer", () => {
const path = ProjectPath.create("/project/src/unknown/Test.ts", "/project")
const sourceFile = new SourceFile(path, "")
expect(sourceFile.layer).toBeUndefined()
})
it("should handle uppercase layer names in path", () => {
const path = ProjectPath.create("/project/src/DOMAIN/User.ts", "/project")
const sourceFile = new SourceFile(path, "")
expect(sourceFile.layer).toBe(LAYERS.DOMAIN)
})
it("should handle mixed case layer names in path", () => {
const path = ProjectPath.create("/project/src/Application/UseCase.ts", "/project")
const sourceFile = new SourceFile(path, "")
expect(sourceFile.layer).toBe(LAYERS.APPLICATION)
})
})
describe("path getter", () => {
it("should return the project path", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const sourceFile = new SourceFile(path, "")
expect(sourceFile.path).toBe(path)
})
})
describe("content getter", () => {
it("should return the file content", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const content = "class User { constructor(public name: string) {} }"
const sourceFile = new SourceFile(path, content)
expect(sourceFile.content).toBe(content)
})
})
describe("imports getter", () => {
it("should return a copy of imports array", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const imports = ["./BaseEntity", "./ValueObject"]
const sourceFile = new SourceFile(path, "", imports)
const returnedImports = sourceFile.imports
expect(returnedImports).toEqual(imports)
expect(returnedImports).not.toBe(imports)
})
it("should not allow mutations of internal imports array", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const imports = ["./BaseEntity"]
const sourceFile = new SourceFile(path, "", imports)
const returnedImports = sourceFile.imports
returnedImports.push("./NewImport")
expect(sourceFile.imports).toEqual(["./BaseEntity"])
})
})
describe("exports getter", () => {
it("should return a copy of exports array", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const exports = ["User", "UserProps"]
const sourceFile = new SourceFile(path, "", [], exports)
const returnedExports = sourceFile.exports
expect(returnedExports).toEqual(exports)
expect(returnedExports).not.toBe(exports)
})
it("should not allow mutations of internal exports array", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const exports = ["User"]
const sourceFile = new SourceFile(path, "", [], exports)
const returnedExports = sourceFile.exports
returnedExports.push("NewExport")
expect(sourceFile.exports).toEqual(["User"])
})
})
describe("addImport", () => {
it("should add a new import to the list", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const sourceFile = new SourceFile(path, "")
sourceFile.addImport("./BaseEntity")
expect(sourceFile.imports).toEqual(["./BaseEntity"])
})
it("should not add duplicate imports", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const sourceFile = new SourceFile(path, "", ["./BaseEntity"])
sourceFile.addImport("./BaseEntity")
expect(sourceFile.imports).toEqual(["./BaseEntity"])
})
it("should update updatedAt timestamp when adding new import", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const sourceFile = new SourceFile(path, "")
const originalUpdatedAt = sourceFile.updatedAt
setTimeout(() => {
sourceFile.addImport("./BaseEntity")
expect(sourceFile.updatedAt.getTime()).toBeGreaterThanOrEqual(
originalUpdatedAt.getTime(),
)
}, 10)
})
it("should not update timestamp when adding duplicate import", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const sourceFile = new SourceFile(path, "", ["./BaseEntity"])
const originalUpdatedAt = sourceFile.updatedAt
setTimeout(() => {
sourceFile.addImport("./BaseEntity")
expect(sourceFile.updatedAt).toBe(originalUpdatedAt)
}, 10)
})
it("should add multiple different imports", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const sourceFile = new SourceFile(path, "")
sourceFile.addImport("./BaseEntity")
sourceFile.addImport("./ValueObject")
sourceFile.addImport("./DomainEvent")
expect(sourceFile.imports).toEqual(["./BaseEntity", "./ValueObject", "./DomainEvent"])
})
})
describe("addExport", () => {
it("should add a new export to the list", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const sourceFile = new SourceFile(path, "")
sourceFile.addExport("User")
expect(sourceFile.exports).toEqual(["User"])
})
it("should not add duplicate exports", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const sourceFile = new SourceFile(path, "", [], ["User"])
sourceFile.addExport("User")
expect(sourceFile.exports).toEqual(["User"])
})
it("should update updatedAt timestamp when adding new export", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const sourceFile = new SourceFile(path, "")
const originalUpdatedAt = sourceFile.updatedAt
setTimeout(() => {
sourceFile.addExport("User")
expect(sourceFile.updatedAt.getTime()).toBeGreaterThanOrEqual(
originalUpdatedAt.getTime(),
)
}, 10)
})
it("should not update timestamp when adding duplicate export", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const sourceFile = new SourceFile(path, "", [], ["User"])
const originalUpdatedAt = sourceFile.updatedAt
setTimeout(() => {
sourceFile.addExport("User")
expect(sourceFile.updatedAt).toBe(originalUpdatedAt)
}, 10)
})
it("should add multiple different exports", () => {
const path = ProjectPath.create("/project/src/domain/User.ts", "/project")
const sourceFile = new SourceFile(path, "")
sourceFile.addExport("User")
sourceFile.addExport("UserProps")
sourceFile.addExport("UserFactory")
expect(sourceFile.exports).toEqual(["User", "UserProps", "UserFactory"])
})
})
describe("importsFrom", () => {
it("should return true if imports contain the specified layer", () => {
const path = ProjectPath.create("/project/src/application/User.ts", "/project")
const imports = ["../../domain/entities/User", "../use-cases/CreateUser"]
const sourceFile = new SourceFile(path, "", imports)
expect(sourceFile.importsFrom("domain")).toBe(true)
})
it("should return false if imports do not contain the specified layer", () => {
const path = ProjectPath.create("/project/src/application/User.ts", "/project")
const imports = ["../use-cases/CreateUser", "../dtos/UserDto"]
const sourceFile = new SourceFile(path, "", imports)
expect(sourceFile.importsFrom("domain")).toBe(false)
})
it("should be case-insensitive", () => {
const path = ProjectPath.create("/project/src/application/User.ts", "/project")
const imports = ["../../DOMAIN/entities/User"]
const sourceFile = new SourceFile(path, "", imports)
expect(sourceFile.importsFrom("domain")).toBe(true)
})
it("should return false for empty imports", () => {
const path = ProjectPath.create("/project/src/application/User.ts", "/project")
const sourceFile = new SourceFile(path, "")
expect(sourceFile.importsFrom("domain")).toBe(false)
})
it("should handle partial matches in import paths", () => {
const path = ProjectPath.create("/project/src/application/User.ts", "/project")
const imports = ["../../infrastructure/database/UserRepository"]
const sourceFile = new SourceFile(path, "", imports)
expect(sourceFile.importsFrom("infrastructure")).toBe(true)
expect(sourceFile.importsFrom("domain")).toBe(false)
})
})
})