From dbdc1656f5dec3ee1cf0ef848ffb3f1fde4752a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez?= Date: Mon, 7 Sep 2026 12:18:38 +0200 Subject: [PATCH 1/7] feat: UFM TOON contract --- .../presenter_ufm_toon_contract_test.go | 64 +++++ .../presenters/testdata/ufm/toon/README.md | 45 +++ .../testdata/ufm/toon/empty_sca.json | 25 ++ .../ufm/toon/empty_sca.testresult.json | 19 ++ .../testdata/ufm/toon/empty_sca.toon | 16 ++ .../testdata/ufm/toon/empty_secrets.json | 25 ++ .../ufm/toon/empty_secrets.testresult.json | 20 ++ .../testdata/ufm/toon/empty_secrets.toon | 16 ++ .../presenters/testdata/ufm/toon/mixed.json | 265 ++++++++++++++++++ .../testdata/ufm/toon/mixed.testresult.json | 250 +++++++++++++++++ .../presenters/testdata/ufm/toon/mixed.toon | 157 +++++++++++ .../presenters/testdata/ufm/toon/nested.json | 71 +++++ .../testdata/ufm/toon/nested.testresult.json | 61 ++++ .../presenters/testdata/ufm/toon/nested.toon | 44 +++ .../testdata/ufm/toon/no_results.json | 3 + .../ufm/toon/no_results.testresult.json | 1 + .../testdata/ufm/toon/no_results.toon | 1 + .../presenters/testdata/ufm/toon/sca.json | 129 +++++++++ .../testdata/ufm/toon/sca.testresult.json | 120 ++++++++ .../presenters/testdata/ufm/toon/sca.toon | 69 +++++ .../presenters/testdata/ufm/toon/secrets.json | 69 +++++ .../testdata/ufm/toon/secrets.testresult.json | 65 +++++ .../presenters/testdata/ufm/toon/secrets.toon | 43 +++ .../presenters/testdata/ufm/toon/verify.mjs | 43 +++ 24 files changed, 1621 insertions(+) create mode 100644 internal/presenters/presenter_ufm_toon_contract_test.go create mode 100644 internal/presenters/testdata/ufm/toon/README.md create mode 100644 internal/presenters/testdata/ufm/toon/empty_sca.json create mode 100644 internal/presenters/testdata/ufm/toon/empty_sca.testresult.json create mode 100644 internal/presenters/testdata/ufm/toon/empty_sca.toon create mode 100644 internal/presenters/testdata/ufm/toon/empty_secrets.json create mode 100644 internal/presenters/testdata/ufm/toon/empty_secrets.testresult.json create mode 100644 internal/presenters/testdata/ufm/toon/empty_secrets.toon create mode 100644 internal/presenters/testdata/ufm/toon/mixed.json create mode 100644 internal/presenters/testdata/ufm/toon/mixed.testresult.json create mode 100644 internal/presenters/testdata/ufm/toon/mixed.toon create mode 100644 internal/presenters/testdata/ufm/toon/nested.json create mode 100644 internal/presenters/testdata/ufm/toon/nested.testresult.json create mode 100644 internal/presenters/testdata/ufm/toon/nested.toon create mode 100644 internal/presenters/testdata/ufm/toon/no_results.json create mode 100644 internal/presenters/testdata/ufm/toon/no_results.testresult.json create mode 100644 internal/presenters/testdata/ufm/toon/no_results.toon create mode 100644 internal/presenters/testdata/ufm/toon/sca.json create mode 100644 internal/presenters/testdata/ufm/toon/sca.testresult.json create mode 100644 internal/presenters/testdata/ufm/toon/sca.toon create mode 100644 internal/presenters/testdata/ufm/toon/secrets.json create mode 100644 internal/presenters/testdata/ufm/toon/secrets.testresult.json create mode 100644 internal/presenters/testdata/ufm/toon/secrets.toon create mode 100644 internal/presenters/testdata/ufm/toon/verify.mjs diff --git a/internal/presenters/presenter_ufm_toon_contract_test.go b/internal/presenters/presenter_ufm_toon_contract_test.go new file mode 100644 index 000000000..8815a21b1 --- /dev/null +++ b/internal/presenters/presenter_ufm_toon_contract_test.go @@ -0,0 +1,64 @@ +package presenters_test + +import ( + "bytes" + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/snyk/go-application-framework/pkg/apiclients/testapi" + "github.com/snyk/go-application-framework/pkg/utils/ufm" +) + +// These fixtures define the JSON input to the future TOON presenter. The pinned +// reference codec verifies their .toon counterparts; see testdata/ufm/toon/README.md. +func Test_UfmTOONContract(t *testing.T) { + for _, name := range []string{"sca", "secrets", "empty_sca", "empty_secrets", "mixed", "nested", "no_results"} { + t.Run(name, func(t *testing.T) { + input, err := os.ReadFile(filepath.Join("testdata", "ufm", "toon", name+".testresult.json")) + require.NoError(t, err) + results, err := ufm.NewSerializableTestResultFromBytes(input) + require.NoError(t, err) + + envelope := make([]map[string]any, 0, len(results)) + for _, result := range results { + findings, complete, findingsErr := result.Findings(t.Context()) + require.NoError(t, findingsErr) + require.True(t, complete, "incomplete findings cannot define a successful golden") + if findings == nil { + findings = []testapi.FindingData{} + } + envelope = append(envelope, map[string]any{ + "testId": result.GetTestID(), + "testConfiguration": result.GetTestConfiguration(), + "testSubject": result.Get(testapi.TestResultTestSubject), + "executionState": result.GetExecutionState(), + "passFail": result.GetPassFail(), + "outcomeReason": result.GetOutcomeReason(), + "errors": result.GetErrors(), + "warnings": result.GetWarnings(), + "rawSummary": result.Get(testapi.TestResultRawSummary), + "effectiveSummary": result.GetEffectiveSummary(), + "findings": findings, + }) + } + actual, err := json.Marshal(map[string]any{"results": envelope}) + require.NoError(t, err) + expected, err := os.ReadFile(filepath.Join("testdata", "ufm", "toon", name+".json")) + require.NoError(t, err) + require.Equal(t, decodeTOONContractJSON(t, expected), decodeTOONContractJSON(t, actual)) + }) + } +} + +func decodeTOONContractJSON(t *testing.T, data []byte) any { + t.Helper() + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.UseNumber() + var value any + require.NoError(t, decoder.Decode(&value)) + return value +} diff --git a/internal/presenters/testdata/ufm/toon/README.md b/internal/presenters/testdata/ufm/toon/README.md new file mode 100644 index 000000000..de31f0818 --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/README.md @@ -0,0 +1,45 @@ +# UFM TOON contract + +Synthetic fixtures: UFM input (`*.testresult.json`), expected JSON (`*.json`) +and exact TOON output (`*.toon`). Use GAF's template engine to render TOON. +No production encoder dependency. + +## Contract + +- Use `{"results": [...]}`. Keep the envelope fields shown in the JSON fixtures, + including both summaries. Prefer `Get(field)` where supported; use dedicated + getters otherwise. Missing or nil envelope values become `null`; complete + empty findings become `[]`. +- Read findings through `TestResult.Findings(ctx)` to restore stored problems. + Return an error before writing if extraction fails or `complete == false`. +- Preserve the returned `FindingData` JSON: tags, custom marshalers, nested + fields, omission/null semantics and numeric types. Keep every finding and + array order; do not aggregate or generate summary text. +- Keep `from_line` and optional `to_line` as supplied, including explicit null. + Do not rename them or fill in a missing end line. +- Preserve numbers exactly or return a rendering error. + +Follow [TOON spec 4.1](https://github.com/toon-format/spec/blob/d6db4b04303bdea132351ce45aed612311c850b2/SPEC.md): +use two-space indentation, comma delimiter, UTF-8 and LF separators. No BOM, +trailing spaces or final newline. Sort object keys recursively by UTF-8 byte +order before rendering; never sort arrays. + +## Verify + +From the repository root: + +```fish +go test ./internal/presenters -run '^Test_UfmTOONContract$' -count=1 + +set toon_reference (mktemp -d) +npm pack @toon-format/toon@4.1.1 --pack-destination "$toon_reference" +tar -xzf "$toon_reference/toon-format-toon-4.1.1.tgz" -C "$toon_reference" +node internal/presenters/testdata/ufm/toon/verify.mjs "$toon_reference/package/dist/index.mjs" +``` + +`@toon-format/toon@4.1.1` is only for generating and verifying fixtures. +The checks compare UFM extraction with expected JSON, exact TOON bytes and +strictly decoded JSON. Do not trim whitespace. The verifier rejects integers +outside JavaScript's safe range; cover those in the template renderer tests. + +To regenerate, add `--write` to the Node command, review the diff and rerun both checks. diff --git a/internal/presenters/testdata/ufm/toon/empty_sca.json b/internal/presenters/testdata/ufm/toon/empty_sca.json new file mode 100644 index 000000000..5f3e5fb09 --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/empty_sca.json @@ -0,0 +1,25 @@ +{ + "results": [ + { + "effectiveSummary": { + "count": 0 + }, + "errors": null, + "executionState": "finished", + "findings": [], + "outcomeReason": null, + "passFail": "pass", + "rawSummary": { + "count": 0 + }, + "testConfiguration": { + "scan_config": { + "sca": {} + } + }, + "testId": "10000000-0000-4000-8000-000000000003", + "testSubject": null, + "warnings": null + } + ] +} diff --git a/internal/presenters/testdata/ufm/toon/empty_sca.testresult.json b/internal/presenters/testdata/ufm/toon/empty_sca.testresult.json new file mode 100644 index 000000000..2409da348 --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/empty_sca.testresult.json @@ -0,0 +1,19 @@ +[ + { + "testId": "10000000-0000-4000-8000-000000000003", + "testConfiguration": { + "scan_config": { + "sca": {} + } + }, + "executionState": "finished", + "passFail": "pass", + "effectiveSummary": { + "count": 0 + }, + "rawSummary": { + "count": 0 + }, + "findingsComplete": true + } +] diff --git a/internal/presenters/testdata/ufm/toon/empty_sca.toon b/internal/presenters/testdata/ufm/toon/empty_sca.toon new file mode 100644 index 000000000..44a19cc8d --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/empty_sca.toon @@ -0,0 +1,16 @@ +results[1]: + - effectiveSummary: + count: 0 + errors: null + executionState: finished + findings: [] + outcomeReason: null + passFail: pass + rawSummary: + count: 0 + testConfiguration: + scan_config: + sca: + testId: 10000000-0000-4000-8000-000000000003 + testSubject: null + warnings: null \ No newline at end of file diff --git a/internal/presenters/testdata/ufm/toon/empty_secrets.json b/internal/presenters/testdata/ufm/toon/empty_secrets.json new file mode 100644 index 000000000..cd4596526 --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/empty_secrets.json @@ -0,0 +1,25 @@ +{ + "results": [ + { + "effectiveSummary": { + "count": 0 + }, + "errors": null, + "executionState": "finished", + "findings": [], + "outcomeReason": null, + "passFail": "pass", + "rawSummary": { + "count": 0 + }, + "testConfiguration": { + "scan_config": { + "secrets": {} + } + }, + "testId": "10000000-0000-4000-8000-000000000004", + "testSubject": null, + "warnings": null + } + ] +} diff --git a/internal/presenters/testdata/ufm/toon/empty_secrets.testresult.json b/internal/presenters/testdata/ufm/toon/empty_secrets.testresult.json new file mode 100644 index 000000000..7e2de5493 --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/empty_secrets.testresult.json @@ -0,0 +1,20 @@ +[ + { + "testId": "10000000-0000-4000-8000-000000000004", + "testConfiguration": { + "scan_config": { + "secrets": {} + } + }, + "executionState": "finished", + "passFail": "pass", + "effectiveSummary": { + "count": 0 + }, + "rawSummary": { + "count": 0 + }, + "findingsComplete": true, + "findings": [] + } +] diff --git a/internal/presenters/testdata/ufm/toon/empty_secrets.toon b/internal/presenters/testdata/ufm/toon/empty_secrets.toon new file mode 100644 index 000000000..4881ef2d2 --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/empty_secrets.toon @@ -0,0 +1,16 @@ +results[1]: + - effectiveSummary: + count: 0 + errors: null + executionState: finished + findings: [] + outcomeReason: null + passFail: pass + rawSummary: + count: 0 + testConfiguration: + scan_config: + secrets: + testId: 10000000-0000-4000-8000-000000000004 + testSubject: null + warnings: null \ No newline at end of file diff --git a/internal/presenters/testdata/ufm/toon/mixed.json b/internal/presenters/testdata/ufm/toon/mixed.json new file mode 100644 index 000000000..5effd18ff --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/mixed.json @@ -0,0 +1,265 @@ +{ + "results": [ + { + "effectiveSummary": { + "count": 1 + }, + "errors": null, + "executionState": "finished", + "findings": [ + { + "attributes": { + "cause_of_failure": true, + "description": "Synthetic finding for output contract tests.", + "evidence": [], + "finding_type": "secrets", + "key": "finding-3", + "locations": [ + { + "file_path": "config.txt", + "from_line": 7, + "to_line": 9, + "type": "source" + }, + { + "file_path": "src/example.go", + "from_line": 4, + "type": "source" + }, + { + "file_path": "nullable.txt", + "from_line": 2, + "to_line": null, + "type": "source" + } + ], + "policy_modifications": [], + "problems": [ + { + "id": "example-secret-rule", + "name": "Example secret rule", + "source": "snyk_secrets_rule" + } + ], + "rating": { + "severity": "high" + }, + "risk": {}, + "title": "Example finding" + }, + "id": "00000000-0000-4000-8000-000000000003", + "type": "findings" + }, + { + "attributes": { + "cause_of_failure": true, + "description": "First line\nSecond\tline \\ quoted \"value\", colon: and Unicode café ☃", + "evidence": null, + "finding_type": "sast", + "key": "finding-4", + "locations": [ + { + "file_path": "folder/\"quoted\",file.go", + "from_line": 1, + "type": "source" + } + ], + "problems": [ + { + "details": { + "#key": "#not a comment", + "a": [ + null, + false, + "null", + "001", + "", + {}, + [ + "x", + "y" + ], + { + "count": 42, + "fraction": 1.25 + } + ], + "colon:key": " trailing ", + "z": null + }, + "id": "example-future", + "source": "future_problem" + } + ], + "rating": { + "severity": "high" + }, + "risk": {}, + "title": "Example finding" + }, + "id": "00000000-0000-4000-8000-000000000004", + "type": "findings" + } + ], + "outcomeReason": null, + "passFail": "fail", + "rawSummary": { + "count": 2 + }, + "testConfiguration": { + "scan_config": { + "secrets": {} + } + }, + "testId": "10000000-0000-4000-8000-000000000002", + "testSubject": null, + "warnings": [] + }, + { + "effectiveSummary": { + "count": 0 + }, + "errors": null, + "executionState": "finished", + "findings": [], + "outcomeReason": null, + "passFail": "pass", + "rawSummary": { + "count": 0 + }, + "testConfiguration": { + "scan_config": { + "sca": {} + } + }, + "testId": "10000000-0000-4000-8000-000000000003", + "testSubject": null, + "warnings": null + }, + { + "effectiveSummary": { + "count": 2 + }, + "errors": null, + "executionState": "finished", + "findings": [ + { + "attributes": { + "cause_of_failure": true, + "description": "Synthetic finding for output contract tests.", + "evidence": [ + { + "path": [ + { + "name": "example-app", + "version": "1.0.0" + }, + { + "name": "example-lib", + "version": "1.0.0" + } + ], + "source": "dependency_path" + } + ], + "finding_type": "sca", + "key": "finding-2", + "locations": [ + { + "package": { + "name": "example-lib", + "version": "1.0.0" + }, + "type": "package" + } + ], + "problems": [ + { + "affected_versions": [ + "<2.0.0" + ], + "id": "SNYK-EXAMPLE-1", + "initially_fixed_in_versions": [ + "2.0.0" + ], + "is_fixable": true, + "package_name": "example-lib", + "source": "snyk_vuln" + } + ], + "rating": { + "severity": "high" + }, + "risk": {}, + "title": "Example finding" + }, + "id": "00000000-0000-4000-8000-000000000002", + "relationships": { + "asset": { + "data": { + "id": "20000000-0000-4000-8000-000000000001", + "type": "assets" + }, + "links": { + "related": "https://example.com/assets/example" + } + } + }, + "type": "findings" + }, + { + "attributes": { + "cause_of_failure": true, + "description": "Synthetic finding for output contract tests.", + "evidence": [], + "finding_type": "sca", + "key": "finding-1", + "locations": [ + { + "package": { + "name": "example-lib", + "version": "1.1.0" + }, + "type": "package" + } + ], + "problems": [ + { + "affected_versions": [ + "<2.0.0" + ], + "id": "SNYK-EXAMPLE-1", + "initially_fixed_in_versions": [ + "2.0.0" + ], + "is_fixable": true, + "package_name": "example-lib", + "source": "snyk_vuln" + } + ], + "rating": { + "severity": "high" + }, + "risk": {}, + "title": "Example finding" + }, + "id": "00000000-0000-4000-8000-000000000001", + "type": "findings" + } + ], + "outcomeReason": null, + "passFail": "fail", + "rawSummary": { + "count": 2 + }, + "testConfiguration": { + "scan_config": { + "sca": {} + } + }, + "testId": "10000000-0000-4000-8000-000000000001", + "testSubject": null, + "warnings": null + } + ] +} diff --git a/internal/presenters/testdata/ufm/toon/mixed.testresult.json b/internal/presenters/testdata/ufm/toon/mixed.testresult.json new file mode 100644 index 000000000..7dedb667e --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/mixed.testresult.json @@ -0,0 +1,250 @@ +[ + { + "testId": "10000000-0000-4000-8000-000000000002", + "testConfiguration": { + "scan_config": { + "secrets": {} + } + }, + "executionState": "finished", + "passFail": "fail", + "effectiveSummary": { + "count": 1 + }, + "rawSummary": { + "count": 2 + }, + "findingsComplete": true, + "findings": [ + { + "id": "00000000-0000-4000-8000-000000000003", + "type": "findings", + "attributes": { + "cause_of_failure": true, + "description": "Synthetic finding for output contract tests.", + "evidence": [], + "finding_type": "secrets", + "key": "finding-3", + "locations": [ + { + "type": "source", + "file_path": "config.txt", + "from_line": 7, + "to_line": 9 + }, + { + "type": "source", + "file_path": "src/example.go", + "from_line": 4 + }, + { + "type": "source", + "file_path": "nullable.txt", + "from_line": 2, + "to_line": null + } + ], + "problems": [ + { + "source": "snyk_secrets_rule", + "id": "example-secret-rule", + "name": "Example secret rule" + } + ], + "rating": { + "severity": "high" + }, + "risk": {}, + "title": "Example finding", + "policy_modifications": [] + } + }, + { + "id": "00000000-0000-4000-8000-000000000004", + "type": "findings", + "attributes": { + "cause_of_failure": true, + "description": "First line\nSecond\tline \\ quoted \"value\", colon: and Unicode café ☃", + "evidence": null, + "finding_type": "sast", + "key": "finding-4", + "locations": [ + { + "type": "source", + "file_path": "folder/\"quoted\",file.go", + "from_line": 1 + } + ], + "problems": [ + { + "source": "future_problem", + "id": "example-future", + "details": { + "z": null, + "a": [ + null, + false, + "null", + "001", + "", + {}, + [ + "x", + "y" + ], + { + "count": 42, + "fraction": 1.25 + } + ], + "#key": "#not a comment", + "colon:key": " trailing " + } + } + ], + "rating": { + "severity": "high" + }, + "risk": {}, + "title": "Example finding" + } + } + ], + "warnings": [] + }, + { + "testId": "10000000-0000-4000-8000-000000000003", + "testConfiguration": { + "scan_config": { + "sca": {} + } + }, + "executionState": "finished", + "passFail": "pass", + "effectiveSummary": { + "count": 0 + }, + "rawSummary": { + "count": 0 + }, + "findingsComplete": true + }, + { + "testId": "10000000-0000-4000-8000-000000000001", + "testConfiguration": { + "scan_config": { + "sca": {} + } + }, + "executionState": "finished", + "passFail": "fail", + "effectiveSummary": { + "count": 2 + }, + "rawSummary": { + "count": 2 + }, + "findingsComplete": true, + "findings": [ + { + "id": "00000000-0000-4000-8000-000000000002", + "type": "findings", + "attributes": { + "cause_of_failure": true, + "description": "Synthetic finding for output contract tests.", + "evidence": [ + { + "source": "dependency_path", + "path": [ + { + "name": "example-app", + "version": "1.0.0" + }, + { + "name": "example-lib", + "version": "1.0.0" + } + ] + } + ], + "finding_type": "sca", + "key": "finding-2", + "locations": [ + { + "type": "package", + "package": { + "name": "example-lib", + "version": "1.0.0" + } + } + ], + "problems": null, + "rating": { + "severity": "high" + }, + "risk": {}, + "title": "Example finding" + }, + "relationships": { + "asset": { + "data": { + "id": "20000000-0000-4000-8000-000000000001", + "type": "assets" + }, + "links": { + "related": "https://example.com/assets/example" + } + } + } + }, + { + "id": "00000000-0000-4000-8000-000000000001", + "type": "findings", + "attributes": { + "cause_of_failure": true, + "description": "Synthetic finding for output contract tests.", + "evidence": [], + "finding_type": "sca", + "key": "finding-1", + "locations": [ + { + "type": "package", + "package": { + "name": "example-lib", + "version": "1.1.0" + } + } + ], + "problems": null, + "rating": { + "severity": "high" + }, + "risk": {}, + "title": "Example finding" + } + } + ], + "problemStore": { + "SNYK-EXAMPLE-1": { + "source": "snyk_vuln", + "id": "SNYK-EXAMPLE-1", + "package_name": "example-lib", + "affected_versions": [ + "<2.0.0" + ], + "initially_fixed_in_versions": [ + "2.0.0" + ], + "is_fixable": true + } + }, + "_problemRefs": { + "00000000-0000-4000-8000-000000000002": [ + "SNYK-EXAMPLE-1" + ], + "00000000-0000-4000-8000-000000000001": [ + "SNYK-EXAMPLE-1" + ] + } + } +] diff --git a/internal/presenters/testdata/ufm/toon/mixed.toon b/internal/presenters/testdata/ufm/toon/mixed.toon new file mode 100644 index 000000000..e3a2801e0 --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/mixed.toon @@ -0,0 +1,157 @@ +results[3]: + - effectiveSummary: + count: 1 + errors: null + executionState: finished + findings[2]: + - attributes: + cause_of_failure: true + description: Synthetic finding for output contract tests. + evidence: [] + finding_type: secrets + key: finding-3 + locations[3]: + - file_path: config.txt + from_line: 7 + to_line: 9 + type: source + - file_path: src/example.go + from_line: 4 + type: source + - file_path: nullable.txt + from_line: 2 + to_line: null + type: source + policy_modifications: [] + problems[1]{id,name,source}: + example-secret-rule,Example secret rule,snyk_secrets_rule + rating: + severity: high + risk: + title: Example finding + id: 00000000-0000-4000-8000-000000000003 + type: findings + - attributes: + cause_of_failure: true + description: "First line\nSecond\tline \\ quoted \"value\", colon: and Unicode café ☃" + evidence: null + finding_type: sast + key: finding-4 + locations[1]{file_path,from_line,type}: + "folder/\"quoted\",file.go",1,source + problems[1]: + - details: + "#key": "#not a comment" + a[8]: + - null + - false + - "null" + - "001" + - "" + - + - [2]: x,y + - count: 42 + fraction: 1.25 + "colon:key": " trailing " + z: null + id: example-future + source: future_problem + rating: + severity: high + risk: + title: Example finding + id: 00000000-0000-4000-8000-000000000004 + type: findings + outcomeReason: null + passFail: fail + rawSummary: + count: 2 + testConfiguration: + scan_config: + secrets: + testId: 10000000-0000-4000-8000-000000000002 + testSubject: null + warnings: [] + - effectiveSummary: + count: 0 + errors: null + executionState: finished + findings: [] + outcomeReason: null + passFail: pass + rawSummary: + count: 0 + testConfiguration: + scan_config: + sca: + testId: 10000000-0000-4000-8000-000000000003 + testSubject: null + warnings: null + - effectiveSummary: + count: 2 + errors: null + executionState: finished + findings[2]: + - attributes: + cause_of_failure: true + description: Synthetic finding for output contract tests. + evidence[1]: + - path[2]{name,version}: + example-app,1.0.0 + example-lib,1.0.0 + source: dependency_path + finding_type: sca + key: finding-2 + locations[1]{package{name,version},type}: + example-lib,1.0.0,package + problems[1]: + - affected_versions[1]: <2.0.0 + id: SNYK-EXAMPLE-1 + initially_fixed_in_versions[1]: 2.0.0 + is_fixable: true + package_name: example-lib + source: snyk_vuln + rating: + severity: high + risk: + title: Example finding + id: 00000000-0000-4000-8000-000000000002 + relationships: + asset: + data: + id: 20000000-0000-4000-8000-000000000001 + type: assets + links: + related: "https://example.com/assets/example" + type: findings + - attributes: + cause_of_failure: true + description: Synthetic finding for output contract tests. + evidence: [] + finding_type: sca + key: finding-1 + locations[1]{package{name,version},type}: + example-lib,1.1.0,package + problems[1]: + - affected_versions[1]: <2.0.0 + id: SNYK-EXAMPLE-1 + initially_fixed_in_versions[1]: 2.0.0 + is_fixable: true + package_name: example-lib + source: snyk_vuln + rating: + severity: high + risk: + title: Example finding + id: 00000000-0000-4000-8000-000000000001 + type: findings + outcomeReason: null + passFail: fail + rawSummary: + count: 2 + testConfiguration: + scan_config: + sca: + testId: 10000000-0000-4000-8000-000000000001 + testSubject: null + warnings: null \ No newline at end of file diff --git a/internal/presenters/testdata/ufm/toon/nested.json b/internal/presenters/testdata/ufm/toon/nested.json new file mode 100644 index 000000000..96691555b --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/nested.json @@ -0,0 +1,71 @@ +{ + "results": [ + { + "effectiveSummary": null, + "errors": null, + "executionState": "finished", + "findings": [ + { + "attributes": { + "cause_of_failure": true, + "description": "First line\nSecond\tline \\ quoted \"value\", colon: and Unicode café ☃", + "evidence": null, + "finding_type": "sast", + "key": "finding-4", + "locations": [ + { + "file_path": "folder/\"quoted\",file.go", + "from_line": 1, + "type": "source" + } + ], + "problems": [ + { + "details": { + "#key": "#not a comment", + "a": [ + null, + false, + "null", + "001", + "", + {}, + [ + "x", + "y" + ], + { + "count": 42, + "fraction": 1.25 + } + ], + "colon:key": " trailing ", + "z": null + }, + "id": "example-future", + "source": "future_problem" + } + ], + "rating": { + "severity": "high" + }, + "risk": {}, + "title": "Example finding" + }, + "id": "00000000-0000-4000-8000-000000000004", + "type": "findings" + }, + { + "type": "findings" + } + ], + "outcomeReason": null, + "passFail": null, + "rawSummary": null, + "testConfiguration": null, + "testId": null, + "testSubject": null, + "warnings": null + } + ] +} diff --git a/internal/presenters/testdata/ufm/toon/nested.testresult.json b/internal/presenters/testdata/ufm/toon/nested.testresult.json new file mode 100644 index 000000000..af30f9d00 --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/nested.testresult.json @@ -0,0 +1,61 @@ +[ + { + "executionState": "finished", + "findingsComplete": true, + "findings": [ + { + "id": "00000000-0000-4000-8000-000000000004", + "type": "findings", + "attributes": { + "cause_of_failure": true, + "description": "First line\nSecond\tline \\ quoted \"value\", colon: and Unicode café ☃", + "evidence": null, + "finding_type": "sast", + "key": "finding-4", + "locations": [ + { + "type": "source", + "file_path": "folder/\"quoted\",file.go", + "from_line": 1 + } + ], + "problems": [ + { + "source": "future_problem", + "id": "example-future", + "details": { + "z": null, + "a": [ + null, + false, + "null", + "001", + "", + {}, + [ + "x", + "y" + ], + { + "count": 42, + "fraction": 1.25 + } + ], + "#key": "#not a comment", + "colon:key": " trailing " + } + } + ], + "rating": { + "severity": "high" + }, + "risk": {}, + "title": "Example finding" + } + }, + { + "type": "findings" + } + ] + } +] diff --git a/internal/presenters/testdata/ufm/toon/nested.toon b/internal/presenters/testdata/ufm/toon/nested.toon new file mode 100644 index 000000000..f1bdcd48b --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/nested.toon @@ -0,0 +1,44 @@ +results[1]: + - effectiveSummary: null + errors: null + executionState: finished + findings[2]: + - attributes: + cause_of_failure: true + description: "First line\nSecond\tline \\ quoted \"value\", colon: and Unicode café ☃" + evidence: null + finding_type: sast + key: finding-4 + locations[1]{file_path,from_line,type}: + "folder/\"quoted\",file.go",1,source + problems[1]: + - details: + "#key": "#not a comment" + a[8]: + - null + - false + - "null" + - "001" + - "" + - + - [2]: x,y + - count: 42 + fraction: 1.25 + "colon:key": " trailing " + z: null + id: example-future + source: future_problem + rating: + severity: high + risk: + title: Example finding + id: 00000000-0000-4000-8000-000000000004 + type: findings + - type: findings + outcomeReason: null + passFail: null + rawSummary: null + testConfiguration: null + testId: null + testSubject: null + warnings: null \ No newline at end of file diff --git a/internal/presenters/testdata/ufm/toon/no_results.json b/internal/presenters/testdata/ufm/toon/no_results.json new file mode 100644 index 000000000..8932c4263 --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/no_results.json @@ -0,0 +1,3 @@ +{ + "results": [] +} diff --git a/internal/presenters/testdata/ufm/toon/no_results.testresult.json b/internal/presenters/testdata/ufm/toon/no_results.testresult.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/no_results.testresult.json @@ -0,0 +1 @@ +[] diff --git a/internal/presenters/testdata/ufm/toon/no_results.toon b/internal/presenters/testdata/ufm/toon/no_results.toon new file mode 100644 index 000000000..c0b2addc7 --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/no_results.toon @@ -0,0 +1 @@ +results: [] \ No newline at end of file diff --git a/internal/presenters/testdata/ufm/toon/sca.json b/internal/presenters/testdata/ufm/toon/sca.json new file mode 100644 index 000000000..8e801a08a --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/sca.json @@ -0,0 +1,129 @@ +{ + "results": [ + { + "effectiveSummary": { + "count": 2 + }, + "errors": null, + "executionState": "finished", + "findings": [ + { + "attributes": { + "cause_of_failure": true, + "description": "Synthetic finding for output contract tests.", + "evidence": [ + { + "path": [ + { + "name": "example-app", + "version": "1.0.0" + }, + { + "name": "example-lib", + "version": "1.0.0" + } + ], + "source": "dependency_path" + } + ], + "finding_type": "sca", + "key": "finding-2", + "locations": [ + { + "package": { + "name": "example-lib", + "version": "1.0.0" + }, + "type": "package" + } + ], + "problems": [ + { + "affected_versions": [ + "<2.0.0" + ], + "id": "SNYK-EXAMPLE-1", + "initially_fixed_in_versions": [ + "2.0.0" + ], + "is_fixable": true, + "package_name": "example-lib", + "source": "snyk_vuln" + } + ], + "rating": { + "severity": "high" + }, + "risk": {}, + "title": "Example finding" + }, + "id": "00000000-0000-4000-8000-000000000002", + "relationships": { + "asset": { + "data": { + "id": "20000000-0000-4000-8000-000000000001", + "type": "assets" + }, + "links": { + "related": "https://example.com/assets/example" + } + } + }, + "type": "findings" + }, + { + "attributes": { + "cause_of_failure": true, + "description": "Synthetic finding for output contract tests.", + "evidence": [], + "finding_type": "sca", + "key": "finding-1", + "locations": [ + { + "package": { + "name": "example-lib", + "version": "1.1.0" + }, + "type": "package" + } + ], + "problems": [ + { + "affected_versions": [ + "<2.0.0" + ], + "id": "SNYK-EXAMPLE-1", + "initially_fixed_in_versions": [ + "2.0.0" + ], + "is_fixable": true, + "package_name": "example-lib", + "source": "snyk_vuln" + } + ], + "rating": { + "severity": "high" + }, + "risk": {}, + "title": "Example finding" + }, + "id": "00000000-0000-4000-8000-000000000001", + "type": "findings" + } + ], + "outcomeReason": null, + "passFail": "fail", + "rawSummary": { + "count": 2 + }, + "testConfiguration": { + "scan_config": { + "sca": {} + } + }, + "testId": "10000000-0000-4000-8000-000000000001", + "testSubject": null, + "warnings": null + } + ] +} diff --git a/internal/presenters/testdata/ufm/toon/sca.testresult.json b/internal/presenters/testdata/ufm/toon/sca.testresult.json new file mode 100644 index 000000000..c1de71823 --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/sca.testresult.json @@ -0,0 +1,120 @@ +[ + { + "testId": "10000000-0000-4000-8000-000000000001", + "testConfiguration": { + "scan_config": { + "sca": {} + } + }, + "executionState": "finished", + "passFail": "fail", + "effectiveSummary": { + "count": 2 + }, + "rawSummary": { + "count": 2 + }, + "findingsComplete": true, + "findings": [ + { + "id": "00000000-0000-4000-8000-000000000002", + "type": "findings", + "attributes": { + "cause_of_failure": true, + "description": "Synthetic finding for output contract tests.", + "evidence": [ + { + "source": "dependency_path", + "path": [ + { + "name": "example-app", + "version": "1.0.0" + }, + { + "name": "example-lib", + "version": "1.0.0" + } + ] + } + ], + "finding_type": "sca", + "key": "finding-2", + "locations": [ + { + "type": "package", + "package": { + "name": "example-lib", + "version": "1.0.0" + } + } + ], + "problems": null, + "rating": { + "severity": "high" + }, + "risk": {}, + "title": "Example finding" + }, + "relationships": { + "asset": { + "data": { + "id": "20000000-0000-4000-8000-000000000001", + "type": "assets" + }, + "links": { + "related": "https://example.com/assets/example" + } + } + } + }, + { + "id": "00000000-0000-4000-8000-000000000001", + "type": "findings", + "attributes": { + "cause_of_failure": true, + "description": "Synthetic finding for output contract tests.", + "evidence": [], + "finding_type": "sca", + "key": "finding-1", + "locations": [ + { + "type": "package", + "package": { + "name": "example-lib", + "version": "1.1.0" + } + } + ], + "problems": null, + "rating": { + "severity": "high" + }, + "risk": {}, + "title": "Example finding" + } + } + ], + "problemStore": { + "SNYK-EXAMPLE-1": { + "source": "snyk_vuln", + "id": "SNYK-EXAMPLE-1", + "package_name": "example-lib", + "affected_versions": [ + "<2.0.0" + ], + "initially_fixed_in_versions": [ + "2.0.0" + ], + "is_fixable": true + } + }, + "_problemRefs": { + "00000000-0000-4000-8000-000000000002": [ + "SNYK-EXAMPLE-1" + ], + "00000000-0000-4000-8000-000000000001": [ + "SNYK-EXAMPLE-1" + ] + } + } +] diff --git a/internal/presenters/testdata/ufm/toon/sca.toon b/internal/presenters/testdata/ufm/toon/sca.toon new file mode 100644 index 000000000..8667af7b4 --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/sca.toon @@ -0,0 +1,69 @@ +results[1]: + - effectiveSummary: + count: 2 + errors: null + executionState: finished + findings[2]: + - attributes: + cause_of_failure: true + description: Synthetic finding for output contract tests. + evidence[1]: + - path[2]{name,version}: + example-app,1.0.0 + example-lib,1.0.0 + source: dependency_path + finding_type: sca + key: finding-2 + locations[1]{package{name,version},type}: + example-lib,1.0.0,package + problems[1]: + - affected_versions[1]: <2.0.0 + id: SNYK-EXAMPLE-1 + initially_fixed_in_versions[1]: 2.0.0 + is_fixable: true + package_name: example-lib + source: snyk_vuln + rating: + severity: high + risk: + title: Example finding + id: 00000000-0000-4000-8000-000000000002 + relationships: + asset: + data: + id: 20000000-0000-4000-8000-000000000001 + type: assets + links: + related: "https://example.com/assets/example" + type: findings + - attributes: + cause_of_failure: true + description: Synthetic finding for output contract tests. + evidence: [] + finding_type: sca + key: finding-1 + locations[1]{package{name,version},type}: + example-lib,1.1.0,package + problems[1]: + - affected_versions[1]: <2.0.0 + id: SNYK-EXAMPLE-1 + initially_fixed_in_versions[1]: 2.0.0 + is_fixable: true + package_name: example-lib + source: snyk_vuln + rating: + severity: high + risk: + title: Example finding + id: 00000000-0000-4000-8000-000000000001 + type: findings + outcomeReason: null + passFail: fail + rawSummary: + count: 2 + testConfiguration: + scan_config: + sca: + testId: 10000000-0000-4000-8000-000000000001 + testSubject: null + warnings: null \ No newline at end of file diff --git a/internal/presenters/testdata/ufm/toon/secrets.json b/internal/presenters/testdata/ufm/toon/secrets.json new file mode 100644 index 000000000..d032e1ff2 --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/secrets.json @@ -0,0 +1,69 @@ +{ + "results": [ + { + "effectiveSummary": { + "count": 1 + }, + "errors": null, + "executionState": "finished", + "findings": [ + { + "attributes": { + "cause_of_failure": true, + "description": "Synthetic finding for output contract tests.", + "evidence": [], + "finding_type": "secrets", + "key": "finding-3", + "locations": [ + { + "file_path": "config.txt", + "from_line": 7, + "to_line": 9, + "type": "source" + }, + { + "file_path": "src/example.go", + "from_line": 4, + "type": "source" + }, + { + "file_path": "nullable.txt", + "from_line": 2, + "to_line": null, + "type": "source" + } + ], + "policy_modifications": [], + "problems": [ + { + "id": "example-secret-rule", + "name": "Example secret rule", + "source": "snyk_secrets_rule" + } + ], + "rating": { + "severity": "high" + }, + "risk": {}, + "title": "Example finding" + }, + "id": "00000000-0000-4000-8000-000000000003", + "type": "findings" + } + ], + "outcomeReason": null, + "passFail": "fail", + "rawSummary": { + "count": 1 + }, + "testConfiguration": { + "scan_config": { + "secrets": {} + } + }, + "testId": "10000000-0000-4000-8000-000000000002", + "testSubject": null, + "warnings": [] + } + ] +} diff --git a/internal/presenters/testdata/ufm/toon/secrets.testresult.json b/internal/presenters/testdata/ufm/toon/secrets.testresult.json new file mode 100644 index 000000000..9ddd6d4af --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/secrets.testresult.json @@ -0,0 +1,65 @@ +[ + { + "testId": "10000000-0000-4000-8000-000000000002", + "testConfiguration": { + "scan_config": { + "secrets": {} + } + }, + "executionState": "finished", + "passFail": "fail", + "effectiveSummary": { + "count": 1 + }, + "rawSummary": { + "count": 1 + }, + "findingsComplete": true, + "findings": [ + { + "id": "00000000-0000-4000-8000-000000000003", + "type": "findings", + "attributes": { + "cause_of_failure": true, + "description": "Synthetic finding for output contract tests.", + "evidence": [], + "finding_type": "secrets", + "key": "finding-3", + "locations": [ + { + "type": "source", + "file_path": "config.txt", + "from_line": 7, + "to_line": 9 + }, + { + "type": "source", + "file_path": "src/example.go", + "from_line": 4 + }, + { + "type": "source", + "file_path": "nullable.txt", + "from_line": 2, + "to_line": null + } + ], + "problems": [ + { + "source": "snyk_secrets_rule", + "id": "example-secret-rule", + "name": "Example secret rule" + } + ], + "rating": { + "severity": "high" + }, + "risk": {}, + "title": "Example finding", + "policy_modifications": [] + } + } + ], + "warnings": [] + } +] diff --git a/internal/presenters/testdata/ufm/toon/secrets.toon b/internal/presenters/testdata/ufm/toon/secrets.toon new file mode 100644 index 000000000..5d54cba9a --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/secrets.toon @@ -0,0 +1,43 @@ +results[1]: + - effectiveSummary: + count: 1 + errors: null + executionState: finished + findings[1]: + - attributes: + cause_of_failure: true + description: Synthetic finding for output contract tests. + evidence: [] + finding_type: secrets + key: finding-3 + locations[3]: + - file_path: config.txt + from_line: 7 + to_line: 9 + type: source + - file_path: src/example.go + from_line: 4 + type: source + - file_path: nullable.txt + from_line: 2 + to_line: null + type: source + policy_modifications: [] + problems[1]{id,name,source}: + example-secret-rule,Example secret rule,snyk_secrets_rule + rating: + severity: high + risk: + title: Example finding + id: 00000000-0000-4000-8000-000000000003 + type: findings + outcomeReason: null + passFail: fail + rawSummary: + count: 1 + testConfiguration: + scan_config: + secrets: + testId: 10000000-0000-4000-8000-000000000002 + testSubject: null + warnings: [] \ No newline at end of file diff --git a/internal/presenters/testdata/ufm/toon/verify.mjs b/internal/presenters/testdata/ufm/toon/verify.mjs new file mode 100644 index 000000000..f631f9870 --- /dev/null +++ b/internal/presenters/testdata/ufm/toon/verify.mjs @@ -0,0 +1,43 @@ +// Reference-only fixture check. Rewriting requires an explicit --write argument. +import assert from 'node:assert/strict'; +import { readFile, readdir, writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +assert.ok(process.argv[2], 'Usage: node verify.mjs /path/to/reference/dist/index.mjs [--write]'); +assert.ok(process.argv.length === 3 || (process.argv.length === 4 && process.argv[3] === '--write')); +const modulePath = resolve(process.argv[2]); +const metadata = JSON.parse(await readFile(resolve(modulePath, '../../package.json'), 'utf8')); +assert.equal(metadata.name, '@toon-format/toon'); +assert.equal(metadata.version, '4.1.1'); +const { encode, decode } = await import(pathToFileURL(modulePath).href); +const directory = fileURLToPath(new URL('.', import.meta.url)); + +function canonical(value) { + if (Array.isArray(value)) return value.map(canonical); + if (value !== null && typeof value === 'object') { + return Object.fromEntries(Object.entries(value) + .sort(([a], [b]) => Buffer.compare(Buffer.from(a), Buffer.from(b))) + .map(([key, child]) => [key, canonical(child)])); + } + return value; +} + +const inputs = (await readdir(directory)).filter(name => name.endsWith('.testresult.json')).sort(); +assert.ok(inputs.length > 0, 'No contract fixtures found'); +for (const input of inputs) { + const name = input.replace(/\.testresult\.json$/, '.toon'); + const json = await readFile(resolve(directory, name.replace(/\.toon$/, '.json')), 'utf8'); + const expected = JSON.parse(json, (key, value) => { + assert.ok(typeof value !== 'number' || !Number.isInteger(value) || Number.isSafeInteger(value), + `${name}: the reference codec cannot verify an unsafe integer at ${key}`); + return value; + }); + const encoded = encode(canonical(expected), { indentSize: 2, delimiter: ',' }); + assert.deepEqual(decode(encoded, { indentSize: 2, strict: true }), expected, `${name}: encoder round trip`); + if (process.argv[3] === '--write') await writeFile(resolve(directory, name), encoded); + const golden = await readFile(resolve(directory, name), 'utf8'); + assert.equal(encoded, golden, `${name}: bytes`); + assert.deepEqual(decode(golden, { indentSize: 2, strict: true }), expected, `${name}: JSON model`); + console.log(`PASS ${name}: exact bytes and decoded JSON`); +} From ce1bf6ff0606b237ced75f85fdc931b0e264fddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez?= Date: Mon, 7 Sep 2026 12:56:02 +0200 Subject: [PATCH 2/7] chore: add regenerate instructions --- internal/presenters/testdata/ufm/toon/README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/internal/presenters/testdata/ufm/toon/README.md b/internal/presenters/testdata/ufm/toon/README.md index de31f0818..c279c0d3c 100644 --- a/internal/presenters/testdata/ufm/toon/README.md +++ b/internal/presenters/testdata/ufm/toon/README.md @@ -28,10 +28,10 @@ order before rendering; never sort arrays. From the repository root: -```fish +```bash go test ./internal/presenters -run '^Test_UfmTOONContract$' -count=1 -set toon_reference (mktemp -d) +toon_reference=$(mktemp -d) npm pack @toon-format/toon@4.1.1 --pack-destination "$toon_reference" tar -xzf "$toon_reference/toon-format-toon-4.1.1.tgz" -C "$toon_reference" node internal/presenters/testdata/ufm/toon/verify.mjs "$toon_reference/package/dist/index.mjs" @@ -42,4 +42,12 @@ The checks compare UFM extraction with expected JSON, exact TOON bytes and strictly decoded JSON. Do not trim whitespace. The verifier rejects integers outside JavaScript's safe range; cover those in the template renderer tests. -To regenerate, add `--write` to the Node command, review the diff and rerun both checks. +## Regenerate + +After the setup above, regenerate `.toon` files from the expected `.json` files: + +```bash +node internal/presenters/testdata/ufm/toon/verify.mjs "$toon_reference/package/dist/index.mjs" --write +``` + +This only updates `.toon` files. Review the diff and rerun both checks. From 85be588d49a101c571df8402206dc8ebf214e81a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez?= Date: Mon, 7 Sep 2026 12:57:07 +0200 Subject: [PATCH 3/7] test: glob .testresult.json --- .../presenter_ufm_toon_contract_test.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/internal/presenters/presenter_ufm_toon_contract_test.go b/internal/presenters/presenter_ufm_toon_contract_test.go index 8815a21b1..4ad43e6ee 100644 --- a/internal/presenters/presenter_ufm_toon_contract_test.go +++ b/internal/presenters/presenter_ufm_toon_contract_test.go @@ -5,6 +5,8 @@ import ( "encoding/json" "os" "path/filepath" + "sort" + "strings" "testing" "github.com/stretchr/testify/require" @@ -16,9 +18,16 @@ import ( // These fixtures define the JSON input to the future TOON presenter. The pinned // reference codec verifies their .toon counterparts; see testdata/ufm/toon/README.md. func Test_UfmTOONContract(t *testing.T) { - for _, name := range []string{"sca", "secrets", "empty_sca", "empty_secrets", "mixed", "nested", "no_results"} { + fixtureDir := filepath.Join("testdata", "ufm", "toon") + matches, err := filepath.Glob(filepath.Join(fixtureDir, "*.testresult.json")) + require.NoError(t, err) + require.NotEmpty(t, matches, "no contract fixtures found") + sort.Strings(matches) + + for _, inputPath := range matches { + name := strings.TrimSuffix(filepath.Base(inputPath), ".testresult.json") t.Run(name, func(t *testing.T) { - input, err := os.ReadFile(filepath.Join("testdata", "ufm", "toon", name+".testresult.json")) + input, err := os.ReadFile(inputPath) require.NoError(t, err) results, err := ufm.NewSerializableTestResultFromBytes(input) require.NoError(t, err) @@ -47,7 +56,7 @@ func Test_UfmTOONContract(t *testing.T) { } actual, err := json.Marshal(map[string]any{"results": envelope}) require.NoError(t, err) - expected, err := os.ReadFile(filepath.Join("testdata", "ufm", "toon", name+".json")) + expected, err := os.ReadFile(filepath.Join(fixtureDir, name+".json")) require.NoError(t, err) require.Equal(t, decodeTOONContractJSON(t, expected), decodeTOONContractJSON(t, actual)) }) From b6a56bd0a9f8b7e7d462e0b99de104822155a9d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez?= Date: Mon, 7 Sep 2026 13:42:15 +0200 Subject: [PATCH 4/7] test: simplify toon fixture --- .../presenter_ufm_toon_contract_test.go | 4 +- .../presenters/testdata/ufm/toon/README.md | 25 +++++------ .../presenters/testdata/ufm/toon/verify.mjs | 43 ------------------- 3 files changed, 13 insertions(+), 59 deletions(-) delete mode 100644 internal/presenters/testdata/ufm/toon/verify.mjs diff --git a/internal/presenters/presenter_ufm_toon_contract_test.go b/internal/presenters/presenter_ufm_toon_contract_test.go index 4ad43e6ee..fd4dcf037 100644 --- a/internal/presenters/presenter_ufm_toon_contract_test.go +++ b/internal/presenters/presenter_ufm_toon_contract_test.go @@ -15,8 +15,8 @@ import ( "github.com/snyk/go-application-framework/pkg/utils/ufm" ) -// These fixtures define the JSON input to the future TOON presenter. The pinned -// reference codec verifies their .toon counterparts; see testdata/ufm/toon/README.md. +// These fixtures define the JSON input to the future TOON presenter. +// See testdata/ufm/toon/README.md for reference TOON commands. func Test_UfmTOONContract(t *testing.T) { fixtureDir := filepath.Join("testdata", "ufm", "toon") matches, err := filepath.Glob(filepath.Join(fixtureDir, "*.testresult.json")) diff --git a/internal/presenters/testdata/ufm/toon/README.md b/internal/presenters/testdata/ufm/toon/README.md index c279c0d3c..870b284f2 100644 --- a/internal/presenters/testdata/ufm/toon/README.md +++ b/internal/presenters/testdata/ufm/toon/README.md @@ -26,28 +26,25 @@ order before rendering; never sort arrays. ## Verify -From the repository root: +Check UFM extraction against expected JSON from the repository root: ```bash go test ./internal/presenters -run '^Test_UfmTOONContract$' -count=1 - -toon_reference=$(mktemp -d) -npm pack @toon-format/toon@4.1.1 --pack-destination "$toon_reference" -tar -xzf "$toon_reference/toon-format-toon-4.1.1.tgz" -C "$toon_reference" -node internal/presenters/testdata/ufm/toon/verify.mjs "$toon_reference/package/dist/index.mjs" ``` -`@toon-format/toon@4.1.1` is only for generating and verifying fixtures. -The checks compare UFM extraction with expected JSON, exact TOON bytes and -strictly decoded JSON. Do not trim whitespace. The verifier rejects integers -outside JavaScript's safe range; cover those in the template renderer tests. - ## Regenerate -After the setup above, regenerate `.toon` files from the expected `.json` files: +From this directory, use the [TOON reference CLI](https://toonformat.dev/cli/). +Replace `sca` with the case to regenerate: ```bash -node internal/presenters/testdata/ufm/toon/verify.mjs "$toon_reference/package/dist/index.mjs" --write +jq -S . sca.json | npx --yes @toon-format/cli@4.1.1 --encode -o /tmp/sca.toon +printf '%s' "$(< /tmp/sca.toon)" > /tmp/sca.toon +cmp sca.toon /tmp/sca.toon ``` -This only updates `.toon` files. Review the diff and rerun both checks. +The `printf` command removes the TOON reference CLI's final newline. For contract changes, copy +`/tmp/sca.toon` to `sca.toon`, review the diff and rerun the Go test. + +Use the TOON reference CLI only for fixtures. Integers outside JavaScript's safe range need +separate template renderer tests. diff --git a/internal/presenters/testdata/ufm/toon/verify.mjs b/internal/presenters/testdata/ufm/toon/verify.mjs deleted file mode 100644 index f631f9870..000000000 --- a/internal/presenters/testdata/ufm/toon/verify.mjs +++ /dev/null @@ -1,43 +0,0 @@ -// Reference-only fixture check. Rewriting requires an explicit --write argument. -import assert from 'node:assert/strict'; -import { readFile, readdir, writeFile } from 'node:fs/promises'; -import { resolve } from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; - -assert.ok(process.argv[2], 'Usage: node verify.mjs /path/to/reference/dist/index.mjs [--write]'); -assert.ok(process.argv.length === 3 || (process.argv.length === 4 && process.argv[3] === '--write')); -const modulePath = resolve(process.argv[2]); -const metadata = JSON.parse(await readFile(resolve(modulePath, '../../package.json'), 'utf8')); -assert.equal(metadata.name, '@toon-format/toon'); -assert.equal(metadata.version, '4.1.1'); -const { encode, decode } = await import(pathToFileURL(modulePath).href); -const directory = fileURLToPath(new URL('.', import.meta.url)); - -function canonical(value) { - if (Array.isArray(value)) return value.map(canonical); - if (value !== null && typeof value === 'object') { - return Object.fromEntries(Object.entries(value) - .sort(([a], [b]) => Buffer.compare(Buffer.from(a), Buffer.from(b))) - .map(([key, child]) => [key, canonical(child)])); - } - return value; -} - -const inputs = (await readdir(directory)).filter(name => name.endsWith('.testresult.json')).sort(); -assert.ok(inputs.length > 0, 'No contract fixtures found'); -for (const input of inputs) { - const name = input.replace(/\.testresult\.json$/, '.toon'); - const json = await readFile(resolve(directory, name.replace(/\.toon$/, '.json')), 'utf8'); - const expected = JSON.parse(json, (key, value) => { - assert.ok(typeof value !== 'number' || !Number.isInteger(value) || Number.isSafeInteger(value), - `${name}: the reference codec cannot verify an unsafe integer at ${key}`); - return value; - }); - const encoded = encode(canonical(expected), { indentSize: 2, delimiter: ',' }); - assert.deepEqual(decode(encoded, { indentSize: 2, strict: true }), expected, `${name}: encoder round trip`); - if (process.argv[3] === '--write') await writeFile(resolve(directory, name), encoded); - const golden = await readFile(resolve(directory, name), 'utf8'); - assert.equal(encoded, golden, `${name}: bytes`); - assert.deepEqual(decode(golden, { indentSize: 2, strict: true }), expected, `${name}: JSON model`); - console.log(`PASS ${name}: exact bytes and decoded JSON`); -} From 3f1c8ae02009def68ce06e2fd4cbd830f358abfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez?= Date: Mon, 7 Sep 2026 15:03:07 +0200 Subject: [PATCH 5/7] chore: remove ufm test --- .../presenter_ufm_toon_contract_test.go | 73 ----- .../ufm/toon/empty_sca.testresult.json | 19 -- .../ufm/toon/empty_secrets.testresult.json | 20 -- .../testdata/ufm/toon/mixed.testresult.json | 250 ------------------ .../testdata/ufm/toon/nested.testresult.json | 61 ----- .../ufm/toon/no_results.testresult.json | 1 - .../testdata/ufm/toon/sca.testresult.json | 120 --------- .../testdata/ufm/toon/secrets.testresult.json | 65 ----- 8 files changed, 609 deletions(-) delete mode 100644 internal/presenters/presenter_ufm_toon_contract_test.go delete mode 100644 internal/presenters/testdata/ufm/toon/empty_sca.testresult.json delete mode 100644 internal/presenters/testdata/ufm/toon/empty_secrets.testresult.json delete mode 100644 internal/presenters/testdata/ufm/toon/mixed.testresult.json delete mode 100644 internal/presenters/testdata/ufm/toon/nested.testresult.json delete mode 100644 internal/presenters/testdata/ufm/toon/no_results.testresult.json delete mode 100644 internal/presenters/testdata/ufm/toon/sca.testresult.json delete mode 100644 internal/presenters/testdata/ufm/toon/secrets.testresult.json diff --git a/internal/presenters/presenter_ufm_toon_contract_test.go b/internal/presenters/presenter_ufm_toon_contract_test.go deleted file mode 100644 index fd4dcf037..000000000 --- a/internal/presenters/presenter_ufm_toon_contract_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package presenters_test - -import ( - "bytes" - "encoding/json" - "os" - "path/filepath" - "sort" - "strings" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/snyk/go-application-framework/pkg/apiclients/testapi" - "github.com/snyk/go-application-framework/pkg/utils/ufm" -) - -// These fixtures define the JSON input to the future TOON presenter. -// See testdata/ufm/toon/README.md for reference TOON commands. -func Test_UfmTOONContract(t *testing.T) { - fixtureDir := filepath.Join("testdata", "ufm", "toon") - matches, err := filepath.Glob(filepath.Join(fixtureDir, "*.testresult.json")) - require.NoError(t, err) - require.NotEmpty(t, matches, "no contract fixtures found") - sort.Strings(matches) - - for _, inputPath := range matches { - name := strings.TrimSuffix(filepath.Base(inputPath), ".testresult.json") - t.Run(name, func(t *testing.T) { - input, err := os.ReadFile(inputPath) - require.NoError(t, err) - results, err := ufm.NewSerializableTestResultFromBytes(input) - require.NoError(t, err) - - envelope := make([]map[string]any, 0, len(results)) - for _, result := range results { - findings, complete, findingsErr := result.Findings(t.Context()) - require.NoError(t, findingsErr) - require.True(t, complete, "incomplete findings cannot define a successful golden") - if findings == nil { - findings = []testapi.FindingData{} - } - envelope = append(envelope, map[string]any{ - "testId": result.GetTestID(), - "testConfiguration": result.GetTestConfiguration(), - "testSubject": result.Get(testapi.TestResultTestSubject), - "executionState": result.GetExecutionState(), - "passFail": result.GetPassFail(), - "outcomeReason": result.GetOutcomeReason(), - "errors": result.GetErrors(), - "warnings": result.GetWarnings(), - "rawSummary": result.Get(testapi.TestResultRawSummary), - "effectiveSummary": result.GetEffectiveSummary(), - "findings": findings, - }) - } - actual, err := json.Marshal(map[string]any{"results": envelope}) - require.NoError(t, err) - expected, err := os.ReadFile(filepath.Join(fixtureDir, name+".json")) - require.NoError(t, err) - require.Equal(t, decodeTOONContractJSON(t, expected), decodeTOONContractJSON(t, actual)) - }) - } -} - -func decodeTOONContractJSON(t *testing.T, data []byte) any { - t.Helper() - decoder := json.NewDecoder(bytes.NewReader(data)) - decoder.UseNumber() - var value any - require.NoError(t, decoder.Decode(&value)) - return value -} diff --git a/internal/presenters/testdata/ufm/toon/empty_sca.testresult.json b/internal/presenters/testdata/ufm/toon/empty_sca.testresult.json deleted file mode 100644 index 2409da348..000000000 --- a/internal/presenters/testdata/ufm/toon/empty_sca.testresult.json +++ /dev/null @@ -1,19 +0,0 @@ -[ - { - "testId": "10000000-0000-4000-8000-000000000003", - "testConfiguration": { - "scan_config": { - "sca": {} - } - }, - "executionState": "finished", - "passFail": "pass", - "effectiveSummary": { - "count": 0 - }, - "rawSummary": { - "count": 0 - }, - "findingsComplete": true - } -] diff --git a/internal/presenters/testdata/ufm/toon/empty_secrets.testresult.json b/internal/presenters/testdata/ufm/toon/empty_secrets.testresult.json deleted file mode 100644 index 7e2de5493..000000000 --- a/internal/presenters/testdata/ufm/toon/empty_secrets.testresult.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "testId": "10000000-0000-4000-8000-000000000004", - "testConfiguration": { - "scan_config": { - "secrets": {} - } - }, - "executionState": "finished", - "passFail": "pass", - "effectiveSummary": { - "count": 0 - }, - "rawSummary": { - "count": 0 - }, - "findingsComplete": true, - "findings": [] - } -] diff --git a/internal/presenters/testdata/ufm/toon/mixed.testresult.json b/internal/presenters/testdata/ufm/toon/mixed.testresult.json deleted file mode 100644 index 7dedb667e..000000000 --- a/internal/presenters/testdata/ufm/toon/mixed.testresult.json +++ /dev/null @@ -1,250 +0,0 @@ -[ - { - "testId": "10000000-0000-4000-8000-000000000002", - "testConfiguration": { - "scan_config": { - "secrets": {} - } - }, - "executionState": "finished", - "passFail": "fail", - "effectiveSummary": { - "count": 1 - }, - "rawSummary": { - "count": 2 - }, - "findingsComplete": true, - "findings": [ - { - "id": "00000000-0000-4000-8000-000000000003", - "type": "findings", - "attributes": { - "cause_of_failure": true, - "description": "Synthetic finding for output contract tests.", - "evidence": [], - "finding_type": "secrets", - "key": "finding-3", - "locations": [ - { - "type": "source", - "file_path": "config.txt", - "from_line": 7, - "to_line": 9 - }, - { - "type": "source", - "file_path": "src/example.go", - "from_line": 4 - }, - { - "type": "source", - "file_path": "nullable.txt", - "from_line": 2, - "to_line": null - } - ], - "problems": [ - { - "source": "snyk_secrets_rule", - "id": "example-secret-rule", - "name": "Example secret rule" - } - ], - "rating": { - "severity": "high" - }, - "risk": {}, - "title": "Example finding", - "policy_modifications": [] - } - }, - { - "id": "00000000-0000-4000-8000-000000000004", - "type": "findings", - "attributes": { - "cause_of_failure": true, - "description": "First line\nSecond\tline \\ quoted \"value\", colon: and Unicode café ☃", - "evidence": null, - "finding_type": "sast", - "key": "finding-4", - "locations": [ - { - "type": "source", - "file_path": "folder/\"quoted\",file.go", - "from_line": 1 - } - ], - "problems": [ - { - "source": "future_problem", - "id": "example-future", - "details": { - "z": null, - "a": [ - null, - false, - "null", - "001", - "", - {}, - [ - "x", - "y" - ], - { - "count": 42, - "fraction": 1.25 - } - ], - "#key": "#not a comment", - "colon:key": " trailing " - } - } - ], - "rating": { - "severity": "high" - }, - "risk": {}, - "title": "Example finding" - } - } - ], - "warnings": [] - }, - { - "testId": "10000000-0000-4000-8000-000000000003", - "testConfiguration": { - "scan_config": { - "sca": {} - } - }, - "executionState": "finished", - "passFail": "pass", - "effectiveSummary": { - "count": 0 - }, - "rawSummary": { - "count": 0 - }, - "findingsComplete": true - }, - { - "testId": "10000000-0000-4000-8000-000000000001", - "testConfiguration": { - "scan_config": { - "sca": {} - } - }, - "executionState": "finished", - "passFail": "fail", - "effectiveSummary": { - "count": 2 - }, - "rawSummary": { - "count": 2 - }, - "findingsComplete": true, - "findings": [ - { - "id": "00000000-0000-4000-8000-000000000002", - "type": "findings", - "attributes": { - "cause_of_failure": true, - "description": "Synthetic finding for output contract tests.", - "evidence": [ - { - "source": "dependency_path", - "path": [ - { - "name": "example-app", - "version": "1.0.0" - }, - { - "name": "example-lib", - "version": "1.0.0" - } - ] - } - ], - "finding_type": "sca", - "key": "finding-2", - "locations": [ - { - "type": "package", - "package": { - "name": "example-lib", - "version": "1.0.0" - } - } - ], - "problems": null, - "rating": { - "severity": "high" - }, - "risk": {}, - "title": "Example finding" - }, - "relationships": { - "asset": { - "data": { - "id": "20000000-0000-4000-8000-000000000001", - "type": "assets" - }, - "links": { - "related": "https://example.com/assets/example" - } - } - } - }, - { - "id": "00000000-0000-4000-8000-000000000001", - "type": "findings", - "attributes": { - "cause_of_failure": true, - "description": "Synthetic finding for output contract tests.", - "evidence": [], - "finding_type": "sca", - "key": "finding-1", - "locations": [ - { - "type": "package", - "package": { - "name": "example-lib", - "version": "1.1.0" - } - } - ], - "problems": null, - "rating": { - "severity": "high" - }, - "risk": {}, - "title": "Example finding" - } - } - ], - "problemStore": { - "SNYK-EXAMPLE-1": { - "source": "snyk_vuln", - "id": "SNYK-EXAMPLE-1", - "package_name": "example-lib", - "affected_versions": [ - "<2.0.0" - ], - "initially_fixed_in_versions": [ - "2.0.0" - ], - "is_fixable": true - } - }, - "_problemRefs": { - "00000000-0000-4000-8000-000000000002": [ - "SNYK-EXAMPLE-1" - ], - "00000000-0000-4000-8000-000000000001": [ - "SNYK-EXAMPLE-1" - ] - } - } -] diff --git a/internal/presenters/testdata/ufm/toon/nested.testresult.json b/internal/presenters/testdata/ufm/toon/nested.testresult.json deleted file mode 100644 index af30f9d00..000000000 --- a/internal/presenters/testdata/ufm/toon/nested.testresult.json +++ /dev/null @@ -1,61 +0,0 @@ -[ - { - "executionState": "finished", - "findingsComplete": true, - "findings": [ - { - "id": "00000000-0000-4000-8000-000000000004", - "type": "findings", - "attributes": { - "cause_of_failure": true, - "description": "First line\nSecond\tline \\ quoted \"value\", colon: and Unicode café ☃", - "evidence": null, - "finding_type": "sast", - "key": "finding-4", - "locations": [ - { - "type": "source", - "file_path": "folder/\"quoted\",file.go", - "from_line": 1 - } - ], - "problems": [ - { - "source": "future_problem", - "id": "example-future", - "details": { - "z": null, - "a": [ - null, - false, - "null", - "001", - "", - {}, - [ - "x", - "y" - ], - { - "count": 42, - "fraction": 1.25 - } - ], - "#key": "#not a comment", - "colon:key": " trailing " - } - } - ], - "rating": { - "severity": "high" - }, - "risk": {}, - "title": "Example finding" - } - }, - { - "type": "findings" - } - ] - } -] diff --git a/internal/presenters/testdata/ufm/toon/no_results.testresult.json b/internal/presenters/testdata/ufm/toon/no_results.testresult.json deleted file mode 100644 index fe51488c7..000000000 --- a/internal/presenters/testdata/ufm/toon/no_results.testresult.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/internal/presenters/testdata/ufm/toon/sca.testresult.json b/internal/presenters/testdata/ufm/toon/sca.testresult.json deleted file mode 100644 index c1de71823..000000000 --- a/internal/presenters/testdata/ufm/toon/sca.testresult.json +++ /dev/null @@ -1,120 +0,0 @@ -[ - { - "testId": "10000000-0000-4000-8000-000000000001", - "testConfiguration": { - "scan_config": { - "sca": {} - } - }, - "executionState": "finished", - "passFail": "fail", - "effectiveSummary": { - "count": 2 - }, - "rawSummary": { - "count": 2 - }, - "findingsComplete": true, - "findings": [ - { - "id": "00000000-0000-4000-8000-000000000002", - "type": "findings", - "attributes": { - "cause_of_failure": true, - "description": "Synthetic finding for output contract tests.", - "evidence": [ - { - "source": "dependency_path", - "path": [ - { - "name": "example-app", - "version": "1.0.0" - }, - { - "name": "example-lib", - "version": "1.0.0" - } - ] - } - ], - "finding_type": "sca", - "key": "finding-2", - "locations": [ - { - "type": "package", - "package": { - "name": "example-lib", - "version": "1.0.0" - } - } - ], - "problems": null, - "rating": { - "severity": "high" - }, - "risk": {}, - "title": "Example finding" - }, - "relationships": { - "asset": { - "data": { - "id": "20000000-0000-4000-8000-000000000001", - "type": "assets" - }, - "links": { - "related": "https://example.com/assets/example" - } - } - } - }, - { - "id": "00000000-0000-4000-8000-000000000001", - "type": "findings", - "attributes": { - "cause_of_failure": true, - "description": "Synthetic finding for output contract tests.", - "evidence": [], - "finding_type": "sca", - "key": "finding-1", - "locations": [ - { - "type": "package", - "package": { - "name": "example-lib", - "version": "1.1.0" - } - } - ], - "problems": null, - "rating": { - "severity": "high" - }, - "risk": {}, - "title": "Example finding" - } - } - ], - "problemStore": { - "SNYK-EXAMPLE-1": { - "source": "snyk_vuln", - "id": "SNYK-EXAMPLE-1", - "package_name": "example-lib", - "affected_versions": [ - "<2.0.0" - ], - "initially_fixed_in_versions": [ - "2.0.0" - ], - "is_fixable": true - } - }, - "_problemRefs": { - "00000000-0000-4000-8000-000000000002": [ - "SNYK-EXAMPLE-1" - ], - "00000000-0000-4000-8000-000000000001": [ - "SNYK-EXAMPLE-1" - ] - } - } -] diff --git a/internal/presenters/testdata/ufm/toon/secrets.testresult.json b/internal/presenters/testdata/ufm/toon/secrets.testresult.json deleted file mode 100644 index 9ddd6d4af..000000000 --- a/internal/presenters/testdata/ufm/toon/secrets.testresult.json +++ /dev/null @@ -1,65 +0,0 @@ -[ - { - "testId": "10000000-0000-4000-8000-000000000002", - "testConfiguration": { - "scan_config": { - "secrets": {} - } - }, - "executionState": "finished", - "passFail": "fail", - "effectiveSummary": { - "count": 1 - }, - "rawSummary": { - "count": 1 - }, - "findingsComplete": true, - "findings": [ - { - "id": "00000000-0000-4000-8000-000000000003", - "type": "findings", - "attributes": { - "cause_of_failure": true, - "description": "Synthetic finding for output contract tests.", - "evidence": [], - "finding_type": "secrets", - "key": "finding-3", - "locations": [ - { - "type": "source", - "file_path": "config.txt", - "from_line": 7, - "to_line": 9 - }, - { - "type": "source", - "file_path": "src/example.go", - "from_line": 4 - }, - { - "type": "source", - "file_path": "nullable.txt", - "from_line": 2, - "to_line": null - } - ], - "problems": [ - { - "source": "snyk_secrets_rule", - "id": "example-secret-rule", - "name": "Example secret rule" - } - ], - "rating": { - "severity": "high" - }, - "risk": {}, - "title": "Example finding", - "policy_modifications": [] - } - } - ], - "warnings": [] - } -] From de7df101ed2464c0c54881fa8642d2e1255c85ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20L=C3=B3pez=20L=C3=B3pez?= Date: Mon, 7 Sep 2026 15:05:19 +0200 Subject: [PATCH 6/7] chore: clean README.md --- .../presenters/testdata/ufm/toon/README.md | 31 ++++++------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/internal/presenters/testdata/ufm/toon/README.md b/internal/presenters/testdata/ufm/toon/README.md index 870b284f2..7ba65e5c2 100644 --- a/internal/presenters/testdata/ufm/toon/README.md +++ b/internal/presenters/testdata/ufm/toon/README.md @@ -1,19 +1,16 @@ # UFM TOON contract -Synthetic fixtures: UFM input (`*.testresult.json`), expected JSON (`*.json`) -and exact TOON output (`*.toon`). Use GAF's template engine to render TOON. +Synthetic fixtures: JSON input (`*.json`) and exact TOON output (`*.toon`). +Use GAF's template engine to render TOON. No production encoder dependency. ## Contract - Use `{"results": [...]}`. Keep the envelope fields shown in the JSON fixtures, - including both summaries. Prefer `Get(field)` where supported; use dedicated - getters otherwise. Missing or nil envelope values become `null`; complete + including both summaries. Missing or nil envelope values become `null`; empty findings become `[]`. -- Read findings through `TestResult.Findings(ctx)` to restore stored problems. - Return an error before writing if extraction fails or `complete == false`. -- Preserve the returned `FindingData` JSON: tags, custom marshalers, nested - fields, omission/null semantics and numeric types. Keep every finding and +- Preserve the `FindingData` JSON: nested fields, omission/null semantics + and numeric types. Keep every finding and array order; do not aggregate or generate summary text. - Keep `from_line` and optional `to_line` as supplied, including explicit null. Do not rename them or fill in a missing end line. @@ -24,17 +21,9 @@ use two-space indentation, comma delimiter, UTF-8 and LF separators. No BOM, trailing spaces or final newline. Sort object keys recursively by UTF-8 byte order before rendering; never sort arrays. -## Verify - -Check UFM extraction against expected JSON from the repository root: - -```bash -go test ./internal/presenters -run '^Test_UfmTOONContract$' -count=1 -``` - ## Regenerate -From this directory, use the [TOON reference CLI](https://toonformat.dev/cli/). +From this directory, use the [@toon-format/cli](https://toonformat.dev/cli/). Replace `sca` with the case to regenerate: ```bash @@ -43,8 +32,8 @@ printf '%s' "$(< /tmp/sca.toon)" > /tmp/sca.toon cmp sca.toon /tmp/sca.toon ``` -The `printf` command removes the TOON reference CLI's final newline. For contract changes, copy -`/tmp/sca.toon` to `sca.toon`, review the diff and rerun the Go test. +The `printf` command removes the `@toon-format/cli`'s final newline. +For contract changes, copy `/tmp/sca.toon` to `sca.toon` and review the diff. -Use the TOON reference CLI only for fixtures. Integers outside JavaScript's safe range need -separate template renderer tests. +Use the `@toon-format/cli` only for fixtures. Integers outside JavaScript's +safe range need separate template renderer tests. From 80b73d110cee7d4a030730adc2f1a8a99607383a Mon Sep 17 00:00:00 2001 From: "snyk-prodsec-orb[bot]" Date: Mon, 7 Sep 2026 13:13:57 +0000 Subject: [PATCH 7/7] fix: remediate high-and-above vulnerabilities reported by Snyk Open Source Applied by snyk fix --agentic via the Snyk ProdSec CircleCI orb, from feat/CLI-1838 at de7df101ed2464c0c54881fa8642d2e1255c85ac. These changes are generated. Review them before merging. --- go.mod | 10 +++++----- go.sum | 10 ++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index cb15265c0..ba8ef4110 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( require ( github.com/cenkalti/backoff/v5 v5.0.2 github.com/go-git/go-billy/v5 v5.9.0 - github.com/go-git/go-git/v5 v5.19.1 + github.com/go-git/go-git/v5 v5.19.2 github.com/gofrs/flock v0.12.1 github.com/manifoldco/promptui v0.9.0 github.com/mattn/go-isatty v0.0.20 @@ -36,11 +36,11 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible github.com/snyk/error-catalog-golang-public v0.0.0-20260806122555-28dc45bbbde6 github.com/subosito/gotenv v1.6.0 - golang.org/x/mod v0.37.0 + golang.org/x/mod v0.38.0 golang.org/x/net v0.57.0 golang.org/x/sync v0.22.0 golang.org/x/term v0.45.0 - golang.org/x/text v0.40.0 + golang.org/x/text v0.41.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -117,9 +117,9 @@ require ( github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.54.0 // indirect + golang.org/x/crypto v0.56.0 // indirect golang.org/x/sys v0.47.0 // indirect - golang.org/x/tools v0.47.0 // indirect + golang.org/x/tools v0.48.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index e75986998..d5a834515 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,8 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00= github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ= +github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY= +github.com/go-git/go-git/v5 v5.19.2/go.mod h1:QqCBE1EFN5ddFmrliLQ3/ntRCUjZU3EJuwuB/jWEHjk= github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= @@ -304,12 +306,16 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= +golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -368,12 +374,16 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=