Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ package text

import (
"fmt"
"maps"
"reflect"
"slices"
"text/tabwriter"

"github.com/notaryproject/notation-go"
Expand Down Expand Up @@ -66,8 +68,8 @@ func printUserMetadataIfPresent(printer *output.Printer, outcome *notation.Verif
func printUserMetadataMap(printer *output.Printer, metadata map[string]string) error {
tw := tabwriter.NewWriter(printer, 0, 0, 3, ' ', 0)
fmt.Fprintln(tw, "\nKEY\tVALUE\t")
for k, v := range metadata {
fmt.Fprintf(tw, "%v\t%v\t\n", k, v)
for _, k := range slices.Sorted(maps.Keys(metadata)) {
fmt.Fprintf(tw, "%v\t%v\t\n", k, metadata[k])
}
return tw.Flush()
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ package text
import (
"bytes"
"encoding/json"
"maps"
"slices"
"strings"
"testing"

"github.com/notaryproject/notation-core-go/signature"
Expand Down Expand Up @@ -55,3 +58,69 @@ func TestPrintMetadataIfPresent(t *testing.T) {
}
})
}

func TestUserMetadataIsSortedByKey(t *testing.T) {
annotations := map[string]string{
"org.example.zone": "us-east",
"org.example.alpha": "1",
"io.wabbit.buildTime": "2024-01-01",
"io.wabbit.buildId": "123",
"com.example.team": "platform",
"a": "first",
"z": "last",
"m": "middle",
}
payloadBytes, err := json.Marshal(&envelope.Payload{
TargetArtifact: ocispec.Descriptor{Annotations: annotations},
})
if err != nil {
t.Fatalf("failed to marshal payload: %v", err)
}
outcome := &notation.VerificationOutcome{
EnvelopeContent: &signature.EnvelopeContent{
Payload: signature.Payload{Content: payloadBytes},
},
}

want := slices.Sorted(maps.Keys(annotations))
// Go randomizes map iteration order, so a single run can be sorted by
// chance. Render repeatedly to make the check meaningful.
for i := 0; i < 20; i++ {
buf := bytes.Buffer{}
h := NewVerifyHandler(output.NewPrinter(&buf, &buf))
h.OnVerifySucceeded([]*notation.VerificationOutcome{outcome}, "localhost:5000/net-monitor@sha256:abcd")
if err := h.Render(); err != nil {
t.Fatalf("Render() error: %v", err)
}
got := metadataKeys(t, buf.String())
if !slices.Equal(got, want) {
t.Fatalf("run %d: user metadata keys are not sorted: got %v, want %v", i, got, want)
}
}
}

// metadataKeys returns the key column of the user metadata table printed by
// the verify handler. It fails the test when the table is missing so that an
// empty result cannot be mistaken for sorted output.
func metadataKeys(t *testing.T, out string) []string {
t.Helper()
if !strings.Contains(out, "Successfully verified signature for") {
t.Fatalf("verification success message missing from output: %q", out)
}
lines := strings.Split(out, "\n")
header := slices.IndexFunc(lines, func(line string) bool {
return strings.HasPrefix(line, "KEY")
})
if header == -1 {
t.Fatalf("user metadata table missing from output: %q", out)
}
var keys []string
for _, line := range lines[header+1:] {
fields := strings.Fields(line)
if len(fields) == 0 {
continue
}
keys = append(keys, fields[0])
}
return keys
}