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  }