/** * lib/app-classification — App/Module/Section folder + namespace builders. * * These builders are the single source of truth shared by scaffold-entity, * scaffold-business, scaffold-controller, scaffold-screen-controller and the * test scaffolders. The contract under test: * - the .NET root (`.Domain`, `.Api`, …) is never altered — only the * suffix gains `.` (+ `
` where section-grained); * - DOMAIN stays FLAT in namespace while its FOLDER gains `/` * (cross-module FK configs resolve every entity via one flat `using`); * - legacy builders point at the pre-classification location for the relocate. */ import { describe, it, expect } from 'vitest' import { appSegment, moduleSegment, sectionSegment, domainEntitiesNs, configurationsNs, applicationNs, servicesNs, controllersNs, screenControllersNs, permissionsNs, domainEntitiesDir, configurationsDir, applicationDir, servicesDir, controllersDir, screenControllersDir, permissionsDir, testsModuleDir, acceptanceTestsDir, featureDir, frontendTestDir, legacyDomainEntitiesDir, legacyConfigurationsDir, legacyApplicationDir, legacyServicesDir, legacyControllersDir, legacyScreenControllersDir, legacyPermissionsDir, legacyTestsModuleDir, legacyFeatureDir, legacyFrontendTestDir, } from '../app-classification.js' const ns = 'Acme' const app = 'crm' const mod = 'pipeline' const sec = 'opportunites' describe('segments', () => { it('PascalCases the business app, module and section', () => { expect(appSegment(app)).toBe('Crm') expect(moduleSegment(mod)).toBe('Pipeline') expect(sectionSegment(sec)).toBe('Opportunites') }) it('handles multi-word kebab codes (fixes the historical capitalize() split)', () => { expect(appSegment('human-resources')).toBe('HumanResources') expect(moduleSegment('order-management')).toBe('OrderManagement') expect(sectionSegment('types-affaire')).toBe('TypesAffaire') }) }) describe('Domain stays FLAT in namespace, classified in folder', () => { it('entities/configs keep the flat root namespace', () => { expect(domainEntitiesNs(ns)).toBe('Acme.Domain.Entities') expect(configurationsNs(ns)).toBe('Acme.Infrastructure.Persistence.Configurations') }) it('entities/configs FOLDERS gain /', () => { expect(domainEntitiesDir(ns, app, mod)).toBe('src/Acme.Domain/Entities/Crm/Pipeline') expect(configurationsDir(ns, app, mod)).toBe( 'src/Acme.Infrastructure/Persistence/Configurations/Crm/Pipeline', ) }) }) describe('Application / Services / Api carry . in BOTH namespace and folder', () => { it('application + services', () => { expect(applicationNs(ns, app, mod)).toBe('Acme.Application.Crm.Pipeline') expect(applicationDir(ns, app, mod)).toBe('src/Acme.Application/Crm/Pipeline') expect(servicesNs(ns, app, mod)).toBe('Acme.Infrastructure.Services.Crm.Pipeline') expect(servicesDir(ns, app, mod)).toBe('src/Acme.Infrastructure/Services/Crm/Pipeline') }) it('integration controller (entity-grained) — no section', () => { expect(controllersNs(ns, app, mod)).toBe('Acme.Api.Controllers.Crm.Pipeline') expect(controllersDir(ns, app, mod)).toBe('src/Acme.Api/Controllers/Crm/Pipeline') }) it('screen controller (section-grained) — section is a real folder + namespace', () => { expect(screenControllersNs(ns, app, mod, sec)).toBe('Acme.Api.Controllers.Crm.Pipeline.Opportunites') expect(screenControllersDir(ns, app, mod, sec)).toBe('src/Acme.Api/Controllers/Crm/Pipeline/Opportunites') }) it('permissions — namespace carries app/module, section stays the inner class', () => { expect(permissionsNs(ns, app, mod)).toBe('Acme.Api.Permissions.Crm.Pipeline') expect(permissionsDir(ns, app, mod)).toBe('src/Acme.Api/Permissions/Crm/Pipeline') }) }) describe('tests + frontend', () => { it('backend tests mirror the code: Tests//(/
)', () => { expect(testsModuleDir(app, mod)).toBe('Tests/Crm/Pipeline') expect(acceptanceTestsDir(app, mod, sec)).toBe('Tests/Crm/Pipeline/Opportunites') }) it('frontend features + tests are lowercase app/module/entity', () => { expect(featureDir(app, mod, 'opportunity')).toBe('src/features/crm/pipeline/opportunity') expect(frontendTestDir(app, mod, 'opportunity')).toBe('tests/crm/pipeline/opportunity') }) }) describe('legacy locations (relocate targets)', () => { it('point at the pre-classification folders', () => { expect(legacyDomainEntitiesDir(ns)).toBe('src/Acme.Domain/Entities') expect(legacyConfigurationsDir(ns)).toBe('src/Acme.Infrastructure/Persistence/Configurations') expect(legacyApplicationDir(ns, mod)).toBe('src/Acme.Application/Pipeline') expect(legacyServicesDir(ns, mod)).toBe('src/Acme.Infrastructure/Services/Pipeline') expect(legacyControllersDir(ns, mod)).toBe('src/Acme.Api/Controllers/Pipeline') expect(legacyScreenControllersDir(ns, mod)).toBe('src/Acme.Api/Controllers/Pipeline/Screens') expect(legacyPermissionsDir(ns, mod)).toBe('src/Acme.Api/Permissions/Pipeline') expect(legacyTestsModuleDir(mod)).toBe('Tests/Pipeline') expect(legacyFeatureDir(mod, 'opportunity')).toBe('src/features/pipeline/opportunity') expect(legacyFrontendTestDir(mod, 'opportunity')).toBe('tests/pipeline/opportunity') }) it('a classified path never equals its legacy path (relocate always moves)', () => { expect(applicationDir(ns, app, mod)).not.toBe(legacyApplicationDir(ns, mod)) expect(controllersDir(ns, app, mod)).not.toBe(legacyControllersDir(ns, mod)) }) })