github.com/bazelbuild/remote-apis-sdks@v0.0.0-20240425170053-8a36686a6350/go/pkg/client/client_test.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"net"
     7  	"os"
     8  	"path"
     9  	"testing"
    10  
    11  	repb "github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2"
    12  	svpb "github.com/bazelbuild/remote-apis/build/bazel/semver"
    13  	"google.golang.org/grpc"
    14  )
    15  
    16  const (
    17  	// This key pair for `foo.example.com` was generated using https://golang.org/src/crypto/tls/generate_cert.go
    18  
    19  	tlsCert = `
    20  -----BEGIN CERTIFICATE-----
    21  MIIC/zCCAeegAwIBAgIQa6p4srafgw0S6iDvt7rlXzANBgkqhkiG9w0BAQsFADAS
    22  MRAwDgYDVQQKEwdBY21lIENvMB4XDTIwMTAwODA5NTExM1oXDTIxMTAwODA5NTEx
    23  M1owEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
    24  AQoCggEBAM1Mo2UOcQxkPHs2+oaclJsPZo80Cqb0Oze0zxA9uFXg6SiklqPjWUPc
    25  o2TTBmbpw6t2Cv6Ywr6cTB7PJXCruOFo5x+6rZlzjrfqArYn3Btv64/6SwaarIEW
    26  UcisWDvcEW+QArAmsJo39RL4ptsb+0Eoqkm1WeQ31XWh9i7W0F3qy43lC4k5QpXb
    27  imeevejwk3TMFAnAc8dBXxNDg3gr9isDq2yH1ZhJ/4BNcle6WYNny0Uh9kYCLCWe
    28  PK1ZYOeT1rRCtBJTg7w1/e0Qpln6V/MsJNtgt14vYPsHAsW+AMLycN1f9/8T7joz
    29  6/D8GVMC5Vaz2z5CxeNb5ifCQkmEN8kCAwEAAaNRME8wDgYDVR0PAQH/BAQDAgWg
    30  MBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwGgYDVR0RBBMwEYIP
    31  Zm9vLmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQB0d9y7XyLwWMBMxIRL
    32  5rdgk9AvpVKX5FtYkJmxG7F9p67GlLhy314SyPDaUm/tl0yf/LM9m7BM7m9900mO
    33  9qjfTi6wc0Co9dDMhi4rh0CPlEilWpiA7wAyM0eVQPJx+wv/u/8aDzVy/Cr8wLwi
    34  UyXETaAVsbrHmoIMQgyy0boTDzBwwhx8e/heqaZfV6H6RsBpChYFIF0A7T92bu91
    35  WmSA9kfUwzYeOvLyX/s+J77U/bnYo4Fl/LnBkBr1vkTPF9xGWYZFU0Ok/uk9TyrB
    36  vI06wv3H6phi4fWDMbWuSiBHr3AefTqcxkBcaxYCzLgOdRJEnV1GWdw/Gt9PNyJ2
    37  IJff
    38  -----END CERTIFICATE-----
    39  `
    40  
    41  	tlsKey = `
    42  -----BEGIN PRIVATE KEY-----
    43  MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDNTKNlDnEMZDx7
    44  NvqGnJSbD2aPNAqm9Ds3tM8QPbhV4OkopJaj41lD3KNk0wZm6cOrdgr+mMK+nEwe
    45  zyVwq7jhaOcfuq2Zc4636gK2J9wbb+uP+ksGmqyBFlHIrFg73BFvkAKwJrCaN/US
    46  +KbbG/tBKKpJtVnkN9V1ofYu1tBd6suN5QuJOUKV24pnnr3o8JN0zBQJwHPHQV8T
    47  Q4N4K/YrA6tsh9WYSf+ATXJXulmDZ8tFIfZGAiwlnjytWWDnk9a0QrQSU4O8Nf3t
    48  EKZZ+lfzLCTbYLdeL2D7BwLFvgDC8nDdX/f/E+46M+vw/BlTAuVWs9s+QsXjW+Yn
    49  wkJJhDfJAgMBAAECggEAOdsALQl92FUjKkIbJfZYdr5HU9IBsn0fdxpwRA5wtVr1
    50  uitv/G2kiVhTf6Vsp3H3X4mbnZYlQ5w+Y2xTC8aJCpUKlUmBhL4pGTFMZFMlV2Ia
    51  C3Ao1oqeVJ6am6feEbh5/WujJksw97UPTrJeK19eVkWEf9MoqwUFFep3u0l0XRK9
    52  zR5ZhhZe+3C1DS3r7P89/7ZlnqLuba94fIqwn11D7jzmiZQBuBLa6X16grGs03Gb
    53  LoBoiTj7g0a6nSEcDen5jI8oPrZuMWAlTXLYb68eCgvU2FXe0A2PFZzh8XQ71Y4J
    54  Y6L0oGGiTThgphsH/bqQi03mF/Hd0BcIlBe+8mPSFQKBgQDuUI5ENq650eOf6PPh
    55  abLl+cmBIxNdfFYZHB5IapDfqKBK3imD3QYT4cBdbcQ0PWjbFj/aCgkPYCmMxBSX
    56  WGVohhH4sXHNIS0BL6dGalQhE+C3tWkzg79AE84tTrvijVuOom3RiR0SGzBQ4YBl
    57  boPPKinY6Hz6FEhlfWdlwR0GcwKBgQDciN2m1wNG6gkDg/2OEn+g79boKPdI0lC4
    58  VTfQ6xDKwGs2N8HIOoeXHhTStgkPCeNmxuOtEtqfKKRpje2ZRFvjZWOh6t+CvX0w
    59  ZE+GhJtBC0ODYQ9+Hai1HgBbLYrMR44cRWRx5uMD4/v4Vrg7jdtpUICeonPd4mMD
    60  ppgV0Ze90wKBgBWu+LLWMuGvakGjhYUuA9wO9TmtwlZQONlCCLNYFuRyyFrniel4
    61  k9P9R254zVBfggnlJVwma5qdikpHkQQQQ/HVuQ7ivlMGwLyJ5HNwu0TjNSAh/nd0
    62  dlNTOgA/WLMNX5ZDyzA0hJOgK65ARll0S8Puty4IQU7Tx56MYXsNriFnAoGBAI1g
    63  +Kfio2ks1AZ68WvJFCT6XM2Mpar7mV/y0cuPRr6USKcDa6mPTClm0Xab2IbZkfzr
    64  eD4WHi12gqBgqnddSYMoFo1Q42UPUVhalljoXhd+vxNUf/gbz3H8/8F0Gs0G+mXJ
    65  XrFekR6HY5s7DPSw5n5Ha41HG/ydC1jlzg3+xcadAoGBAIZ/ZJpnC2Tv8poGHbmD
    66  B3hWlPIQSU2dEGQCEhiGhzoi7muXmYqx5t8YnMGLGmsltLxD95KhACrTZZmBGiUS
    67  HavFSkKMI1EuncwH2ooshIF9ZQqYNpIH7YGxnzDgNXnmeb26FI+b0uuxW74PWZrL
    68  h4A58eQ+JGSLao6JSmi2T0tZ
    69  -----END PRIVATE KEY-----
    70  `
    71  
    72  	instance = "instance"
    73  )
    74  
    75  func TestCreateTLSConfig(t *testing.T) {
    76  	t.Run("EmptyDialParams", func(t *testing.T) {
    77  		_, err := createTLSConfig(DialParams{})
    78  		if err != nil {
    79  			t.Errorf("Could not create TLS config: %v", err)
    80  		}
    81  	})
    82  
    83  	t.Run("OverrideServerName", func(t *testing.T) {
    84  		tlsConfig, err := createTLSConfig(DialParams{
    85  			TLSServerName: "foo.example.com",
    86  		})
    87  		if err != nil {
    88  			t.Errorf("Could not create TLS config: %v", err)
    89  		}
    90  		if tlsConfig.ServerName != "foo.example.com" {
    91  			t.Errorf("Expected ServerName to be 'foo.example.com', got '%v'", tlsConfig.ServerName)
    92  		}
    93  	})
    94  
    95  	t.Run("UseClientCertificate", func(t *testing.T) {
    96  		t.Run("OnlyTLSClientAuthCert", func(t *testing.T) {
    97  			_, err := createTLSConfig(DialParams{
    98  				TLSClientAuthCert: "/foo/bar",
    99  			})
   100  			if err == nil {
   101  				t.Error("Expected error creating mTLS config without auth-key, got nil")
   102  			}
   103  		})
   104  
   105  		t.Run("OnlyTLSClientAuthKey", func(t *testing.T) {
   106  			_, err := createTLSConfig(DialParams{
   107  				TLSClientAuthKey: "/foo/bar",
   108  			})
   109  			if err == nil {
   110  				t.Error("Expected error creating mTLS config without auth-cert, got nil")
   111  			}
   112  		})
   113  
   114  		t.Run("OnlyTLSClientAuthCert", func(t *testing.T) {
   115  			certPath := path.Join(t.TempDir(), "cert.pem")
   116  			if err := os.WriteFile(certPath, []byte(tlsCert), 0644); err != nil {
   117  				t.Fatalf("Could not write '%v': %v", certPath, err)
   118  			}
   119  			keyPath := path.Join(t.TempDir(), "key.pem")
   120  			if err := os.WriteFile(keyPath, []byte(tlsKey), 0644); err != nil {
   121  				t.Fatalf("Could not write '%v': %v", keyPath, err)
   122  			}
   123  
   124  			tlsConfig, err := createTLSConfig(DialParams{
   125  				TLSClientAuthCert: certPath,
   126  				TLSClientAuthKey:  keyPath,
   127  			})
   128  			if err != nil {
   129  				t.Errorf("Could not create TLS config: %v", err)
   130  			}
   131  			if len(tlsConfig.Certificates) != 1 {
   132  				t.Errorf("Expected exactly 1 certificate, got: %v", tlsConfig.Certificates)
   133  			}
   134  		})
   135  	})
   136  }
   137  
   138  func TestVersionComparison(t *testing.T) {
   139  	latestSupportedVersion := &svpb.SemVer{Major: 2, Minor: 1}
   140  	serverCapabilities := &repb.ServerCapabilities{
   141  		HighApiVersion: latestSupportedVersion,
   142  	}
   143  
   144  	if !highAPIVersionNewerThanOrEqualTo(serverCapabilities, 1, 9) {
   145  		t.Errorf("Got: !(2.1 >= 1.9")
   146  	}
   147  
   148  	if !highAPIVersionNewerThanOrEqualTo(serverCapabilities, 2, 0) {
   149  		t.Errorf("Got: !(2.1 >= 2.0")
   150  	}
   151  
   152  	if highAPIVersionNewerThanOrEqualTo(serverCapabilities, 2, 2) {
   153  		t.Errorf("Got: 2.1 >= 2.2")
   154  	}
   155  
   156  	if highAPIVersionNewerThanOrEqualTo(&repb.ServerCapabilities{}, 0, 1) {
   157  		t.Errorf("Got: empty >= 0.1")
   158  	}
   159  
   160  	if highAPIVersionNewerThanOrEqualTo(nil, 0, 1) {
   161  		t.Errorf("Got: nil >= 0.1")
   162  	}
   163  }
   164  
   165  func TestActionSupportsPlatformProperties(t *testing.T) {
   166  	if supportsActionPlatformProperties(&repb.ServerCapabilities{HighApiVersion: &svpb.SemVer{Major: 2, Minor: 1}}) {
   167  		t.Errorf("Got platform properties field suppported for v2.1 (expected only for v >= 2.2)")
   168  	}
   169  
   170  	if !supportsActionPlatformProperties(&repb.ServerCapabilities{HighApiVersion: &svpb.SemVer{Major: 2, Minor: 2}}) {
   171  		t.Errorf("Got platform properties field not suppported for v2.2 (expected for v >= 2.2)")
   172  	}
   173  
   174  	if !supportsActionPlatformProperties(&repb.ServerCapabilities{HighApiVersion: &svpb.SemVer{Major: 2, Minor: 3}}) {
   175  		t.Errorf("Got platform properties field not suppported for v2.3 (expected for v >= 2.2)")
   176  	}
   177  }
   178  
   179  func TestCommandUsesOutputPaths(t *testing.T) {
   180  	if supportsCommandOutputPaths(&repb.ServerCapabilities{HighApiVersion: &svpb.SemVer{Major: 2, Minor: 0}}) {
   181  		t.Errorf("Got `output_paths` field suppported for v2.0 (expected only for v >= 2.1)")
   182  	}
   183  
   184  	if !supportsCommandOutputPaths(&repb.ServerCapabilities{HighApiVersion: &svpb.SemVer{Major: 2, Minor: 1}}) {
   185  		t.Errorf("Got `output_paths` field not suppported for v2.1 (expected for v >= 2.1)")
   186  	}
   187  
   188  	if !supportsCommandOutputPaths(&repb.ServerCapabilities{HighApiVersion: &svpb.SemVer{Major: 2, Minor: 2}}) {
   189  		t.Errorf("Got `output_paths` field not suppported for v2.2 (expected for v >= 2.1)")
   190  	}
   191  }
   192  
   193  func TestNewClient(t *testing.T) {
   194  	t.Parallel()
   195  	ctx := context.Background()
   196  
   197  	c, err := NewClient(ctx, instance, DialParams{
   198  		Service:    "server",
   199  		NoSecurity: true,
   200  	}, StartupCapabilities(false))
   201  	if err != nil {
   202  		t.Fatalf("Error creating client: %v", err)
   203  	}
   204  	defer c.Close()
   205  }
   206  
   207  func TestNewClientFromConnection(t *testing.T) {
   208  	t.Parallel()
   209  	ctx := context.Background()
   210  	l, err := net.Listen("tcp", ":0")
   211  	if err != nil {
   212  		t.Fatalf("Cannot listen: %v", err)
   213  	}
   214  	conn, err := grpc.Dial(l.Addr().String(), grpc.WithInsecure())
   215  	if err != nil {
   216  		t.Fatalf("Cannot establish gRPC connection: %v", err)
   217  	}
   218  
   219  	c, err := NewClientFromConnection(ctx, instance, conn, conn, StartupCapabilities(false))
   220  	if err != nil {
   221  		t.Fatalf("Error creating client: %v", err)
   222  	}
   223  	defer c.Close()
   224  
   225  	_, err = NewClientFromConnection(ctx, instance, nil, conn, StartupCapabilities(false))
   226  	if err == nil {
   227  		t.Fatalf("Expected error got nil")
   228  	}
   229  
   230  	_, err = NewClientFromConnection(ctx, instance, conn, nil, StartupCapabilities(false))
   231  	if err == nil {
   232  		t.Fatalf("Expected error got nil")
   233  	}
   234  }
   235  
   236  func TestResourceName(t *testing.T) {
   237  	t.Parallel()
   238  
   239  	tests := []struct {
   240  		name         string
   241  		instanceName string
   242  		segments     []string
   243  		wantName     string
   244  		wantErr      error
   245  	}{
   246  		{
   247  			name:         "valid",
   248  			segments:     []string{"uploads", "uuid", "blobs", "abc", "1", "meta"},
   249  			instanceName: "the/instance",
   250  			wantName:     "the/instance/uploads/uuid/blobs/abc/1/meta",
   251  		},
   252  		{
   253  			name:         "empty_instance_name",
   254  			segments:     []string{"uploads", "uuid", "blobs", "abc", "1", "meta"},
   255  			instanceName: "",
   256  			wantName:     "uploads/uuid/blobs/abc/1/meta",
   257  		},
   258  		{
   259  			name:         "empty_instance_name_no_segments",
   260  			segments:     []string{},
   261  			instanceName: "",
   262  			wantName:     "",
   263  		},
   264  		{
   265  			name:         "empty_segment",
   266  			segments:     []string{"uploads", "", "uuid", "blobs", "abc", "1", "meta"},
   267  			instanceName: "the/instance",
   268  			wantName:     "",
   269  			wantErr:      ErrEmptySegment,
   270  		},
   271  	}
   272  
   273  	for _, test := range tests {
   274  		t.Run(test.name, func(t *testing.T) {
   275  			c := Client{InstanceName: test.instanceName}
   276  			name, err := c.ResourceName(test.segments...)
   277  			if !errors.Is(test.wantErr, err) {
   278  				t.Errorf("unexpected error; want %v, got %v", test.wantErr, err)
   279  			}
   280  			if name != test.wantName {
   281  				t.Errorf("name mismatch; want %q, got %q", test.wantName, name)
   282  			}
   283  		})
   284  	}
   285  }