blob: 54d45a40c7fc3bfdbb00f60e68f7ec4d6fe0f6b9 [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 reapi
import (
"context"
"errors"
"testing"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"go.chromium.org/build/siso/auth/cred"
)
// fakeConn is a grpcClientConn that records Close calls. Invoke returns
// invokeErr (nil leaves the reply at its zero value, e.g. empty
// ServerCapabilities).
type fakeConn struct {
invokeErr error
closed int
}
func (f *fakeConn) Invoke(ctx context.Context, method string, args, reply any, opts ...grpc.CallOption) error {
return f.invokeErr
}
func (f *fakeConn) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
return nil, errors.New("fakeConn: NewStream not implemented")
}
func (f *fakeConn) Close() error {
f.closed++
return nil
}
func TestClose_ClosesCASConn(t *testing.T) {
ctx := t.Context()
conn := &fakeConn{}
casConn := &fakeConn{}
casDataConn := &fakeConn{}
c, err := NewFromConn(ctx, Option{}, cred.Cred{}, conn, casConn, casDataConn)
if err != nil {
t.Fatalf("NewFromConn: %v", err)
}
if err := c.Close(); err != nil {
t.Fatalf("Close: %v", err)
}
if got, want := conn.closed, 1; got != want {
t.Errorf("conn.closed = %d; want %d", got, want)
}
if got, want := casConn.closed, 1; got != want {
t.Errorf("casConn.closed = %d; want %d", got, want)
}
if got, want := casDataConn.closed, 1; got != want {
t.Errorf("casDataConn.closed = %d; want %d", got, want)
}
}
func TestClose_SharedConnClosedOnce(t *testing.T) {
ctx := t.Context()
conn := &fakeConn{}
c, err := NewFromConn(ctx, Option{}, cred.Cred{}, conn, conn, conn)
if err != nil {
t.Fatalf("NewFromConn: %v", err)
}
if err := c.Close(); err != nil {
t.Fatalf("Close: %v", err)
}
if got, want := conn.closed, 1; got != want {
t.Errorf("conn.closed = %d; want %d", got, want)
}
}
func TestNewFromConn_DigestFunctionErrorClosesBothConns(t *testing.T) {
ctx := t.Context()
conn := &fakeConn{}
casConn := &fakeConn{}
casDataConn := &fakeConn{}
_, err := NewFromConn(ctx, Option{DigestFunction: "bogus"}, cred.Cred{}, conn, casConn, casDataConn)
if err == nil {
t.Fatal("NewFromConn succeeded; want digest function error")
}
if got, want := conn.closed, 1; got != want {
t.Errorf("conn.closed = %d; want %d", got, want)
}
if got, want := casConn.closed, 1; got != want {
t.Errorf("casConn.closed = %d; want %d", got, want)
}
if got, want := casDataConn.closed, 1; got != want {
t.Errorf("casDataConn.closed = %d; want %d", got, want)
}
}
func TestInit_CapabilitiesErrorClosesBothConns(t *testing.T) {
ctx := t.Context()
conn := &fakeConn{invokeErr: status.Error(codes.InvalidArgument, "no capabilities")}
casConn := &fakeConn{}
casDataConn := &fakeConn{}
c, err := NewFromConn(ctx, Option{}, cred.Cred{}, conn, casConn, casDataConn)
if err != nil {
t.Fatalf("NewFromConn: %v", err)
}
if err := c.Init(ctx); err == nil {
t.Fatal("Init succeeded; want GetCapabilities error")
}
if got, want := conn.closed, 1; got != want {
t.Errorf("conn.closed = %d; want %d", got, want)
}
if got, want := casConn.closed, 1; got != want {
t.Errorf("casConn.closed = %d; want %d", got, want)
}
if got, want := casDataConn.closed, 1; got != want {
t.Errorf("casDataConn.closed = %d; want %d", got, want)
}
}
func TestInit_DigestFunctionValidationErrorClosesBothConns(t *testing.T) {
ctx := t.Context()
// Empty ServerCapabilities advertise no digest functions, so any
// function other than sha256 fails validation.
conn := &fakeConn{}
casConn := &fakeConn{}
casDataConn := &fakeConn{}
c, err := NewFromConn(ctx, Option{DigestFunction: "sha1"}, cred.Cred{}, conn, casConn, casDataConn)
if err != nil {
t.Fatalf("NewFromConn: %v", err)
}
if err := c.Init(ctx); err == nil {
t.Fatal("Init succeeded; want digest function validation error")
}
if got, want := conn.closed, 1; got != want {
t.Errorf("conn.closed = %d; want %d", got, want)
}
if got, want := casConn.closed, 1; got != want {
t.Errorf("casConn.closed = %d; want %d", got, want)
}
if got, want := casDataConn.closed, 1; got != want {
t.Errorf("casDataConn.closed = %d; want %d", got, want)
}
}