import test from 'node:test' import assert from 'node:assert/strict' import { readFileSync, mkdtempSync, writeFileSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { execFileSync } from 'node:child_process' import { planTests, verifyReports } from '../deploy/scripts/ci-test-scope.mjs' const module = 'lastleg-service' function java(type, body = '', set = 'main') { const name = type.split('.').at(-1) const pkg = type.slice(0, -name.length - 1) return { path: `${module}/src/${set}/java/com/baiyue/lastleg/${type.replaceAll('.', '/')}.java`, source: `package com.baiyue.lastleg.${pkg}; class ${name} { ${body} }` } } const source = java('dispatch.DispatchService') const fixture = java('dispatch.Fixture', 'DispatchService service;', 'test') const direct = java('dispatch.DispatchTest', 'Fixture fixture; @Test void check() {}', 'test') const consumer = java('delivery.DeliveryService', 'DispatchService service;') const indirect = java('delivery.DeliveryTest', 'DeliveryService service; @Test void check() {}', 'test') const unrelated = java('selfoperated.AnalyticsTest', '@Test void check() {}', 'test') const security = java('workflow.OrderSecurityTest', '@Test void check() {}', 'test') const sources = [source, fixture, direct, consumer, indirect, unrelated, security] const plan = (options = {}) => planTests({ modules: [module], baseline: '1234567', changes: [source.path], sources, ...options }) test('changed Java selects transitive callers and fixture consumers without unrelated domains', () => { const result = plan() assert.equal(result.scope, 'AFFECTED') assert.deepEqual(result.tests, [ 'com.baiyue.lastleg.delivery.DeliveryTest', 'com.baiyue.lastleg.dispatch.DispatchTest', 'com.baiyue.lastleg.workflow.OrderSecurityTest' ]) }) test('implementation changes reach callers of interfaces, including duplicate simple names', () => { const contract = java('dispatch.Port') const implementation = java('dispatch.Adapter', 'implements Port') const portCaller = java('fleet.PortCaller', 'Port port;') const portTest = java('fleet.PortCallerTest', 'PortCaller service; @Test void check() {}', 'test') const result = plan({ changes: [implementation.path], sources: [...sources, contract, implementation, portCaller, portTest] }) assert.ok(result.tests.includes('com.baiyue.lastleg.fleet.PortCallerTest')) }) test('SQL tables expand to other mappers and their tests', () => { const mapper = java('dispatch.SourceMapper', 'String sql = "UPDATE ll_dispatch_source SET version=2";') const sibling = java('delivery.DeliveryMapper', 'String sql = "UPDATE ll_dispatch_source SET version=1";') const check = java('delivery.DeliveryMapperTest', 'DeliveryMapper mapper; @Test void check() {}', 'test') const result = plan({ changes: [mapper.path], sources: [...sources, mapper, sibling, check] }) assert.ok(result.tests.includes('com.baiyue.lastleg.delivery.DeliveryMapperTest')) }) test('unchanged writes beside a changed SELECT do not pull in unrelated table consumers', () => { const mapper = java('dispatch.QueryMapper', '@Update("UPDATE ll_shared SET value=1") void write(); @Select("SELECT value FROM ll_source") void read();') mapper.previousSource = mapper.source.replace('SELECT value', 'SELECT old_value') const check = java('dispatch.QueryMapperTest', 'QueryMapper mapper; @Test void check() {}', 'test') const unrelatedTable = java('selfoperated.SharedTableTest', 'String query = "SELECT * FROM ll_shared"; @Test void check() {}', 'test') const result = plan({ changes: [mapper.path], sources: [...sources, mapper, check, unrelatedTable] }) assert.ok(result.tests.includes('com.baiyue.lastleg.dispatch.QueryMapperTest')) assert.ok(!result.tests.includes('com.baiyue.lastleg.selfoperated.SharedTableTest')) mapper.source = mapper.source.replace('value=1', 'value=2') assert.ok(plan({ changes: [mapper.path], sources: [...sources, mapper, check, unrelatedTable] }) .tests.includes('com.baiyue.lastleg.selfoperated.SharedTableTest')) }) test('environment opt-in classes are reported separately and do not count as passing tests', () => { const optional = java('dispatch.RealMysqlTest', 'DispatchService service; @Test void check() {}', 'test') optional.source = optional.source.replace('class RealMysqlTest', '@EnabledIfSystemProperty(named="live",matches="true") class RealMysqlTest') const result = plan({ sources: [...sources, optional] }) assert.ok(!result.tests.includes('com.baiyue.lastleg.dispatch.RealMysqlTest')) assert.deepEqual(result.optInTests, ['com.baiyue.lastleg.dispatch.RealMysqlTest']) }) test('changed test fixtures and deleted Java types keep their dependent tests', () => { assert.ok(plan({ changes: [fixture.path] }).tests.includes('com.baiyue.lastleg.dispatch.DispatchTest')) const deleted = java('dispatch.RemovedService') const user = java('delivery.ReplacementTest', 'String old = "RemovedService"; @Test void check() {}', 'test') assert.ok(plan({ changes: [deleted.path], sources: [...sources, user] }).tests.includes('com.baiyue.lastleg.delivery.ReplacementTest')) }) test('public API changes also select the existing contract tests', () => { const contract = java('selfoperated.AnalyticsOpenApiContractTest', '@Test void check() {}', 'test') const result = plan({ changes: [source.path, `${module}/src/main/resources/openapi/dispatch.yaml`], sources: [...sources, contract] }) assert.ok(result.tests.includes('com.baiyue.lastleg.selfoperated.AnalyticsOpenApiContractTest')) }) test('shared, unknown, configuration, schema and missing-baseline changes retain full regression', () => { for (const path of ['pom.xml', 'Jenkinsfile', 'common-core/src/main/java/Core.java', 'deploy/scripts/run-ci-maven.sh', 'config/nacos/test/lastleg.yml', `${module}/pom.xml`, `${module}/src/main/resources/db/lastleg.sql`, `${module}/src/test/resources/test-config.yml`, `${module}/src/main/java/com/baiyue/lastleg/entity/Order.java`]) { assert.equal(plan({ changes: [source.path, path] }).scope, 'MODULE', path) } assert.equal(plan({ baseline: '' }).scope, 'MODULE') assert.equal(plan({ mode: 'MODULE' }).scope, 'MODULE') assert.equal(plan({ modules: ['order'] }).scope, 'MODULE') assert.equal(plan({ modules: [module, 'iam'] }).scope, 'MODULE') assert.equal(plan({ changes: [] }).scope, 'MODULE') assert.equal(plan({ sources: [source] }).scope, 'MODULE') assert.throws(() => plan({ mode: 'SKIP' })) }) test('architecture script changes run their own checks instead of broadening Java regression', () => { const result = plan({ changes: [source.path, 'scripts/agents-index-contract.ps1', `${module}/scripts/analyze-lastleg-dependencies.test.mjs`] }) assert.equal(result.scope, 'AFFECTED') assert.deepEqual(result.staticChecks, [`${module}/scripts/analyze-lastleg-dependencies.test.mjs`]) }) test('focused report verification rejects missing, failed and entirely skipped classes', () => { const focused = { scope: 'AFFECTED', tests: ['com.example.Test'] } const report = { name: 'com.example.Test', tests: 2, skipped: 0, failures: 0, errors: 0 } verifyReports(focused, [report]) verifyReports(focused, [{ ...report, name: 'com.example.Test$Nested' }]) for (const reports of [[], [{ ...report, tests: 0 }], [{ ...report, skipped: 2 }], [{ ...report, failures: 1 }], [{ ...report, errors: 1 }], [{ ...report, tests: NaN }]]) { assert.throws(() => verifyReports(focused, reports)) } }) test('CLI prevents double execution of selected classes in the SQL semantic execution', () => { const directory = mkdtempSync(join(tmpdir(), 'ci-test-scope-')) try { const file = join(directory, 'plan.json') writeFileSync(file, JSON.stringify(plan())) const args = execFileSync(process.execPath, ['deploy/scripts/ci-test-scope.mjs', 'args', '--plan', file], { encoding: 'utf8' }) assert.match(args, /-Dtest=com\.baiyue/) assert.match(args, /-Dsurefire.failIfNoSpecifiedTests=false/) assert.match(args, /-Dci.sql-semantic.skip=true/) const pom = readFileSync(new URL('../lastleg-service/pom.xml', import.meta.url), 'utf8') assert.match(pom, /false<\/ci.sql-semantic.skip>/) const execution = pom.slice(pom.indexOf('sql-semantic-tests')) assert.match(execution, /\$\{ci.sql-semantic.skip\}<\/skip>/) assert.match(execution, /false<\/reuseForks>/) } finally { rmSync(directory, { recursive: true, force: true }) } }) test('Jenkins plans from a successful baseline, verifies fresh reports and preserves module override', () => { const pipeline = readFileSync(new URL('../Jenkinsfile', import.meta.url), 'utf8') assert.match(pipeline, /candidate.result == 'SUCCESS' && markerIndex >= 0/) assert.match(pipeline, /requiredServices.every \{ metadata\[1\].split\(','\).contains\(it\) \}/) assert.match(pipeline, /if \(env.TEST_GATE_PASSED == 'true'\)/) assert.match(pipeline, /TESTS=PASSED \$\{env.SOURCE_COMMIT\} \$\{env.SELECTED_SERVICES\}/) assert.match(pipeline, /summary.indexOf\(' \| '\)/) assert.match(pipeline, /env.TEST_BASE_COMMIT = previousSuccessfulTestCommit/) assert.match(pipeline, /choice\(name: 'TEST_SCOPE', choices: \['AUTO', 'MODULE'\]/) assert.match(pipeline, /ci-test-scope.mjs plan --modules/) assert.match(pipeline, /ci-test-scope.mjs checks --plan/) assert.match(pipeline, /-am test \\\$test_arguments/) assert.match(pipeline, /ci-test-scope.mjs verify --plan/) assert.match(pipeline, /booleanParam\(name: 'SKIP_TESTS', value: true\)/) })