import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; import path from 'node:path'; import test from 'node:test'; const moduleRoot = path.resolve(import.meta.dirname, '..'); const analyzer = path.join(moduleRoot, 'scripts', 'analyze-lastleg-dependencies.mjs'); const contract = path.join(moduleRoot, 'src', 'test', 'resources', 'architecture', 'lastleg-slice-dependencies.json'); const vehiclePrefix = 'com.baiyue.lastleg.masterdata.vehicle'; function analyze(sourceSet = 'main') { return JSON.parse(execFileSync(process.execPath, [analyzer, '--source-set', sourceSet, '--format', 'json'], { cwd: moduleRoot, encoding: 'utf8', })); } test('current source does not add slice dependencies beyond the Lastleg baseline', () => { execFileSync(process.execPath, [analyzer, '--source-set', 'main', '--format', 'dot', '--check', contract], { cwd: moduleRoot, encoding: 'utf8', }); }); test('main, test, and combined source sets expose package dependency graphs', () => { for (const sourceSet of ['main', 'test', 'all']) { const dot = execFileSync(process.execPath, [analyzer, '--source-set', sourceSet, '--format', 'dot', '--graph', 'package'], { cwd: moduleRoot, encoding: 'utf8', }); assert.match(dot, /^digraph lastleg_packages \{/); assert.match(dot, /com\.baiyue\.lastleg/); } }); test('vehicle master data remains the reviewed low-coupling first-slice candidate', () => { const report = analyze(); const classes = report.classes.filter(entry => entry.fqcn.startsWith(vehiclePrefix)); assert.deepEqual(classes.map(entry => entry.fqcn).sort(), [ 'com.baiyue.lastleg.masterdata.vehicle.adapter.in.web.PlatformVehicleController', 'com.baiyue.lastleg.masterdata.vehicle.adapter.out.persistence.VehicleMapper', 'com.baiyue.lastleg.masterdata.vehicle.adapter.out.persistence.VehiclePersistenceAdapter', 'com.baiyue.lastleg.masterdata.vehicle.adapter.out.persistence.VehicleRecord', 'com.baiyue.lastleg.masterdata.vehicle.application.VehicleApplicationService', 'com.baiyue.lastleg.masterdata.vehicle.application.model.VehicleModels', 'com.baiyue.lastleg.masterdata.vehicle.application.port.out.VehicleRepository', ]); const outgoing = report.classDependencies.filter(edge => edge.source.startsWith(vehiclePrefix) && !edge.target.startsWith(vehiclePrefix)); const incoming = report.classDependencies.filter(edge => edge.target.startsWith(vehiclePrefix) && !edge.source.startsWith(vehiclePrefix)); assert.deepEqual(outgoing, [{ source: 'com.baiyue.lastleg.masterdata.vehicle.adapter.in.web.PlatformVehicleController', target: 'com.baiyue.lastleg.security.OverseasPermissions', }]); assert.deepEqual(incoming, []); assert.equal(report.sliceSccs.some(component => component.includes('overseas-master-data')), false); assert.equal(classes.some(entry => entry.kind === 'cross-service-adapter'), false); assert.equal(classes.some(entry => entry.imports.some(imported => /RestClient|\.client\./.test(imported))), false); }); test('vehicle application only depends inward and stays free of adapter frameworks', () => { const report = analyze(); const prefix = `${vehiclePrefix}.application`; const applicationClasses = report.classes.filter(entry => entry.packageName.startsWith(prefix)); assert.equal(applicationClasses.length, 3); for (const entry of applicationClasses) { assert.equal(entry.imports.some(imported => imported.startsWith('org.springframework.web')), false); assert.equal(entry.imports.some(imported => imported.startsWith('org.apache.ibatis')), false); assert.equal(entry.imports.some(imported => imported.startsWith('com.baomidou.mybatisplus')), false); assert.equal(entry.imports.some(imported => /RestClient|\.client\./.test(imported)), false); } const outwardDependencies = report.classDependencies.filter(edge => edge.source.startsWith(prefix) && edge.target.startsWith('com.baiyue.lastleg.') && !edge.target.startsWith(prefix)); assert.deepEqual(outwardDependencies, []); }); test('security projection stays isolated from Lastleg business slices', () => { const report = analyze(); const projectionClasses = report.classes.filter(entry => entry.slice === 'security-projection'); const projectionNames = new Set(projectionClasses.map(entry => entry.fqcn)); assert.deepEqual(projectionClasses.map(entry => entry.fqcn).sort(), [ 'com.baiyue.lastleg.security.controller.LastlegSecurityController', 'com.baiyue.lastleg.security.dto.LastlegSecurityDtos', 'com.baiyue.lastleg.security.service.LastlegSecurityProjectionRedisMirror', 'com.baiyue.lastleg.security.service.LastlegSecurityProjectionService', 'com.baiyue.lastleg.security.service.LastlegSecurityValidationService', 'com.baiyue.lastleg.security.service.SecurityProjectionMonotonicity', ]); assert.deepEqual(report.sliceEdges.filter(edge => edge.source === 'security-projection'), [ { source: 'security-projection', target: 'shared-technical', count: 1 }, ]); assert.deepEqual(report.sliceEdges.filter(edge => edge.target === 'security-projection'), []); assert.equal(report.sliceSccs.some(component => component.includes('security-projection')), false); const businessConsumers = report.classDependencies.filter(edge => projectionNames.has(edge.target) && !projectionNames.has(edge.source)); assert.deepEqual(businessConsumers, []); });