@@ -5,6 +5,8 @@ set -euo pipefail
55SCRIPT_DIR=" $( cd " $( dirname " ${BASH_SOURCE[0]} " ) " && pwd) "
66MATCH_FILTER=" $SCRIPT_DIR /match-vulnerability-issue.jq"
77CLASSIFY_FILTER=" $SCRIPT_DIR /classify-vulnerability-issues.jq"
8+ LINEAR_REQUEST=" $SCRIPT_DIR /linear-graphql-request.sh"
9+ WORKFLOW_FILE=" $SCRIPT_DIR /../workflows/vulnerability-triage.yml"
810PREFIX=" [sourcebot-dev/example]"
911
1012assert_json () {
@@ -20,6 +22,86 @@ assert_json() {
2022 fi
2123}
2224
25+ assert_workflow_contains () {
26+ local description=" $1 "
27+ local expected=" $2 "
28+
29+ if ! grep -Fq -- " $expected " " $WORKFLOW_FILE " ; then
30+ echo " FAIL: $description "
31+ echo " Expected workflow to contain: $expected "
32+ exit 1
33+ fi
34+ }
35+
36+ assert_workflow_contains " checks out assets from the called workflow repository" ' repository: ${{ job.workflow_repository }}'
37+ assert_workflow_contains " pins assets to the called workflow revision" ' ref: ${{ job.workflow_sha }}'
38+ assert_workflow_contains " uses the shared match filter" ' -f .vulnerability-triage-workflow/.github/scripts/match-vulnerability-issue.jq'
39+ assert_workflow_contains " uses the shared classification filter" ' -f .vulnerability-triage-workflow/.github/scripts/classify-vulnerability-issues.jq'
40+ assert_workflow_contains " uses the retrying Linear GraphQL client" ' .vulnerability-triage-workflow/.github/scripts/linear-graphql-request.sh'
41+
42+ FAKE_CURL_DIR=$( mktemp -d)
43+ FAKE_CURL_COUNT=$( mktemp)
44+ trap ' rm -rf "$FAKE_CURL_DIR"; rm -f "$FAKE_CURL_COUNT"' EXIT
45+ printf ' 0\n' > " $FAKE_CURL_COUNT "
46+
47+ cat > " $FAKE_CURL_DIR /curl" << 'EOF '
48+ #!/usr/bin/env bash
49+ set -euo pipefail
50+
51+ output_file=""
52+ while (($# > 0)); do
53+ case "$1" in
54+ --output)
55+ output_file="$2"
56+ shift 2
57+ ;;
58+ *)
59+ shift
60+ ;;
61+ esac
62+ done
63+
64+ count=$(( $(<"$FAKE_CURL_COUNT") + 1 ))
65+ printf '%s\n' "$count" > "$FAKE_CURL_COUNT"
66+ if ((count < 3)); then
67+ printf 'Bad Gateway' > "$output_file"
68+ printf '502'
69+ else
70+ printf '{"data":{"ok":true}}' > "$output_file"
71+ printf '200'
72+ fi
73+ EOF
74+ chmod +x " $FAKE_CURL_DIR /curl"
75+
76+ LINEAR_RESPONSE=$(
77+ PATH=" $FAKE_CURL_DIR :$PATH " \
78+ FAKE_CURL_COUNT=" $FAKE_CURL_COUNT " \
79+ LINEAR_API_KEY=" test-key" \
80+ LINEAR_GRAPHQL_ATTEMPTS=3 \
81+ LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
82+ " $LINEAR_REQUEST " <<< ' {"query":"query { viewer { id } }"}'
83+ )
84+ assert_json " retries non-JSON Linear responses" " $LINEAR_RESPONSE " ' {"data":{"ok":true}}'
85+ if [[ " $( < " $FAKE_CURL_COUNT " ) " != " 3" ]]; then
86+ echo " FAIL: expected Linear request helper to retry twice"
87+ exit 1
88+ fi
89+
90+ printf ' 0\n' > " $FAKE_CURL_COUNT "
91+ if PATH=" $FAKE_CURL_DIR :$PATH " \
92+ FAKE_CURL_COUNT=" $FAKE_CURL_COUNT " \
93+ LINEAR_API_KEY=" test-key" \
94+ LINEAR_GRAPHQL_ATTEMPTS=3 \
95+ LINEAR_GRAPHQL_RETRY_DELAY_SECONDS=0 \
96+ " $LINEAR_REQUEST " <<< ' {"query":"mutation { issueCreate(input: {}) { success } }"}' > /dev/null 2>&1 ; then
97+ echo " FAIL: ambiguous Linear mutations must not be retried"
98+ exit 1
99+ fi
100+ if [[ " $( < " $FAKE_CURL_COUNT " ) " != " 1" ]]; then
101+ echo " FAIL: expected exactly one Linear mutation attempt"
102+ exit 1
103+ fi
104+
23105match () {
24106 local finding_id=" $1 "
25107 jq -c --arg prefix " $PREFIX " --arg findingId " $finding_id " -f " $MATCH_FILTER "
@@ -119,13 +201,17 @@ OPEN_ISSUES='[
119201 }
120202]'
121203
122- CLASSIFIED=" $( jq -c --arg prefix " $PREFIX " --slurpfile findings <( printf ' %s\n' " $FINDINGS " ) -f " $CLASSIFY_FILTER " <<< " $OPEN_ISSUES" ) "
204+ CLASSIFIED=" $( jq -c --arg prefix " $PREFIX " --argjson skipCodeql false -- slurpfile findings <( printf ' %s\n' " $FINDINGS " ) -f " $CLASSIFY_FILTER " <<< " $OPEN_ISSUES" ) "
123205assert_json " keeps every exact current finding" " $( jq -c ' [.[] | select(.action == "keep") | .id]' <<< " $CLASSIFIED" ) " ' ["keep-cve","keep-codeql"]'
124206assert_json " closes only resolved managed findings" " $( jq -c ' [.[] | select(.action == "close") | .id]' <<< " $CLASSIFIED" ) " ' ["close-cve","close-prefix-collision"]'
125207assert_json " ignores unrelated titles and repositories" " $( jq -c ' [.[] | select(.action == "ignore") | .id]' <<< " $CLASSIFIED" ) " ' ["ignore-other-repo","ignore-unmanaged"]'
126208
127209EMPTY_FINDINGS=' {"cves":[]}'
128- CLASSIFIED_EMPTY=" $( jq -c --arg prefix " $PREFIX " --slurpfile findings <( printf ' %s\n' " $EMPTY_FINDINGS " ) -f " $CLASSIFY_FILTER " <<< " $OPEN_ISSUES" ) "
210+ CLASSIFIED_EMPTY=" $( jq -c --arg prefix " $PREFIX " --argjson skipCodeql false -- slurpfile findings <( printf ' %s\n' " $EMPTY_FINDINGS " ) -f " $CLASSIFY_FILTER " <<< " $OPEN_ISSUES" ) "
129211assert_json " closes all managed issues when a complete scan is clean" " $( jq -c ' [.[] | select(.action == "close") | .id]' <<< " $CLASSIFIED_EMPTY" ) " ' ["keep-cve","keep-codeql","close-cve","close-prefix-collision"]'
130212
213+ CLASSIFIED_WITH_CODEQL_SKIPPED=" $( jq -c --arg prefix " $PREFIX " --argjson skipCodeql true --slurpfile findings <( printf ' %s\n' " $EMPTY_FINDINGS " ) -f " $CLASSIFY_FILTER " <<< " $OPEN_ISSUES" ) "
214+ assert_json " preserves CodeQL issues when CodeQL is unavailable" " $( jq -c ' [.[] | select(.action == "ignore") | .id]' <<< " $CLASSIFIED_WITH_CODEQL_SKIPPED" ) " ' ["keep-codeql","ignore-other-repo","ignore-unmanaged"]'
215+ assert_json " still closes resolved non-CodeQL issues when CodeQL is unavailable" " $( jq -c ' [.[] | select(.action == "close") | .id]' <<< " $CLASSIFIED_WITH_CODEQL_SKIPPED" ) " ' ["keep-cve","close-cve","close-prefix-collision"]'
216+
131217echo " All vulnerability triage reconciliation tests passed."
0 commit comments