fix(ax): normalise audit and health machine output
This commit is contained in:
parent
140d2b0583
commit
364b4b96de
5 changed files with 110 additions and 9 deletions
|
|
@ -269,8 +269,15 @@ func summariseHealthResults(totalRepos int, filteredRepos int, results []RepoHea
|
|||
summary := HealthSummary{
|
||||
TotalRepos: totalRepos,
|
||||
FilteredRepos: filteredRepos,
|
||||
ByStatus: make(map[string]int),
|
||||
ProblemsOnly: problemsOnly,
|
||||
ByStatus: map[string]int{
|
||||
"passing": 0,
|
||||
"failing": 0,
|
||||
"error": 0,
|
||||
"pending": 0,
|
||||
"disabled": 0,
|
||||
"no_ci": 0,
|
||||
},
|
||||
ProblemsOnly: problemsOnly,
|
||||
}
|
||||
|
||||
for _, health := range results {
|
||||
|
|
|
|||
|
|
@ -424,35 +424,52 @@ func addPHPSecurityCommand(parent *cli.Command) {
|
|||
}
|
||||
|
||||
type auditJSONOutput struct {
|
||||
Results []AuditResultJSON `json:"results"`
|
||||
Results []auditResultJSON `json:"results"`
|
||||
HasVulnerabilities bool `json:"has_vulnerabilities"`
|
||||
Vulnerabilities int `json:"vulnerabilities"`
|
||||
}
|
||||
|
||||
type AuditResultJSON struct {
|
||||
type auditResultJSON struct {
|
||||
Tool string `json:"tool"`
|
||||
Vulnerabilities int `json:"vulnerabilities"`
|
||||
Advisories []php.AuditAdvisory `json:"advisories"`
|
||||
Advisories []auditAdvisoryJSON `json:"advisories"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type auditAdvisoryJSON struct {
|
||||
Package string `json:"package"`
|
||||
Severity string `json:"severity,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Identifiers []string `json:"identifiers,omitempty"`
|
||||
}
|
||||
|
||||
func mapAuditResultsForJSON(results []php.AuditResult) auditJSONOutput {
|
||||
output := auditJSONOutput{
|
||||
Results: make([]AuditResultJSON, 0, len(results)),
|
||||
Results: make([]auditResultJSON, 0, len(results)),
|
||||
}
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].Tool < results[j].Tool
|
||||
})
|
||||
|
||||
for _, result := range results {
|
||||
entry := AuditResultJSON{
|
||||
entry := auditResultJSON{
|
||||
Tool: result.Tool,
|
||||
Vulnerabilities: result.Vulnerabilities,
|
||||
Advisories: append([]php.AuditAdvisory(nil), result.Advisories...),
|
||||
}
|
||||
if result.Error != nil {
|
||||
entry.Error = result.Error.Error()
|
||||
}
|
||||
entry.Advisories = make([]auditAdvisoryJSON, 0, len(result.Advisories))
|
||||
for _, advisory := range result.Advisories {
|
||||
entry.Advisories = append(entry.Advisories, auditAdvisoryJSON{
|
||||
Package: advisory.Package,
|
||||
Severity: advisory.Severity,
|
||||
Title: advisory.Title,
|
||||
URL: advisory.URL,
|
||||
Identifiers: append([]string(nil), advisory.Identifiers...),
|
||||
})
|
||||
}
|
||||
sort.Slice(entry.Advisories, func(i, j int) bool {
|
||||
if entry.Advisories[i].Package == entry.Advisories[j].Package {
|
||||
return entry.Advisories[i].Title < entry.Advisories[j].Title
|
||||
|
|
|
|||
|
|
@ -153,6 +153,63 @@ func TestPHPSecuritySARIFOutput_IsStructuredAndChromeFree(t *testing.T) {
|
|||
assert.NotContains(t, output, "Summary:")
|
||||
}
|
||||
|
||||
func TestPHPAuditJSONOutput_UsesLowerCaseAdvisoryKeys(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeTestFile(t, filepath.Join(dir, "composer.json"), "{}")
|
||||
writeExecutable(t, filepath.Join(dir, "composer"), `#!/bin/sh
|
||||
cat <<'JSON'
|
||||
{
|
||||
"advisories": {
|
||||
"vendor/package-a": [
|
||||
{
|
||||
"title": "Remote Code Execution",
|
||||
"link": "https://example.com/advisory/1",
|
||||
"cve": "CVE-2025-1234",
|
||||
"affectedVersions": ">=1.0,<1.5"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
JSON
|
||||
`)
|
||||
|
||||
restoreWorkingDir(t, dir)
|
||||
prependPath(t, dir)
|
||||
resetPHPAuditFlags(t)
|
||||
|
||||
parent := &cli.Command{Use: "qa"}
|
||||
addPHPAuditCommand(parent)
|
||||
command := findSubcommand(t, parent, "audit")
|
||||
require.NoError(t, command.Flags().Set("json", "true"))
|
||||
|
||||
var runErr error
|
||||
output := captureStdout(t, func() {
|
||||
runErr = command.RunE(command, nil)
|
||||
})
|
||||
|
||||
require.Error(t, runErr)
|
||||
|
||||
var payload struct {
|
||||
Results []struct {
|
||||
Tool string `json:"tool"`
|
||||
Advisories []struct {
|
||||
Package string `json:"package"`
|
||||
} `json:"advisories"`
|
||||
} `json:"results"`
|
||||
HasVulnerabilities bool `json:"has_vulnerabilities"`
|
||||
Vulnerabilities int `json:"vulnerabilities"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal([]byte(output), &payload))
|
||||
require.Len(t, payload.Results, 1)
|
||||
assert.Equal(t, "composer", payload.Results[0].Tool)
|
||||
require.Len(t, payload.Results[0].Advisories, 1)
|
||||
assert.Equal(t, "vendor/package-a", payload.Results[0].Advisories[0].Package)
|
||||
assert.True(t, payload.HasVulnerabilities)
|
||||
assert.Equal(t, 1, payload.Vulnerabilities)
|
||||
assert.NotContains(t, output, "\"Package\"")
|
||||
assert.NotContains(t, output, "Dependency Audit")
|
||||
}
|
||||
|
||||
func TestPHPTestJUnitOutput_PrintsOnlyXML(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeTestFile(t, filepath.Join(dir, "composer.json"), "{}")
|
||||
|
|
@ -258,6 +315,18 @@ func resetPHPSecurityFlags(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func resetPHPAuditFlags(t *testing.T) {
|
||||
t.Helper()
|
||||
oldJSON := phpAuditJSON
|
||||
oldFix := phpAuditFix
|
||||
phpAuditJSON = false
|
||||
phpAuditFix = false
|
||||
t.Cleanup(func() {
|
||||
phpAuditJSON = oldJSON
|
||||
phpAuditFix = oldFix
|
||||
})
|
||||
}
|
||||
|
||||
func resetPHPTestFlags(t *testing.T) {
|
||||
t.Helper()
|
||||
oldParallel := phpTestParallel
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package php
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
coreio "forge.lthn.ai/core/go-io"
|
||||
|
|
@ -112,6 +114,12 @@ func RunSecurityChecks(ctx context.Context, opts SecurityOptions) (*SecurityResu
|
|||
}
|
||||
}
|
||||
|
||||
// Keep the check order stable for callers that consume the package result
|
||||
// directly instead of going through the CLI layer.
|
||||
slices.SortFunc(result.Checks, func(a, b SecurityCheck) int {
|
||||
return cmp.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,5 +16,5 @@ tasks:
|
|||
run_capture_stdout 1 "$output" ../../bin/core qa audit --json
|
||||
jq -e '.results[0].tool == "composer" and .results[0].vulnerabilities == 1' "$output" >/dev/null
|
||||
jq -e '.has_vulnerabilities == true and .vulnerabilities == 1' "$output" >/dev/null
|
||||
jq -e '.results[0].advisories[0].Package == "vendor/package-a"' "$output" >/dev/null
|
||||
jq -e '.results[0].advisories[0].package == "vendor/package-a"' "$output" >/dev/null
|
||||
EOF
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue