github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/datastore/crdb/pool_test.go (about)

     1  //go:build docker
     2  
     3  package crdb
     4  
     5  import (
     6  	"context"
     7  	"errors"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/jackc/pgx/v5/pgconn"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	"github.com/authzed/spicedb/internal/datastore/crdb/pool"
    15  	testdatastore "github.com/authzed/spicedb/internal/testserver/datastore"
    16  	"github.com/authzed/spicedb/pkg/datastore"
    17  	"github.com/authzed/spicedb/pkg/namespace"
    18  )
    19  
    20  const (
    21  	testUserNamespace = "test/user"
    22  )
    23  
    24  var testUserNS = namespace.Namespace(testUserNamespace)
    25  
    26  func TestTxReset(t *testing.T) {
    27  	b := testdatastore.RunCRDBForTesting(t, "")
    28  
    29  	cases := []struct {
    30  		name        string
    31  		maxRetries  uint8
    32  		errors      []error
    33  		expectError bool
    34  	}{
    35  		{
    36  			name:       "retryable",
    37  			maxRetries: 4,
    38  			errors: []error{
    39  				&pgconn.PgError{Code: pool.CrdbRetryErrCode},
    40  				&pgconn.PgError{Code: pool.CrdbRetryErrCode},
    41  				&pgconn.PgError{Code: pool.CrdbRetryErrCode},
    42  			},
    43  			expectError: false,
    44  		},
    45  		{
    46  			name:       "resettable",
    47  			maxRetries: 4,
    48  			errors: []error{
    49  				&pgconn.PgError{Code: pool.CrdbAmbiguousErrorCode},
    50  				&pgconn.PgError{Code: pool.CrdbAmbiguousErrorCode},
    51  				&pgconn.PgError{Code: pool.CrdbServerNotAcceptingClients},
    52  			},
    53  			expectError: false,
    54  		},
    55  		{
    56  			name:       "mixed",
    57  			maxRetries: 50,
    58  			errors: []error{
    59  				&pgconn.PgError{Code: pool.CrdbRetryErrCode},
    60  				&pgconn.PgError{Code: pool.CrdbAmbiguousErrorCode},
    61  				&pgconn.PgError{Code: pool.CrdbRetryErrCode},
    62  			},
    63  			expectError: false,
    64  		},
    65  		{
    66  			name:        "noErrors",
    67  			maxRetries:  50,
    68  			errors:      []error{},
    69  			expectError: false,
    70  		},
    71  		{
    72  			name:       "nonRecoverable",
    73  			maxRetries: 1,
    74  			errors: []error{
    75  				&pgconn.PgError{Code: pool.CrdbRetryErrCode},
    76  				&pgconn.PgError{Code: pool.CrdbAmbiguousErrorCode},
    77  			},
    78  			expectError: true,
    79  		},
    80  		{
    81  			name:       "stale connections",
    82  			maxRetries: 3,
    83  			errors: []error{
    84  				errors.New("unexpected EOF"),
    85  				errors.New("broken pipe"),
    86  			},
    87  			expectError: false,
    88  		},
    89  		{
    90  			name:       "clockSkew",
    91  			maxRetries: 1,
    92  			errors: []error{
    93  				&pgconn.PgError{Code: pool.CrdbUnknownSQLState, Message: pool.CrdbClockSkewMessage},
    94  			},
    95  			expectError: false,
    96  		},
    97  	}
    98  	for _, tt := range cases {
    99  		tt := tt
   100  		t.Run(tt.name, func(t *testing.T) {
   101  			require := require.New(t)
   102  			ctx := context.Background()
   103  
   104  			ds := b.NewDatastore(t, func(engine, uri string) datastore.Datastore {
   105  				ds, err := newCRDBDatastore(
   106  					ctx,
   107  					uri,
   108  					GCWindow(24*time.Hour),
   109  					RevisionQuantization(5*time.Second),
   110  					WatchBufferLength(128),
   111  					MaxRetries(tt.maxRetries),
   112  				)
   113  				require.NoError(err)
   114  				return ds
   115  			})
   116  			defer ds.Close()
   117  
   118  			r, err := ds.ReadyState(ctx)
   119  			require.NoError(err)
   120  			require.True(r.IsReady)
   121  
   122  			// WriteNamespace utilizes execute so we'll use it
   123  			i := 0
   124  			rev, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   125  				if i < len(tt.errors) {
   126  					defer func() { i++ }()
   127  					return tt.errors[i]
   128  				}
   129  				return rwt.WriteNamespaces(ctx, testUserNS)
   130  			})
   131  			if tt.expectError {
   132  				require.Error(err)
   133  				require.Equal(datastore.NoRevision, rev)
   134  			} else {
   135  				require.NoError(err)
   136  				require.NotEqual(datastore.NoRevision, rev)
   137  			}
   138  		})
   139  	}
   140  }