2026-08-25 15:08:34 +08:00
|
|
|
import assert from 'node:assert/strict'
|
|
|
|
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
|
|
|
|
import { tmpdir } from 'node:os'
|
|
|
|
|
import path from 'node:path'
|
|
|
|
|
import test from 'node:test'
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
buildQualitySummary,
|
|
|
|
|
parseJacocoReport,
|
|
|
|
|
parseSpotbugsReport,
|
|
|
|
|
requirementFailures,
|
|
|
|
|
repositoryRoot
|
|
|
|
|
} from './java-quality-summary.mjs'
|
|
|
|
|
|
|
|
|
|
test('parses JaCoCo line coverage and SpotBugs findings without quality thresholds', () => {
|
|
|
|
|
assert.deepEqual(parseJacocoReport('<report><counter type="LINE" missed="3" covered="7"/></report>'), {
|
|
|
|
|
missed: 3, covered: 7, percentage: 70
|
|
|
|
|
})
|
|
|
|
|
assert.deepEqual(parseSpotbugsReport('<BugCollection><BugInstance priority="1" category="CORRECTNESS"/><BugInstance priority="2" category="STYLE"/></BugCollection>'), {
|
|
|
|
|
findings: 2,
|
|
|
|
|
byPriority: { 1: 1, 2: 1 },
|
|
|
|
|
byCategory: { CORRECTNESS: 1, STYLE: 1 }
|
|
|
|
|
})
|
2026-08-25 19:07:34 +08:00
|
|
|
assert.deepEqual(parseSpotbugsReport("<BugCollection><BugInstance priority='2' category='MALICIOUS_CODE'/></BugCollection>"), {
|
|
|
|
|
findings: 1,
|
|
|
|
|
byPriority: { 2: 1 },
|
|
|
|
|
byCategory: { MALICIOUS_CODE: 1 }
|
|
|
|
|
})
|
2026-08-25 15:08:34 +08:00
|
|
|
assert.throws(() => parseJacocoReport('<report/>'), /not a JaCoCo report/)
|
|
|
|
|
assert.throws(() => parseSpotbugsReport('<BugCollection/>'), /not a SpotBugs report/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('aggregates code modules, report presence and the OpenAPI service matrix', async (t) => {
|
|
|
|
|
const root = await mkdtemp(path.join(tmpdir(), 'baiyue-quality-summary-'))
|
|
|
|
|
t.after(() => rm(root, { recursive: true, force: true }))
|
|
|
|
|
await writeFile(path.join(root, 'pom.xml'), '<modules><module>bom</module><module>alpha</module><module>beta</module></modules>', 'utf8')
|
|
|
|
|
await mkdir(path.join(root, 'deploy'), { recursive: true })
|
|
|
|
|
await writeFile(path.join(root, 'deploy', 'service-map.yml'), 'services:\n alpha:\n port: 1\n beta:\n port: 2\n', 'utf8')
|
|
|
|
|
await mkdir(path.join(root, 'alpha', 'src', 'main', 'java'), { recursive: true })
|
|
|
|
|
await mkdir(path.join(root, 'beta', 'src', 'main', 'java'), { recursive: true })
|
|
|
|
|
await writeFile(path.join(root, 'alpha', 'src', 'main', 'java', 'Alpha.java'), 'class Alpha {}', 'utf8')
|
|
|
|
|
await writeFile(path.join(root, 'beta', 'src', 'main', 'java', 'Beta.java'), 'class Beta {}', 'utf8')
|
|
|
|
|
await mkdir(path.join(root, 'alpha', 'src', 'main', 'resources', 'openapi'), { recursive: true })
|
|
|
|
|
await writeFile(path.join(root, 'alpha', 'src', 'main', 'resources', 'openapi', 'alpha.yaml'), 'openapi: 3.0.3\n', 'utf8')
|
|
|
|
|
await mkdir(path.join(root, 'alpha', 'target', 'site', 'jacoco'), { recursive: true })
|
|
|
|
|
await writeFile(path.join(root, 'alpha', 'target', 'site', 'jacoco', 'jacoco.xml'), '<report><counter type="LINE" missed="1" covered="9"/></report>', 'utf8')
|
|
|
|
|
await mkdir(path.join(root, 'alpha', 'target', 'site'), { recursive: true })
|
|
|
|
|
await writeFile(path.join(root, 'alpha', 'target', 'spotbugsXml.xml'), '<BugCollection><BugInstance priority="2" category="STYLE"/></BugCollection>', 'utf8')
|
|
|
|
|
await writeFile(path.join(root, 'alpha', 'target', 'site', 'spotbugs.html'), '<html></html>', 'utf8')
|
|
|
|
|
|
|
|
|
|
const summary = await buildQualitySummary(root)
|
|
|
|
|
assert.deepEqual(summary.topology, { modules: 3, codeModules: 2, services: 2 })
|
|
|
|
|
assert.equal(summary.openapi.documents, 1)
|
|
|
|
|
assert.deepEqual(summary.openapi.coveredServices, ['alpha'])
|
|
|
|
|
assert.deepEqual(summary.openapi.codeFirstServices, ['beta'])
|
|
|
|
|
assert.deepEqual(summary.jacoco, { present: 1, invalid: 0, missing: 1 })
|
|
|
|
|
assert.deepEqual(summary.spotbugs, { present: 1, invalid: 0, missing: 1, findings: 1 })
|
|
|
|
|
assert.deepEqual(requirementFailures(summary), [])
|
|
|
|
|
assert.deepEqual(requirementFailures(summary, { requireJacoco: true, requireSpotbugs: true }), [
|
|
|
|
|
'beta: JaCoCo report is missing', 'beta: SpotBugs report is missing'
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('repository freezes the current topology and non-blocking aggregate summary wiring', async () => {
|
|
|
|
|
const summary = await buildQualitySummary(repositoryRoot)
|
2026-08-25 19:07:34 +08:00
|
|
|
const baseline = JSON.parse(await readFile(path.join(repositoryRoot, 'scripts', 'java-quality-baseline.json'), 'utf8'))
|
2026-10-08 17:07:57 +08:00
|
|
|
assert.deepEqual(summary.topology, { modules: 20, codeModules: 19, services: 13 })
|
2026-10-20 10:35:52 +08:00
|
|
|
assert.equal(summary.openapi.documents, 100)
|
2026-10-08 19:09:30 +08:00
|
|
|
for (const name of ['lastleg-exports', 'return-packing', 'fleet-capacity-offers']) {
|
|
|
|
|
assert.ok(summary.openapi.files.includes(`lastleg-service/src/main/resources/openapi/${name}-v1.yaml`))
|
|
|
|
|
}
|
2026-10-08 17:07:57 +08:00
|
|
|
assert.ok(summary.openapi.files.includes('lastleg-service/src/main/resources/openapi/fleet-trips-v1.yaml'))
|
|
|
|
|
assert.ok(summary.openapi.files.includes('lastleg-service/src/main/resources/openapi/fleet-drivers-v1.yaml'))
|
|
|
|
|
assert.ok(summary.openapi.files.includes('file/src/main/resources/openapi/fleet-driver-license-v1.yaml'))
|
|
|
|
|
assert.ok(summary.modules.every(row => !['gateway-overseas', 'event-bridge-service'].includes(row.module)))
|
2026-09-15 14:58:26 +08:00
|
|
|
assert.ok(summary.openapi.files.includes('file/src/main/resources/openapi/loading-evidence-v1.yaml'))
|
2026-09-09 12:54:14 +08:00
|
|
|
assert.ok(summary.openapi.files.includes('file/src/main/resources/openapi/overseas-relabel-document-v1.yaml'))
|
2026-10-20 10:35:52 +08:00
|
|
|
assert.ok(summary.openapi.files.includes('file/src/main/resources/openapi/dispatch-evidence-v1.yaml'))
|
|
|
|
|
assert.ok(summary.openapi.files.includes('lastleg-service/src/main/resources/openapi/fleet-preparation-v1.yaml'))
|
2026-09-09 12:19:16 +08:00
|
|
|
assert.ok(summary.openapi.files.includes('finance/src/main/resources/openapi/finance-container-settlement-v1.yaml'))
|
|
|
|
|
assert.ok(summary.openapi.files.includes('warehouse-overseas-service/src/main/resources/openapi/warehouse-pallet-batch-internal-v1.yaml'))
|
|
|
|
|
assert.equal(baseline.openapi.documents, summary.openapi.documents)
|
2026-10-08 17:07:57 +08:00
|
|
|
assert.equal(summary.openapi.coveredServices.length, 11)
|
|
|
|
|
assert.deepEqual(summary.openapi.codeFirstServices, ['audit-service', 'gateway'])
|
2026-08-25 19:07:34 +08:00
|
|
|
assert.equal(baseline.mode, 'report-only')
|
|
|
|
|
assert.deepEqual(baseline.topology, summary.topology)
|
2026-10-08 17:07:57 +08:00
|
|
|
assert.equal(baseline.modules.length, 19)
|
|
|
|
|
assert.deepEqual(baseline.modules.map(row => row.module).sort(), summary.modules.map(row => row.module).sort())
|
2026-08-25 19:07:34 +08:00
|
|
|
assert.equal(baseline.modules.reduce((total, row) => total + row.spotbugsFindings, 0), baseline.reports.spotbugsFindings)
|
2026-08-25 15:08:34 +08:00
|
|
|
|
|
|
|
|
const gitlab = (await readFile(path.join(repositoryRoot, '.gitlab-ci.yml'), 'utf8')).replaceAll('\r\n', '\n')
|
|
|
|
|
const job = gitlab.match(/^质量摘要:\n([\s\S]*?)(?=^[^\s#].*:\n)/m)?.[1] ?? ''
|
|
|
|
|
const packageJob = gitlab.match(/^打包服务:\n([\s\S]*)/m)?.[1] ?? ''
|
|
|
|
|
assert.match(job, /^ allow_failure: true$/m)
|
|
|
|
|
assert.match(job, /java-quality-summary\.test\.mjs/)
|
|
|
|
|
assert.match(job, /java-quality-summary\.mjs --output target\/java-quality-summary\.json --require-jacoco --require-spotbugs/)
|
|
|
|
|
assert.match(job, /^ - job: 校验与测试$/m)
|
|
|
|
|
assert.match(job, /^ - job: 静态分析报告$/m)
|
|
|
|
|
assert.match(job, /^ - target\/java-quality-summary\.json$/m)
|
|
|
|
|
assert.doesNotMatch(job, /threshold|ratchet/i)
|
|
|
|
|
assert.doesNotMatch(packageJob, /^ - 质量摘要$/m)
|
|
|
|
|
})
|