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 }
|
|
|
|
|
})
|
|
|
|
|
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 16:40:48 +08:00
|
|
|
assert.deepEqual(summary.topology, { modules: 22, codeModules: 21, services: 15 })
|
|
|
|
|
assert.equal(summary.openapi.documents, 26)
|
|
|
|
|
assert.equal(summary.openapi.coveredServices.length, 7)
|
2026-08-25 15:08:34 +08:00
|
|
|
assert.equal(summary.openapi.codeFirstServices.length, 8)
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
})
|