blob: 1513b83940d68506bf6599321605a2b1bc3b674d [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package value
import (
"crypto/sha256"
"encoding/base64"
"strings"
"testing"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/structpb"
orchestratorpb "go.chromium.org/turboci/proto/go/graph/orchestrator/v1"
"go.chromium.org/turboci/proto/go/utils/internal/test/assert"
)
func TestComputeDigest(t *testing.T) {
t.Parallel()
cases := []struct {
name string
msg proto.Message
want Digest
}{
{
"empty",
&emptypb.Empty{},
"zC1HiB0gq_T1muuCh5VIAoC4FjWvxp00E9waqU1YMhkrAQ",
},
{
"float_val",
structpb.NewNumberValue(123.456),
"aexUjcBYp_UhSBsbm6TwadrRm0ZAYUrR5mRAKiJ2XtQ2AQ",
},
{
"long_string",
structpb.NewStringValue(strings.Repeat("this is a very long string", 40000)),
"tvpg39g5kBqzdMKxPOWxvE82_CR13ZmPUmuaq186WyCzvT8B",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
apb, err := anypb.New(tc.msg)
assert.NoErr(t, err)
dgst := ComputeDigest(apb)
assert.Equal(t, tc.want, dgst)
dgstPb, err := dgst.ToProto()
assert.NoErr(t, err)
wantSize := proto.Size(apb)
enc, err := proto.Marshal(apb)
assert.NoErr(t, err)
assert.Equal(t, wantSize, len(enc))
detEnc := DeterministicallySerializeAny(apb)
assert.Len(t, detEnc, wantSize)
dec := &anypb.Any{}
assert.NoErr(t, proto.Unmarshal(detEnc, dec))
assert.True(t, proto.Equal(dec, apb))
sha := sha256.Sum256(detEnc)
assert.Match(t, sha[:], dgstPb.GetHash())
assert.Equal(t, uint64(wantSize), dgstPb.GetSizeBytes())
})
}
}
func TestDigestToProtoErrors(t *testing.T) {
t.Parallel()
b64 := func(hsh, size, algo []byte) Digest {
toEnc := make([]byte, 0, len(hsh)+len(size)+len(algo))
toEnc = append(toEnc, hsh...)
toEnc = append(toEnc, size...)
toEnc = append(toEnc, algo...)
return Digest(base64.RawURLEncoding.EncodeToString(toEnc))
}
cases := []struct {
name string
digest Digest
wantErr string
}{
{
name: "bad_base64",
digest: "heloworld",
wantErr: "illegal base64",
},
{
name: "missing_algo",
digest: "",
wantErr: "missing algorithm",
},
{
name: "bad_algo",
digest: b64(make([]byte, 32), []byte{1}, []byte{32}),
wantErr: "bad algorithm",
},
{
name: "small_hash",
digest: b64(make([]byte, 20), []byte{1}, []byte{byte(orchestratorpb.ValueHashAlgo_VALUE_HASH_ALGO_SHA256)}),
wantErr: "insufficient bytes for hash",
},
{
name: "extra_size",
digest: b64(make([]byte, 32), []byte{1, 1, 1}, []byte{byte(orchestratorpb.ValueHashAlgo_VALUE_HASH_ALGO_SHA256)}),
wantErr: "extra bytes while decoding size",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
_, err := tc.digest.ToProto()
assert.ErrLike(t, err, tc.wantErr)
})
}
}