go.fuchsia.dev/infra@v0.0.0-20240507153436-9b593402251b/cmd/recipe_wrapper/props/props_test.go (about) 1 // Copyright 2020 The Fuchsia Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package props 6 7 import ( 8 "testing" 9 10 "github.com/google/go-cmp/cmp" 11 buildbucketpb "go.chromium.org/luci/buildbucket/proto" 12 apipb "go.chromium.org/luci/swarming/proto/api" 13 "google.golang.org/protobuf/testing/protocmp" 14 "google.golang.org/protobuf/types/known/structpb" 15 ) 16 17 func TestGetStringInputProperty(t *testing.T) { 18 t.Parallel() 19 20 tests := []struct { 21 name string 22 props map[string]any 23 expectedValue string 24 expectErr bool 25 }{ 26 { 27 name: "valid string", 28 props: map[string]any{ 29 "string": "foo", 30 }, 31 expectedValue: "foo", 32 }, 33 { 34 name: "unset", 35 props: map[string]any{}, 36 expectedValue: "", 37 }, 38 { 39 name: "invalid value", 40 props: map[string]any{ 41 "string": 123, 42 }, 43 expectErr: true, 44 }, 45 } 46 47 for _, test := range tests { 48 t.Run(test.name, func(t *testing.T) { 49 build := buildWithProperties(t, test.props) 50 value, err := String(build, "string") 51 if err == nil { 52 if test.expectErr { 53 t.Fatalf("expected error, got nil") 54 } 55 } else if !test.expectErr { 56 t.Fatalf("got unexpected error: %s", err) 57 } 58 if value != test.expectedValue { 59 t.Fatalf("expected string value %s, got %s", test.expectedValue, value) 60 } 61 }) 62 } 63 } 64 65 func TestGetBoolInputProperty(t *testing.T) { 66 t.Parallel() 67 68 tests := []struct { 69 name string 70 props map[string]any 71 expectedValue bool 72 expectErr bool 73 }{ 74 { 75 name: "valid bool", 76 props: map[string]any{ 77 "bool": true, 78 }, 79 expectedValue: true, 80 }, 81 { 82 name: "unset", 83 props: map[string]any{}, 84 expectedValue: false, 85 }, 86 { 87 name: "invalid value", 88 props: map[string]any{ 89 "bool": 123, 90 }, 91 expectErr: true, 92 }, 93 } 94 95 for _, test := range tests { 96 t.Run(test.name, func(t *testing.T) { 97 build := buildWithProperties(t, test.props) 98 value, err := Bool(build, "bool") 99 if err == nil { 100 if test.expectErr { 101 t.Fatalf("expected error, got nil") 102 } 103 } else if !test.expectErr { 104 t.Fatalf("got unexpected error %q", err) 105 } 106 if value != test.expectedValue { 107 t.Fatalf("expected bool value %t, got %t", test.expectedValue, value) 108 } 109 }) 110 } 111 } 112 113 func TestSetInputProperty(t *testing.T) { 114 t.Parallel() 115 116 tests := []struct { 117 name string 118 props map[string]any 119 key string 120 value any 121 expectErr bool 122 }{ 123 { 124 name: "string value", 125 props: map[string]any{}, 126 key: "foo", 127 value: "bar", 128 }, 129 { 130 name: "overwrite nested value", 131 props: map[string]any{ 132 "foo": map[string]any{ 133 "foo": "", 134 }, 135 }, 136 key: "foo", 137 value: "bar", 138 }, 139 } 140 141 for _, test := range tests { 142 t.Run(test.name, func(t *testing.T) { 143 build := buildWithProperties(t, test.props) 144 err := SetBuildInputProperty(build, test.key, test.value) 145 if err == nil { 146 if test.expectErr { 147 t.Fatalf("expected error, got nil") 148 } 149 } else if !test.expectErr { 150 t.Fatalf("got unexpected error: %s", err) 151 } 152 gotValue, err := String(build, test.key) 153 if err == nil { 154 if test.expectErr { 155 t.Fatalf("expected error, got nil") 156 } 157 } else if !test.expectErr { 158 t.Fatalf("got unexpected error: %s", err) 159 } 160 if test.value != gotValue { 161 t.Fatalf("expected value %s, got %s", test.value, gotValue) 162 } 163 }) 164 } 165 } 166 167 func TestGetProtoInputProperty(t *testing.T) { 168 t.Parallel() 169 170 tests := []struct { 171 name string 172 key string 173 props map[string]any 174 // We somewhat arbitrarily choose CASReference as the proto message for 175 // testing this general-purpose function because it contains a good mix 176 // of field types, and it's a type that we actually care about reading 177 // from a property. 178 expected *apipb.CASReference 179 }{ 180 { 181 name: "property missing", 182 key: "prop", 183 props: map[string]any{ 184 "foo": "bar", 185 }, 186 expected: &apipb.CASReference{}, 187 }, 188 { 189 name: "property set", 190 key: "prop", 191 props: map[string]any{ 192 "prop": map[string]any{ 193 "cas_instance": "cas_instance_name", 194 "digest": map[string]any{ 195 "hash": "abc123", 196 // jsonpb serializes int64 to string. 197 "size_bytes": "587", 198 }, 199 }, 200 "foo": "bar", 201 }, 202 expected: &apipb.CASReference{ 203 CasInstance: "cas_instance_name", 204 Digest: &apipb.Digest{ 205 Hash: "abc123", 206 SizeBytes: 587, 207 }, 208 }, 209 }, 210 } 211 for _, test := range tests { 212 t.Run(test.name, func(t *testing.T) { 213 build := buildWithProperties(t, test.props) 214 casRef := &apipb.CASReference{} 215 if err := Proto(build, test.key, casRef); err != nil { 216 t.Fatal(err) 217 } 218 if diff := cmp.Diff(test.expected, casRef, protocmp.Transform()); diff != "" { 219 t.Errorf("Unexpected CAS reference diff (-want +got):\n%s", diff) 220 } 221 }) 222 } 223 } 224 225 // buildWithProperties returns a Build proto object with the given input 226 // properties. 227 func buildWithProperties(t *testing.T, props map[string]any) *buildbucketpb.Build { 228 t.Helper() 229 structProps, err := structpb.NewStruct(props) 230 if err != nil { 231 t.Fatal(err) 232 } 233 return &buildbucketpb.Build{ 234 Input: &buildbucketpb.Build_Input{ 235 Properties: structProps, 236 }, 237 } 238 }