Files

154 lines
9.4 KiB
JavaScript

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, /<ci.sql-semantic.skip>false<\/ci.sql-semantic.skip>/)
const execution = pom.slice(pom.indexOf('<id>sql-semantic-tests</id>'))
assert.match(execution, /<skip>\$\{ci.sql-semantic.skip\}<\/skip>/)
assert.match(execution, /<reuseForks>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\)/)
})