<commit_before>package signature

import (
	"bytes"
	"encoding/json"
	"os"
	"path/filepath"
	"testing"

	"github.com/containers/image/v5/directory"
	"github.com/containers/image/v5/docker"
	perrors "github.com/pkg/errors"

	// this import is needed  where we use the "atomic" transport in TestPolicyUnmarshalJSON
	_ "github.com/containers/image/v5/openshift"
	"github.com/containers/image/v5/types"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type mSI map[string]interface{} // To minimize typing the long name

// A short-hand way to get a JSON object field value or panic. No error handling done, we know
// what we are working with, a panic in a test is good enough, and fitting test cases on a single line
// is a priority.
func x(m mSI, fields ...string) mSI {
	for _, field := range fields {
		// Not .(mSI) because type assertion of an unnamed type to a named type always fails (the types
		// are not "identical"), but the assignment is fine because they are "assignable".
		m = m[field].(map[string]interface{})
	}
	return m
}

// policyFixtureContents is a data structure equal to the contents of "fixtures/policy.json"
var policyFixtureContents = &Policy{
	Default: PolicyRequirements{NewPRReject()},
	Transports: map[string]PolicyTransportScopes{
		"dir": {
			"": PolicyRequirements{NewPRInsecureAcceptAnything()},
		},
		"docker": {
			"example.com/playground": {
				NewPRInsecureAcceptAnything(),
			},
			"example.com/production": {
				xNewPRSignedByKeyPath(SBKeyTypeGPGKeys,
					"/keys/employee-gpg-keyring",
					NewPRMMatchRepoDigestOrExact()),
			},
			"example.com/hardened": {
				xNewPRSignedByKeyPath(SBKeyTypeGPGKeys,
					"/keys/employee-gpg-keyring",
					NewPRMMatchRepository()),
				xNewPRSignedByKeyPath(SBKeyTypeSignedByGPGKeys,
					"/keys/public-key-signing-gpg-keyring",
					NewPRMMatchExact()),
				xNewPRSignedBaseLayer(xNewPRMExactRepository("registry.access.redhat.com/rhel7/rhel")),
			},
			"example.com/hardened-x509": {
				xNewPRSignedByKeyPath(SBKeyTypeX509Certificates,
					"/keys/employee-cert-file",
					NewPRMMatchRepository()),
				xNewPRSignedByKeyPath(SBKeyTypeSignedByX509CAs,
					"/keys/public-key-signing-ca-file",
					NewPRMMatchRepoDigestOrExact()),
			},
			"registry.access.redhat.com": {
				xNewPRSignedByKeyPath(SBKeyTypeSignedByGPGKeys,
					"/keys/RH-key-signing-key-gpg-keyring",
					NewPRMMatchRepoDigestOrExact()),
			},
			"private-mirror:5000/vendor-mirror": {
				xNewPRSignedByKeyPath(SBKeyTypeGPGKeys,
					"/keys/vendor-gpg-keyring",
					xNewPRMRemapIdentity("private-mirror:5000/vendor-mirror", "vendor.example.com")),
			},
			"*.access.redhat.com": {
				xNewPRSignedByKeyPath(SBKeyTypeSignedByGPGKeys,
					"/keys/RH-key-signing-key-gpg-keyring",
					NewPRMMatchRepoDigestOrExact()),
			},
			"*.redhat.com": {
				xNewPRSignedByKeyPath(SBKeyTypeSignedByGPGKeys,
					"/keys/RH-key-signing-key-gpg-keyring",
					NewPRMMatchRepoDigestOrExact()),
			},
			"*.com": {
				xNewPRSignedByKeyPath(SBKeyTypeSignedByGPGKeys,
					"/keys/RH-key-signing-key-gpg-keyring",
					NewPRMMatchRepoDigestOrExact()),
			},
			"bogus/key-data-example": {
				xNewPRSignedByKeyData(SBKeyTypeSignedByGPGKeys,
					[]byte("nonsense"),
					NewPRMMatchRepoDigestOrExact()),
			},
			"bogus/signed-identity-example": {
				xNewPRSignedBaseLayer(xNewPRMExactReference("registry.access.redhat.com/rhel7/rhel:latest")),
			},
			"example.com/sigstore/key-data-example": {
				xNewPRSigstoreSignedKeyData([]byte("nonsense"),
					NewPRMMatchRepoDigestOrExact()),
			},
			"example.com/sigstore/key-path-example": {
				xNewPRSigstoreSignedKeyPath("/keys/public-key",
					NewPRMMatchRepository()),
			},
		},
	},
}

func TestInvalidPolicyFormatError(t *testing.T) {
	// A stupid test just to keep code coverage
	s := "test"
	err := InvalidPolicyFormatError(s)
	assert.Equal(t, s, err.Error())
}

func TestDefaultPolicy(t *testing.T) {
	// We can't test the actual systemDefaultPolicyPath, so override.
	// TestDefaultPolicyPath below tests that we handle the overrides and defaults
	// correctly.

	// Success
	policy, err := DefaultPolicy(&types.SystemContext{SignaturePolicyPath: "./fixtures/policy.json"})
	require.NoError(t, err)
	assert.Equal(t, policyFixtureContents, policy)

	for _, path := range []string{
		"/this/does/not/exist", // Error reading file
		"/dev/null",            // A failure case; most are tested in the individual method unit tests.
	} {
		policy, err := DefaultPolicy(&types.SystemContext{SignaturePolicyPath: path})
		assert.Error(t, err)
		assert.Nil(t, policy)
	}
}

func TestDefaultPolicyPath(t *testing.T) {
	const nondefaultPath = "/this/is/not/the/default/path.json"
	const variableReference = "$HOME"
	const rootPrefix = "/root/prefix"
	tempHome := t.TempDir()
	userDefaultPolicyPath := filepath.Join(tempHome, userPolicyFile)

	for _, c := range []struct {
		sys             *types.SystemContext
		userfilePresent bool
		expected        string
	}{
		// The common case
		{nil, false, systemDefaultPolicyPath},
		// There is a context, but it does not override the path.
		{&types.SystemContext{}, false, systemDefaultPolicyPath},
		// Path overridden
		{&types.SystemContext{SignaturePolicyPath: nondefaultPath}, false, nondefaultPath},
		// Root overridden
		{
			&types.SystemContext{RootForImplicitAbsolutePaths: rootPrefix},
			false,
			filepath.Join(rootPrefix, systemDefaultPolicyPath),
		},
		// Empty context and user policy present
		{&types.SystemContext{}, true, userDefaultPolicyPath},
		// Only user policy present
		{nil, true, userDefaultPolicyPath},
		// Context signature path and user policy present
		{
			&types.SystemContext{
				SignaturePolicyPath: nondefaultPath,
			},
			true,
			nondefaultPath,
		},
		// Root and user policy present
		{
			&types.SystemContext{
				RootForImplicitAbsolutePaths: rootPrefix,
			},
			true,
			userDefaultPolicyPath,
		},
		// Context and user policy file preset simultaneously
		{
			&types.SystemContext{
				RootForImplicitAbsolutePaths: rootPrefix,
				SignaturePolicyPath:          nondefaultPath,
			},
			true,
			nondefaultPath,
		},
		// Root and path overrides present simultaneously,
		{
			&types.SystemContext{
				RootForImplicitAbsolutePaths: rootPrefix,
				SignaturePolicyPath:          nondefaultPath,
			},
			false,
			nondefaultPath,
		},
		// No environment expansion happens in the overridden paths
		{&types.SystemContext{SignaturePolicyPath: variableReference}, false, variableReference},
	} {
		if c.userfilePresent {
			err := os.MkdirAll(filepath.Dir(userDefaultPolicyPath), os.ModePerm)
			require.NoError(t, err)
			f, err := os.Create(userDefaultPolicyPath)
			require.NoError(t, err)
			f.Close()
		} else {
			os.Remove(userDefaultPolicyPath)
		}
		path := defaultPolicyPathWithHomeDir(c.sys, tempHome)
		assert.Equal(t, c.expected, path)
	}
}

func TestNewPolicyFromFile(t *testing.T) {
	// Success
	policy, err := NewPolicyFromFile("./fixtures/policy.json")
	require.NoError(t, err)
	assert.Equal(t, policyFixtureContents, policy)

	// Error reading file
	_, err = NewPolicyFromFile("/this/does/not/exist")
	assert.Error(t, err)

	// A failure case; most are tested in the individual method unit tests.
	_, err = NewPolicyFromFile("/dev/null")
	require.Error(t, err)
	assert.IsType(t, InvalidPolicyFormatError(""), perrors.Cause(err))
}

func TestNewPolicyFromBytes(t *testing.T) {
	// Success
	bytes, err := os.ReadFile("./fixtures/policy.json")
	require.NoError(t, err)
	policy, err := NewPolicyFromBytes(bytes)
	require.NoError(t, err)
	assert.Equal(t, policyFixtureContents, policy)

	// A failure case; most are tested in the individual method unit tests.
	_, err = NewPolicyFromBytes([]byte(""))
	require.Error(t, err)
	assert.IsType(t, InvalidPolicyFormatError(""), err)
}

// FIXME? There is quite a bit of duplication below. Factor some of it out?

// jsonUnmarshalFromObject is like json.Unmarshal(), but the input is an arbitrary object
// that is JSON-marshalled first (as a convenient way to create an invalid/unusual JSON input)
func jsonUnmarshalFromObject(t *testing.T, object interface{}, dest interface{}) error {
	testJSON, err := json.Marshal(object)
	require.NoError(t, err)
	return json.Unmarshal(testJSON, dest)
}

// assertJSONUnmarshalFromObjectFails checks that unmarshaling the JSON-marshaled version
// of an arbitrary object (as a convenient way to create an invalid/unusual JSON input) into
// into dest fails.
func assertJSONUnmarshalFromObjectFails(t *testing.T, object interface{}, dest interface{}) {
	err := jsonUnmarshalFromObject(t, object, dest)
	assert.Error(t, err)
}

// testInvalidJSONInput verifies that obviously invalid input is rejected for dest.
func testInvalidJSONInput(t *testing.T, dest json.Unmarshaler) {
	// Invalid input. Note that json.Unmarshal is guaranteed to validate input before calling our
	// UnmarshalJSON implementation; so test that first, then test our error handling for completeness.
	err := json.Unmarshal([]byte("&"), dest)
	assert.Error(t, err)
	err = dest.UnmarshalJSON([]byte("&"))
	assert.Error(t, err)

	// Not an object/array/string
	err = json.Unmarshal([]byte("1"), dest)
	assert.Error(t, err)
}

// addExtraJSONMember adds an additional member "$name": $extra,
// possibly with a duplicate name, to encoded.
// Errors, if any, are reported through t.
func addExtraJSONMember(t *testing.T, encoded []byte, name string, extra interface{}) []byte {
	extraJSON, err := json.Marshal(extra)
	require.NoError(t, err)

	require.True(t, bytes.HasSuffix(encoded, []byte("}")))
	preservedLen := len(encoded) - 1

	res := bytes.Join([][]byte{encoded[:preservedLen], []byte(`,"`), []byte(name), []byte(`":`), extraJSON, []byte("}")}, nil)
	// Verify that the result is valid JSON, as a sanity check that we are actually triggering
	// the “duplicate member” case in the caller.
	var raw map[string]interface{}
	err = json.Unmarshal(res, &raw)
	require.NoError(t, err)
	return res
}

// policyJSONUnmarshallerTests formalizes the repeated structure of the JSON unmarshaller
// tests in this file, and allows sharing the test implementation.
type policyJSONUmarshallerTests struct {
	newDest         func() json.Unmarshaler           // Create a new json.Unmarshaler to test against
	newValidObject  func() (interface{}, error)       // A function that generates a valid object, used as a base for other tests
	otherJSONParser func([]byte) (interface{}, error) // Another function that must accept the result of encoding validObject
	invalidObjects  []mSI                             // mSI values that are invalid for this unmarshaller; a simpler alternative to breakFns
	breakFns        []func(mSI)                       // Functions that edit a mSI from newValidObject() to make it invalid
	duplicateFields []string                          // Names of fields in the return value of newValidObject() that should not be duplicated
}

// validObjectAndJSON returns an object created by d.newValidObject() and its JSON representation.
func (d policyJSONUmarshallerTests) validObjectAndJSON(t *testing.T) (interface{}, []byte) {
	validObject, err := d.newValidObject()
	require.NoError(t, err)
	validJSON, err := json.Marshal(validObject)
	require.NoError(t, err)
	return validObject, validJSON
}

func (d policyJSONUmarshallerTests) run(t *testing.T) {
	dest := d.newDest()
	testInvalidJSONInput(t, dest)

	validObject, validJSON := d.validObjectAndJSON(t)

	// Success
	dest = d.newDest()
	err := json.Unmarshal(validJSON, dest)
	require.NoError(t, err)
	assert.Equal(t, validObject, dest)

	// otherJSONParser recognizes this data
	if d.otherJSONParser != nil {
		other, err := d.otherJSONParser(validJSON)
		require.NoError(t, err)
		assert.Equal(t, validObject, other)
	}

	// Invalid JSON objects
	for _, invalid := range d.invalidObjects {
		dest := d.newDest()
		assertJSONUnmarshalFromObjectFails(t, invalid, dest)
	}
	// Various ways to corrupt the JSON
	for _, fn := range d.breakFns {
		var tmp mSI
		err := json.Unmarshal(validJSON, &tmp)
		require.NoError(t, err)

		fn(tmp)

		dest := d.newDest()
		assertJSONUnmarshalFromObjectFails(t, tmp, dest)
	}

	// Duplicated fields
	for _, field := range d.duplicateFields {
		var tmp mSI
		err := json.Unmarshal(validJSON, &tmp)
		require.NoError(t, err)

		testJSON := addExtraJSONMember(t, validJSON, field, tmp[field])

		dest := d.newDest()
		err = json.Unmarshal(testJSON, dest)
		assert.Error(t, err)
	}
}

// xNewPRSignedByKeyPath is like NewPRSignedByKeyPath, except it must not fail.
func xNewPRSignedByKeyPath(keyType sbKeyType, keyPath string, signedIdentity PolicyReferenceMatch) PolicyRequirement {
	pr, err := NewPRSignedByKeyPath(keyType, keyPath, signedIdentity)
	if err != nil {
		panic("xNewPRSignedByKeyPath failed")
	}
	return pr<commit_msg>Add context to some test failures<commit_after>package signature

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"testing"

	"github.com/containers/image/v5/directory"
	"github.com/containers/image/v5/docker"
	perrors "github.com/pkg/errors"

	// this import is needed  where we use the "atomic" transport in TestPolicyUnmarshalJSON
	_ "github.com/containers/image/v5/openshift"
	"github.com/containers/image/v5/types"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type mSI map[string]interface{} // To minimize typing the long name

// A short-hand way to get a JSON object field value or panic. No error handling done, we know
// what we are working with, a panic in a test is good enough, and fitting test cases on a single line
// is a priority.
func x(m mSI, fields ...string) mSI {
	for _, field := range fields {
		// Not .(mSI) because type assertion of an unnamed type to a named type always fails (the types
		// are not "identical"), but the assignment is fine because they are "assignable".
		m = m[field].(map[string]interface{})
	}
	return m
}

// policyFixtureContents is a data structure equal to the contents of "fixtures/policy.json"
var policyFixtureContents = &Policy{
	Default: PolicyRequirements{NewPRReject()},
	Transports: map[string]PolicyTransportScopes{
		"dir": {
			"": PolicyRequirements{NewPRInsecureAcceptAnything()},
		},
		"docker": {
			"example.com/playground": {
				NewPRInsecureAcceptAnything(),
			},
			"example.com/production": {
				xNewPRSignedByKeyPath(SBKeyTypeGPGKeys,
					"/keys/employee-gpg-keyring",
					NewPRMMatchRepoDigestOrExact()),
			},
			"example.com/hardened": {
				xNewPRSignedByKeyPath(SBKeyTypeGPGKeys,
					"/keys/employee-gpg-keyring",
					NewPRMMatchRepository()),
				xNewPRSignedByKeyPath(SBKeyTypeSignedByGPGKeys,
					"/keys/public-key-signing-gpg-keyring",
					NewPRMMatchExact()),
				xNewPRSignedBaseLayer(xNewPRMExactRepository("registry.access.redhat.com/rhel7/rhel")),
			},
			"example.com/hardened-x509": {
				xNewPRSignedByKeyPath(SBKeyTypeX509Certificates,
					"/keys/employee-cert-file",
					NewPRMMatchRepository()),
				xNewPRSignedByKeyPath(SBKeyTypeSignedByX509CAs,
					"/keys/public-key-signing-ca-file",
					NewPRMMatchRepoDigestOrExact()),
			},
			"registry.access.redhat.com": {
				xNewPRSignedByKeyPath(SBKeyTypeSignedByGPGKeys,
					"/keys/RH-key-signing-key-gpg-keyring",
					NewPRMMatchRepoDigestOrExact()),
			},
			"private-mirror:5000/vendor-mirror": {
				xNewPRSignedByKeyPath(SBKeyTypeGPGKeys,
					"/keys/vendor-gpg-keyring",
					xNewPRMRemapIdentity("private-mirror:5000/vendor-mirror", "vendor.example.com")),
			},
			"*.access.redhat.com": {
				xNewPRSignedByKeyPath(SBKeyTypeSignedByGPGKeys,
					"/keys/RH-key-signing-key-gpg-keyring",
					NewPRMMatchRepoDigestOrExact()),
			},
			"*.redhat.com": {
				xNewPRSignedByKeyPath(SBKeyTypeSignedByGPGKeys,
					"/keys/RH-key-signing-key-gpg-keyring",
					NewPRMMatchRepoDigestOrExact()),
			},
			"*.com": {
				xNewPRSignedByKeyPath(SBKeyTypeSignedByGPGKeys,
					"/keys/RH-key-signing-key-gpg-keyring",
					NewPRMMatchRepoDigestOrExact()),
			},
			"bogus/key-data-example": {
				xNewPRSignedByKeyData(SBKeyTypeSignedByGPGKeys,
					[]byte("nonsense"),
					NewPRMMatchRepoDigestOrExact()),
			},
			"bogus/signed-identity-example": {
				xNewPRSignedBaseLayer(xNewPRMExactReference("registry.access.redhat.com/rhel7/rhel:latest")),
			},
			"example.com/sigstore/key-data-example": {
				xNewPRSigstoreSignedKeyData([]byte("nonsense"),
					NewPRMMatchRepoDigestOrExact()),
			},
			"example.com/sigstore/key-path-example": {
				xNewPRSigstoreSignedKeyPath("/keys/public-key",
					NewPRMMatchRepository()),
			},
		},
	},
}

func TestInvalidPolicyFormatError(t *testing.T) {
	// A stupid test just to keep code coverage
	s := "test"
	err := InvalidPolicyFormatError(s)
	assert.Equal(t, s, err.Error())
}

func TestDefaultPolicy(t *testing.T) {
	// We can't test the actual systemDefaultPolicyPath, so override.
	// TestDefaultPolicyPath below tests that we handle the overrides and defaults
	// correctly.

	// Success
	policy, err := DefaultPolicy(&types.SystemContext{SignaturePolicyPath: "./fixtures/policy.json"})
	require.NoError(t, err)
	assert.Equal(t, policyFixtureContents, policy)

	for _, path := range []string{
		"/this/does/not/exist", // Error reading file
		"/dev/null",            // A failure case; most are tested in the individual method unit tests.
	} {
		policy, err := DefaultPolicy(&types.SystemContext{SignaturePolicyPath: path})
		assert.Error(t, err)
		assert.Nil(t, policy)
	}
}

func TestDefaultPolicyPath(t *testing.T) {
	const nondefaultPath = "/this/is/not/the/default/path.json"
	const variableReference = "$HOME"
	const rootPrefix = "/root/prefix"
	tempHome := t.TempDir()
	userDefaultPolicyPath := filepath.Join(tempHome, userPolicyFile)

	for _, c := range []struct {
		sys             *types.SystemContext
		userfilePresent bool
		expected        string
	}{
		// The common case
		{nil, false, systemDefaultPolicyPath},
		// There is a context, but it does not override the path.
		{&types.SystemContext{}, false, systemDefaultPolicyPath},
		// Path overridden
		{&types.SystemContext{SignaturePolicyPath: nondefaultPath}, false, nondefaultPath},
		// Root overridden
		{
			&types.SystemContext{RootForImplicitAbsolutePaths: rootPrefix},
			false,
			filepath.Join(rootPrefix, systemDefaultPolicyPath),
		},
		// Empty context and user policy present
		{&types.SystemContext{}, true, userDefaultPolicyPath},
		// Only user policy present
		{nil, true, userDefaultPolicyPath},
		// Context signature path and user policy present
		{
			&types.SystemContext{
				SignaturePolicyPath: nondefaultPath,
			},
			true,
			nondefaultPath,
		},
		// Root and user policy present
		{
			&types.SystemContext{
				RootForImplicitAbsolutePaths: rootPrefix,
			},
			true,
			userDefaultPolicyPath,
		},
		// Context and user policy file preset simultaneously
		{
			&types.SystemContext{
				RootForImplicitAbsolutePaths: rootPrefix,
				SignaturePolicyPath:          nondefaultPath,
			},
			true,
			nondefaultPath,
		},
		// Root and path overrides present simultaneously,
		{
			&types.SystemContext{
				RootForImplicitAbsolutePaths: rootPrefix,
				SignaturePolicyPath:          nondefaultPath,
			},
			false,
			nondefaultPath,
		},
		// No environment expansion happens in the overridden paths
		{&types.SystemContext{SignaturePolicyPath: variableReference}, false, variableReference},
	} {
		if c.userfilePresent {
			err := os.MkdirAll(filepath.Dir(userDefaultPolicyPath), os.ModePerm)
			require.NoError(t, err)
			f, err := os.Create(userDefaultPolicyPath)
			require.NoError(t, err)
			f.Close()
		} else {
			os.Remove(userDefaultPolicyPath)
		}
		path := defaultPolicyPathWithHomeDir(c.sys, tempHome)
		assert.Equal(t, c.expected, path)
	}
}

func TestNewPolicyFromFile(t *testing.T) {
	// Success
	policy, err := NewPolicyFromFile("./fixtures/policy.json")
	require.NoError(t, err)
	assert.Equal(t, policyFixtureContents, policy)

	// Error reading file
	_, err = NewPolicyFromFile("/this/does/not/exist")
	assert.Error(t, err)

	// A failure case; most are tested in the individual method unit tests.
	_, err = NewPolicyFromFile("/dev/null")
	require.Error(t, err)
	assert.IsType(t, InvalidPolicyFormatError(""), perrors.Cause(err))
}

func TestNewPolicyFromBytes(t *testing.T) {
	// Success
	bytes, err := os.ReadFile("./fixtures/policy.json")
	require.NoError(t, err)
	policy, err := NewPolicyFromBytes(bytes)
	require.NoError(t, err)
	assert.Equal(t, policyFixtureContents, policy)

	// A failure case; most are tested in the individual method unit tests.
	_, err = NewPolicyFromBytes([]byte(""))
	require.Error(t, err)
	assert.IsType(t, InvalidPolicyFormatError(""), err)
}

// FIXME? There is quite a bit of duplication below. Factor some of it out?

// jsonUnmarshalFromObject is like json.Unmarshal(), but the input is an arbitrary object
// that is JSON-marshalled first (as a convenient way to create an invalid/unusual JSON input)
func jsonUnmarshalFromObject(t *testing.T, object interface{}, dest interface{}) error {
	testJSON, err := json.Marshal(object)
	require.NoError(t, err)
	return json.Unmarshal(testJSON, dest)
}

// assertJSONUnmarshalFromObjectFails checks that unmarshaling the JSON-marshaled version
// of an arbitrary object (as a convenient way to create an invalid/unusual JSON input) into
// into dest fails.
func assertJSONUnmarshalFromObjectFails(t *testing.T, object interface{}, dest interface{}) {
	err := jsonUnmarshalFromObject(t, object, dest)
	assert.Error(t, err)
}

// testInvalidJSONInput verifies that obviously invalid input is rejected for dest.
func testInvalidJSONInput(t *testing.T, dest json.Unmarshaler) {
	// Invalid input. Note that json.Unmarshal is guaranteed to validate input before calling our
	// UnmarshalJSON implementation; so test that first, then test our error handling for completeness.
	err := json.Unmarshal([]byte("&"), dest)
	assert.Error(t, err)
	err = dest.UnmarshalJSON([]byte("&"))
	assert.Error(t, err)

	// Not an object/array/string
	err = json.Unmarshal([]byte("1"), dest)
	assert.Error(t, err)
}

// addExtraJSONMember adds an additional member "$name": $extra,
// possibly with a duplicate name, to encoded.
// Errors, if any, are reported through t.
func addExtraJSONMember(t *testing.T, encoded []byte, name string, extra interface{}) []byte {
	extraJSON, err := json.Marshal(extra)
	require.NoError(t, err)

	require.True(t, bytes.HasSuffix(encoded, []byte("}")))
	preservedLen := len(encoded) - 1

	res := bytes.Join([][]byte{encoded[:preservedLen], []byte(`,"`), []byte(name), []byte(`":`), extraJSON, []byte("}")}, nil)
	// Verify that the result is valid JSON, as a sanity check that we are actually triggering
	// the “duplicate member” case in the caller.
	var raw map[string]interface{}
	err = json.Unmarshal(res, &raw)
	require.NoError(t, err)
	return res
}

// policyJSONUnmarshallerTests formalizes the repeated structure of the JSON unmarshaller
// tests in this file, and allows sharing the test implementation.
type policyJSONUmarshallerTests struct {
	newDest         func() json.Unmarshaler           // Create a new json.Unmarshaler to test against
	newValidObject  func() (interface{}, error)       // A function that generates a valid object, used as a base for other tests
	otherJSONParser func([]byte) (interface{}, error) // Another function that must accept the result of encoding validObject
	invalidObjects  []mSI                             // mSI values that are invalid for this unmarshaller; a simpler alternative to breakFns
	breakFns        []func(mSI)                       // Functions that edit a mSI from newValidObject() to make it invalid
	duplicateFields []string                          // Names of fields in the return value of newValidObject() that should not be duplicated
}

// validObjectAndJSON returns an object created by d.newValidObject() and its JSON representation.
func (d policyJSONUmarshallerTests) validObjectAndJSON(t *testing.T) (interface{}, []byte) {
	validObject, err := d.newValidObject()
	require.NoError(t, err)
	validJSON, err := json.Marshal(validObject)
	require.NoError(t, err)
	return validObject, validJSON
}

func (d policyJSONUmarshallerTests) run(t *testing.T) {
	dest := d.newDest()
	testInvalidJSONInput(t, dest)

	validObject, validJSON := d.validObjectAndJSON(t)

	// Success
	dest = d.newDest()
	err := json.Unmarshal(validJSON, dest)
	require.NoError(t, err)
	assert.Equal(t, validObject, dest)

	// otherJSONParser recognizes this data
	if d.otherJSONParser != nil {
		other, err := d.otherJSONParser(validJSON)
		require.NoError(t, err)
		assert.Equal(t, validObject, other)
	}

	// Invalid JSON objects
	for _, invalid := range d.invalidObjects {
		dest := d.newDest()
		assertJSONUnmarshalFromObjectFails(t, invalid, dest)
	}
	// Various ways to corrupt the JSON
	for index, fn := range d.breakFns {
		t.Run(fmt.Sprintf("breakFns[%d]", index), func(t *testing.T) {
			var tmp mSI
			err := json.Unmarshal(validJSON, &tmp)
			require.NoError(t, err)

			fn(tmp)

			dest := d.newDest()
			assertJSONUnmarshalFromObjectFails(t, tmp, dest)
		})
	}

	// Duplicated fields
	for _, field := range d.duplicateFields {
		var tmp mSI
		err := json.Unmarshal(validJSON, &tmp)
		require.NoError(t, err)

		testJSON := addExtraJSONMember(t, validJSON, field, tmp[field])

		dest := d.newDest()
		err = json.Unmarshal(testJSON, dest)
		assert.Error(t, err)
	}
}

// xNewPRSignedByKeyPath is like NewPRSignedByKeyPath, except it must not fail.
func xNewPRSignedByKeyPath(keyType sbKeyType, keyPath string, signedIdentity PolicyReferenceMatch) PolicyRequirement {
	pr, err := NewPRSignedByKeyPath(keyType, keyPath, signedIdentity)
	if err != nil {
		panic("xNewPRSignedByKeyPath failed")
	}
	return pr
{'fraction_non_alphanumeric': 0.1205563541707137, 'fraction_numerical': 0.0019425774117098565, 'mean_word_length': 4.1490298059611925, 'pattern_counts': {'":': 36, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7225740', 'n_tokens_mistral': 9037, 'n_tokens_neox': 8186, 'n_words': 2030}