From e5623c62e937c89db7296cff7e7b2dd2d6a865d4 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Mon, 21 Oct 2024 16:20:05 -0600 Subject: [PATCH 01/13] Add uuid as dep --- go.mod | 1 + go.sum | 2 ++ 2 files changed, 3 insertions(+) diff --git a/go.mod b/go.mod index ba0078e..e3aa17f 100644 --- a/go.mod +++ b/go.mod @@ -93,6 +93,7 @@ require ( github.com/golangci/revgrep v0.5.3 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect diff --git a/go.sum b/go.sum index c423db3..40c857d 100644 --- a/go.sum +++ b/go.sum @@ -292,6 +292,8 @@ github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSF github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= From 684b19237335fc0e32e7c8d098f2ee8dc63b5a85 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Tue, 22 Oct 2024 12:44:43 -0600 Subject: [PATCH 02/13] Add handle to connection in client struct --- v1/client.go | 5 +++++ v1/retryable_client.go | 1 + 2 files changed, 6 insertions(+) diff --git a/v1/client.go b/v1/client.go index df7e83d..bae29ee 100644 --- a/v1/client.go +++ b/v1/client.go @@ -11,6 +11,9 @@ import ( // // Clients are backed by a gRPC client and as such are thread-safe. type Client struct { + // Provide a handle on the underlying connection to enable cleanup + // behaviors (among others) + Conn *grpc.ClientConn v1.SchemaServiceClient v1.PermissionsServiceClient v1.WatchServiceClient @@ -34,6 +37,7 @@ func NewClient(endpoint string, opts ...grpc.DialOption) (*Client, error) { } return &Client{ + conn, v1.NewSchemaServiceClient(conn), v1.NewPermissionsServiceClient(conn), v1.NewWatchServiceClient(conn), @@ -50,6 +54,7 @@ func NewClientWithExperimentalAPIs(endpoint string, opts ...grpc.DialOption) (*C return &ClientWithExperimental{ Client{ + conn, v1.NewSchemaServiceClient(conn), v1.NewPermissionsServiceClient(conn), v1.NewWatchServiceClient(conn), diff --git a/v1/retryable_client.go b/v1/retryable_client.go index def61a4..b4551aa 100644 --- a/v1/retryable_client.go +++ b/v1/retryable_client.go @@ -65,6 +65,7 @@ func NewRetryableClient(endpoint string, opts ...grpc.DialOption) (*RetryableCli return &RetryableClient{ ClientWithExperimental{ Client{ + conn, v1.NewSchemaServiceClient(conn), v1.NewPermissionsServiceClient(conn), v1.NewWatchServiceClient(conn), From 9d6813f047512ff48a2c9c1a6da674b80459eeca Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Tue, 22 Oct 2024 12:44:53 -0600 Subject: [PATCH 03/13] Flesh out client tests --- v1/client_test.go | 408 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 399 insertions(+), 9 deletions(-) diff --git a/v1/client_test.go b/v1/client_test.go index bd0cde2..2657810 100644 --- a/v1/client_test.go +++ b/v1/client_test.go @@ -2,18 +2,24 @@ package authzed_test import ( "context" + "errors" + "io" "log" "testing" "github.com/authzed/grpcutil" + "github.com/google/uuid" "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "google.golang.org/protobuf/types/known/structpb" v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" "github.com/authzed/authzed-go/v1" ) +var fullyConsistent = &v1.Consistency{Requirement: &v1.Consistency_FullyConsistent{FullyConsistent: true}} + func ExampleNewClient() { systemCerts, err := grpcutil.WithSystemCerts(grpcutil.VerifyCA) if err != nil { @@ -30,18 +36,22 @@ func ExampleNewClient() { log.Println(client) } -func TestWriteSchemaCall(t *testing.T) { - t.Parallel() - require := require.New(t) - - // TODO: should we get a handle on the connection in order to be able to close it? - // It should only matter in testing, but it could still be a problem. +func SutClient(t *testing.T) *authzed.Client { + token := uuid.New().String() client, err := authzed.NewClient( "localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()), - grpcutil.WithInsecureBearerToken("some token"), + grpcutil.WithInsecureBearerToken(token), ) - require.NoError(err) + require.NoError(t, err) + return client +} + +func TestBasicSchema(t *testing.T) { + t.Parallel() + require := require.New(t) + client := SutClient(t) + defer client.Conn.Close() schema := ` definition document { @@ -50,6 +60,386 @@ func TestWriteSchemaCall(t *testing.T) { definition user {} ` - _, err = client.SchemaServiceClient.WriteSchema(context.Background(), &v1.WriteSchemaRequest{Schema: schema}) + writeResponse, err := client.SchemaServiceClient.WriteSchema(context.Background(), &v1.WriteSchemaRequest{Schema: schema}) + require.NoError(err) + require.NotEmpty(writeResponse.WrittenAt.String()) + + readResponse, err := client.SchemaServiceClient.ReadSchema(context.Background(), &v1.ReadSchemaRequest{}) + require.NoError(err) + require.Contains(readResponse.SchemaText, "definition document") + require.Contains(readResponse.SchemaText, "definition user") +} + +func TestSchemaWithCaveats(t *testing.T) { + t.Parallel() + require := require.New(t) + client := SutClient(t) + defer client.Conn.Close() + + err := WriteTestSchema(client) + require.NoError(err) +} + +func TestCheck(t *testing.T) { + t.Parallel() + require := require.New(t) + client := SutClient(t) + defer client.Conn.Close() + + err := WriteTestSchema(client) + require.NoError(err) + emilia, beatrice, postOne, _, err := WriteTestTuples(client) + require.NoError(err) + + firstResponse, err := client.PermissionsServiceClient.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ + Resource: postOne, + Permission: "view", + Subject: emilia, + Consistency: fullyConsistent, + }) + require.NoError(err) + require.Equal(v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION, firstResponse.Permissionship) + + secondResponse, err := client.PermissionsServiceClient.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ + Resource: postOne, + Permission: "write", + Subject: emilia, + Consistency: fullyConsistent, + }) + require.NoError(err) + require.Equal(v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION, secondResponse.Permissionship) + + thirdResponse, err := client.PermissionsServiceClient.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ + Resource: postOne, + Permission: "view", + Subject: beatrice, + Consistency: fullyConsistent, + }) + require.NoError(err) + require.Equal(v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION, thirdResponse.Permissionship) + + fourthResponse, err := client.PermissionsServiceClient.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ + Resource: postOne, + Permission: "write", + Subject: beatrice, + Consistency: fullyConsistent, + }) + require.NoError(err) + require.Equal(v1.CheckPermissionResponse_PERMISSIONSHIP_NO_PERMISSION, fourthResponse.Permissionship) +} + +func TestCaveatedCheck(t *testing.T) { + t.Parallel() + require := require.New(t) + client := SutClient(t) + defer client.Conn.Close() + + err := WriteTestSchema(client) + require.NoError(err) + _, beatrice, postOne, _, err := WriteTestTuples(client) + require.NoError(err) + + // Likes Harry Potter + likesContext, err := structpb.NewStruct(map[string]any{"likes": true}) + require.NoError(err) + firstResponse, err := client.PermissionsServiceClient.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ + Resource: postOne, + Permission: "view_as_fan", + Subject: beatrice, + Consistency: fullyConsistent, + Context: likesContext, + }) + require.NoError(err) + require.Equal(v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION, firstResponse.Permissionship) + + // No longer likes Harry Potter + dislikesContext, err := structpb.NewStruct(map[string]any{"likes": false}) + require.NoError(err) + secondResponse, err := client.PermissionsServiceClient.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ + Resource: postOne, + Permission: "view_as_fan", + Subject: beatrice, + Consistency: fullyConsistent, + Context: dislikesContext, + }) + require.NoError(err) + require.Equal(v1.CheckPermissionResponse_PERMISSIONSHIP_NO_PERMISSION, secondResponse.Permissionship) + + // Fandom is in question + require.NoError(err) + thirdResponse, err := client.PermissionsServiceClient.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ + Resource: postOne, + Permission: "view_as_fan", + Subject: beatrice, + Consistency: fullyConsistent, + }) + require.NoError(err) + require.Equal(v1.CheckPermissionResponse_PERMISSIONSHIP_CONDITIONAL_PERMISSION, thirdResponse.Permissionship) + require.Contains(thirdResponse.PartialCaveatInfo.MissingRequiredContext, "likes") +} + +func TestLookupResources(t *testing.T) { + t.Parallel() + require := require.New(t) + client := SutClient(t) + defer client.Conn.Close() + + err := WriteTestSchema(client) + require.NoError(err) + emilia, _, postOne, postTwo, err := WriteTestTuples(client) + require.NoError(err) + + // NOTE: setting a page limit and then cursoring over that limit is entirely overkill + // for this case, where we know how many results we're expecting. This is meant as an + // example to demonstrate a real-world lookupResources usage. + pageLimit := 50 + // The page buffer is where we'll store individual results from the stream + pageBuffer := make([]string, 0, pageLimit) + // Where the result buffer is where we'll concatenate the page buffers together + // to get a final set of results + resultBuffer := make([]string, 0) + + for { + response, err := client.PermissionsServiceClient.LookupResources(context.Background(), &v1.LookupResourcesRequest{ + ResourceObjectType: "post", + Permission: "write", + Subject: emilia, + Consistency: fullyConsistent, + }) + require.NoError(err) + + resultCount := 0 + stream: + for { + item, err := response.Recv() + switch { + case errors.Is(err, io.EOF): + break stream + case err != nil: + require.NoError(err) + default: + resultCount += 1 + pageBuffer = append(pageBuffer, item.ResourceObjectId) + } + } + + resultBuffer = append(resultBuffer, pageBuffer...) + + // If there are no results or the number of results is less than the page limit, + // we know that we've exhausted the pages of results. + if resultCount == 0 || resultCount < pageLimit { + break + } + } + require.Contains(resultBuffer, postOne.ObjectId) + require.Contains(resultBuffer, postTwo.ObjectId) + require.Len(resultBuffer, 2) +} + +func TestLookupSubjects(t *testing.T) { + t.Parallel() + require := require.New(t) + client := SutClient(t) + defer client.Conn.Close() + + err := WriteTestSchema(client) require.NoError(err) + emilia, beatrice, postOne, _, err := WriteTestTuples(client) + require.NoError(err) + + // NOTE: we do a more naive approach here because the LookupSubjects API + // doesn't support cursoring. + resultBuffer := make([]string, 0) + + response, err := client.PermissionsServiceClient.LookupSubjects(context.Background(), &v1.LookupSubjectsRequest{ + SubjectObjectType: "user", + Permission: "view", + Resource: postOne, + Consistency: fullyConsistent, + }) + require.NoError(err) + +stream: + for { + item, err := response.Recv() + switch { + case errors.Is(err, io.EOF): + break stream + case err != nil: + require.NoError(err) + default: + resultBuffer = append(resultBuffer, item.Subject.SubjectObjectId) + } + } + + require.Contains(resultBuffer, emilia.Object.ObjectId) + require.Contains(resultBuffer, beatrice.Object.ObjectId) + require.Len(resultBuffer, 2) +} + +func TestCheckBulkPermissions(t *testing.T) { + t.Parallel() + require := require.New(t) + client := SutClient(t) + defer client.Conn.Close() + + err := WriteTestSchema(client) + require.NoError(err) + emilia, _, postOne, _, err := WriteTestTuples(client) + require.NoError(err) + + response, err := client.PermissionsServiceClient.CheckBulkPermissions(context.Background(), &v1.CheckBulkPermissionsRequest{ + Consistency: fullyConsistent, + Items: []*v1.CheckBulkPermissionsRequestItem{ + { + Resource: postOne, + Permission: "view", + Subject: emilia, + }, + { + Resource: postOne, + Permission: "write", + Subject: emilia, + }, + }, + }) + require.NoError(err) + + require.Len(response.Pairs, 2) + require.Equal(v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION, response.Pairs[0].GetItem().Permissionship) + require.Equal(v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION, response.Pairs[1].GetItem().Permissionship) +} + +func TestBulkExportImport(t *testing.T) { + t.Parallel() + require := require.New(t) + client := SutClient(t) + defer client.Conn.Close() + + err := WriteTestSchema(client) + require.NoError(err) + _, _, _, _, err = WriteTestTuples(client) + require.NoError(err) + + // Validate export + exportResponse, err := client.PermissionsServiceClient.ExportBulkRelationships(context.Background(), &v1.ExportBulkRelationshipsRequest{}) + require.NoError(err) + + exportResults := make([]*v1.Relationship, 0) +stream: + for { + item, err := exportResponse.Recv() + switch { + case errors.Is(err, io.EOF): + break stream + case err != nil: + require.NoError(err) + default: + exportResults = append(exportResults, item.Relationships...) + } + } + + require.Len(exportResults, 4) + + // Note that this has a different preshared key + // Validate import + emptyClient := SutClient(t) + err = WriteTestSchema(emptyClient) + require.NoError(err) + + stream, err := emptyClient.PermissionsServiceClient.ImportBulkRelationships(context.Background()) + require.NoError(err) + err = stream.Send(&v1.ImportBulkRelationshipsRequest{ + Relationships: exportResults, + }) + require.NoError(err) + importResponse, err := stream.CloseAndRecv() + require.NoError(err) + require.Equal(uint64(4), importResponse.NumLoaded) + + // Validate that things were loaded + exportAfterImportResponse, err := emptyClient.PermissionsServiceClient.ExportBulkRelationships(context.Background(), &v1.ExportBulkRelationshipsRequest{}) + require.NoError(err) + + exportAfterImportResults := make([]*v1.Relationship, 0) +afterImportStream: + for { + item, err := exportAfterImportResponse.Recv() + switch { + case errors.Is(err, io.EOF): + break afterImportStream + case err != nil: + require.NoError(err) + default: + exportAfterImportResults = append(exportAfterImportResults, item.Relationships...) + } + } + + require.Len(exportAfterImportResults, 4) +} + +func WriteTestTuples(client *authzed.Client) (emilia *v1.SubjectReference, beatrice *v1.SubjectReference, postOne *v1.ObjectReference, postTwo *v1.ObjectReference, err error) { + emilia = &v1.SubjectReference{Object: &v1.ObjectReference{ObjectType: "user", ObjectId: "emilia"}} + beatrice = &v1.SubjectReference{Object: &v1.ObjectReference{ObjectType: "user", ObjectId: "beatrice"}} + postOne = &v1.ObjectReference{ObjectType: "post", ObjectId: "post-one"} + postTwo = &v1.ObjectReference{ObjectType: "post", ObjectId: "post-two"} + _, err = client.PermissionsServiceClient.WriteRelationships(context.Background(), &v1.WriteRelationshipsRequest{ + Updates: []*v1.RelationshipUpdate{ + { + Operation: v1.RelationshipUpdate_OPERATION_CREATE, + Relationship: &v1.Relationship{ + Resource: postOne, + Relation: "writer", + Subject: emilia, + }, + }, + { + Operation: v1.RelationshipUpdate_OPERATION_CREATE, + Relationship: &v1.Relationship{ + Resource: postTwo, + Relation: "writer", + Subject: emilia, + }, + }, + { + Operation: v1.RelationshipUpdate_OPERATION_CREATE, + Relationship: &v1.Relationship{ + Resource: postOne, + Relation: "reader", + Subject: beatrice, + }, + }, + { + Operation: v1.RelationshipUpdate_OPERATION_CREATE, + Relationship: &v1.Relationship{ + Resource: postOne, + Relation: "caveated_reader", + Subject: beatrice, + OptionalCaveat: &v1.ContextualizedCaveat{CaveatName: "likes_harry_potter"}, + }, + }, + }, + }) + return +} + +func WriteTestSchema(client *authzed.Client) error { + schema := ` + caveat likes_harry_potter(likes bool) { + likes == true + } + + definition post { + relation writer: user + relation reader: user + relation caveated_reader: user with likes_harry_potter + + permission write = writer + permission view = reader + writer + permission view_as_fan = caveated_reader + writer + } + definition user {} + ` + _, err := client.SchemaServiceClient.WriteSchema(context.Background(), &v1.WriteSchemaRequest{Schema: schema}) + return err } From e494456186ed3caba83c333f0e1b3ddf94737f6c Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Tue, 22 Oct 2024 15:10:40 -0600 Subject: [PATCH 04/13] Rename client, fix use of page buffer, use random string generator --- v1/client_test.go | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/v1/client_test.go b/v1/client_test.go index 2657810..694d6c8 100644 --- a/v1/client_test.go +++ b/v1/client_test.go @@ -2,13 +2,14 @@ package authzed_test import ( "context" + "crypto/rand" + "encoding/base64" "errors" "io" "log" "testing" "github.com/authzed/grpcutil" - "github.com/google/uuid" "github.com/stretchr/testify/require" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -36,8 +37,14 @@ func ExampleNewClient() { log.Println(client) } -func SutClient(t *testing.T) *authzed.Client { - token := uuid.New().String() +func randomString(length int) string { + buffer := make([]byte, length) + rand.Read(buffer) + return base64.StdEncoding.EncodeToString(buffer)[:length] +} + +func testClient(t *testing.T) *authzed.Client { + token := randomString(12) client, err := authzed.NewClient( "localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()), @@ -50,7 +57,7 @@ func SutClient(t *testing.T) *authzed.Client { func TestBasicSchema(t *testing.T) { t.Parallel() require := require.New(t) - client := SutClient(t) + client := testClient(t) defer client.Conn.Close() schema := ` @@ -73,7 +80,7 @@ func TestBasicSchema(t *testing.T) { func TestSchemaWithCaveats(t *testing.T) { t.Parallel() require := require.New(t) - client := SutClient(t) + client := testClient(t) defer client.Conn.Close() err := WriteTestSchema(client) @@ -83,7 +90,7 @@ func TestSchemaWithCaveats(t *testing.T) { func TestCheck(t *testing.T) { t.Parallel() require := require.New(t) - client := SutClient(t) + client := testClient(t) defer client.Conn.Close() err := WriteTestSchema(client) @@ -131,7 +138,7 @@ func TestCheck(t *testing.T) { func TestCaveatedCheck(t *testing.T) { t.Parallel() require := require.New(t) - client := SutClient(t) + client := testClient(t) defer client.Conn.Close() err := WriteTestSchema(client) @@ -181,7 +188,7 @@ func TestCaveatedCheck(t *testing.T) { func TestLookupResources(t *testing.T) { t.Parallel() require := require.New(t) - client := SutClient(t) + client := testClient(t) defer client.Conn.Close() err := WriteTestSchema(client) @@ -193,8 +200,6 @@ func TestLookupResources(t *testing.T) { // for this case, where we know how many results we're expecting. This is meant as an // example to demonstrate a real-world lookupResources usage. pageLimit := 50 - // The page buffer is where we'll store individual results from the stream - pageBuffer := make([]string, 0, pageLimit) // Where the result buffer is where we'll concatenate the page buffers together // to get a final set of results resultBuffer := make([]string, 0) @@ -208,7 +213,8 @@ func TestLookupResources(t *testing.T) { }) require.NoError(err) - resultCount := 0 + // The page buffer is where we'll store individual results from the stream + pageBuffer := make([]string, 0, pageLimit) stream: for { item, err := response.Recv() @@ -218,12 +224,12 @@ func TestLookupResources(t *testing.T) { case err != nil: require.NoError(err) default: - resultCount += 1 pageBuffer = append(pageBuffer, item.ResourceObjectId) } } resultBuffer = append(resultBuffer, pageBuffer...) + resultCount := len(pageBuffer) // If there are no results or the number of results is less than the page limit, // we know that we've exhausted the pages of results. @@ -239,7 +245,7 @@ func TestLookupResources(t *testing.T) { func TestLookupSubjects(t *testing.T) { t.Parallel() require := require.New(t) - client := SutClient(t) + client := testClient(t) defer client.Conn.Close() err := WriteTestSchema(client) @@ -280,7 +286,7 @@ stream: func TestCheckBulkPermissions(t *testing.T) { t.Parallel() require := require.New(t) - client := SutClient(t) + client := testClient(t) defer client.Conn.Close() err := WriteTestSchema(client) @@ -313,7 +319,7 @@ func TestCheckBulkPermissions(t *testing.T) { func TestBulkExportImport(t *testing.T) { t.Parallel() require := require.New(t) - client := SutClient(t) + client := testClient(t) defer client.Conn.Close() err := WriteTestSchema(client) @@ -343,7 +349,7 @@ stream: // Note that this has a different preshared key // Validate import - emptyClient := SutClient(t) + emptyClient := testClient(t) err = WriteTestSchema(emptyClient) require.NoError(err) From 92be241310ba77e6ccc3345e40b4dd82ad388d49 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Tue, 22 Oct 2024 15:10:58 -0600 Subject: [PATCH 05/13] Revert deps changes --- go.mod | 1 - go.sum | 2 -- 2 files changed, 3 deletions(-) diff --git a/go.mod b/go.mod index e3aa17f..ba0078e 100644 --- a/go.mod +++ b/go.mod @@ -93,7 +93,6 @@ require ( github.com/golangci/revgrep v0.5.3 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect diff --git a/go.sum b/go.sum index 40c857d..c423db3 100644 --- a/go.sum +++ b/go.sum @@ -292,8 +292,6 @@ github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSF github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= From 6253c0591cc3ac333db443a6b9b67b39329a6eb7 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Tue, 22 Oct 2024 16:38:59 -0600 Subject: [PATCH 06/13] Fix lint issues --- v1/client_test.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/v1/client_test.go b/v1/client_test.go index 694d6c8..9a6a8ff 100644 --- a/v1/client_test.go +++ b/v1/client_test.go @@ -37,14 +37,18 @@ func ExampleNewClient() { log.Println(client) } -func randomString(length int) string { +func randomString(length int) (string, error) { buffer := make([]byte, length) - rand.Read(buffer) - return base64.StdEncoding.EncodeToString(buffer)[:length] + _, err := rand.Read(buffer) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(buffer)[:length], nil } func testClient(t *testing.T) *authzed.Client { - token := randomString(12) + token, err := randomString(12) + require.NoError(t, err) client, err := authzed.NewClient( "localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()), From dfcffdd614afa2b69e75493559117d5e5ee4bdd0 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Tue, 22 Oct 2024 16:44:00 -0600 Subject: [PATCH 07/13] Remove build step that doesn't do anything different from test --- .github/workflows/build.yaml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 .github/workflows/build.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml deleted file mode 100644 index 1494243..0000000 --- a/.github/workflows/build.yaml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: "Build & Test" -on: # yamllint disable-line rule:truthy - push: - branches: - - "main" - pull_request: - branches: ["*"] - merge_group: - types: - - "checks_requested" -jobs: - test: - name: "Run Integration Tests" - runs-on: "ubuntu-latest" - steps: - - uses: "actions/checkout@v4" - - uses: "authzed/actions/setup-go@main" - - uses: "authzed/action-spicedb@v1" - - name: "Unit tests" - run: "go run magefile.go test:integration" From b6f2171cdb3239f5c5ac808d11368d52c820014d Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Wed, 23 Oct 2024 09:40:41 -0600 Subject: [PATCH 08/13] Use helper, simplify loops --- v1/client_test.go | 49 ++++++++++++++++------------------------------- 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/v1/client_test.go b/v1/client_test.go index 9a6a8ff..903f3c5 100644 --- a/v1/client_test.go +++ b/v1/client_test.go @@ -47,6 +47,7 @@ func randomString(length int) (string, error) { } func testClient(t *testing.T) *authzed.Client { + t.Helper() token, err := randomString(12) require.NoError(t, err) client, err := authzed.NewClient( @@ -219,17 +220,13 @@ func TestLookupResources(t *testing.T) { // The page buffer is where we'll store individual results from the stream pageBuffer := make([]string, 0, pageLimit) - stream: for { item, err := response.Recv() - switch { - case errors.Is(err, io.EOF): - break stream - case err != nil: - require.NoError(err) - default: - pageBuffer = append(pageBuffer, item.ResourceObjectId) + if errors.Is(err, io.EOF) { + break } + require.NoError(err) + pageBuffer = append(pageBuffer, item.ResourceObjectId) } resultBuffer = append(resultBuffer, pageBuffer...) @@ -269,17 +266,13 @@ func TestLookupSubjects(t *testing.T) { }) require.NoError(err) -stream: for { item, err := response.Recv() - switch { - case errors.Is(err, io.EOF): - break stream - case err != nil: - require.NoError(err) - default: - resultBuffer = append(resultBuffer, item.Subject.SubjectObjectId) + if errors.Is(err, io.EOF) { + break } + require.NoError(err) + resultBuffer = append(resultBuffer, item.Subject.SubjectObjectId) } require.Contains(resultBuffer, emilia.Object.ObjectId) @@ -336,17 +329,13 @@ func TestBulkExportImport(t *testing.T) { require.NoError(err) exportResults := make([]*v1.Relationship, 0) -stream: for { item, err := exportResponse.Recv() - switch { - case errors.Is(err, io.EOF): - break stream - case err != nil: - require.NoError(err) - default: - exportResults = append(exportResults, item.Relationships...) + if errors.Is(err, io.EOF) { + break } + require.NoError(err) + exportResults = append(exportResults, item.Relationships...) } require.Len(exportResults, 4) @@ -372,17 +361,13 @@ stream: require.NoError(err) exportAfterImportResults := make([]*v1.Relationship, 0) -afterImportStream: for { item, err := exportAfterImportResponse.Recv() - switch { - case errors.Is(err, io.EOF): - break afterImportStream - case err != nil: - require.NoError(err) - default: - exportAfterImportResults = append(exportAfterImportResults, item.Relationships...) + if errors.Is(err, io.EOF) { + break } + require.NoError(err) + exportAfterImportResults = append(exportAfterImportResults, item.Relationships...) } require.Len(exportAfterImportResults, 4) From c445712374f902a86301f11409c230a7834cc25f Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Wed, 23 Oct 2024 09:42:30 -0600 Subject: [PATCH 09/13] Fix flakes by adding consistency --- v1/client_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/v1/client_test.go b/v1/client_test.go index 903f3c5..f89edaf 100644 --- a/v1/client_test.go +++ b/v1/client_test.go @@ -325,7 +325,9 @@ func TestBulkExportImport(t *testing.T) { require.NoError(err) // Validate export - exportResponse, err := client.PermissionsServiceClient.ExportBulkRelationships(context.Background(), &v1.ExportBulkRelationshipsRequest{}) + exportResponse, err := client.PermissionsServiceClient.ExportBulkRelationships(context.Background(), &v1.ExportBulkRelationshipsRequest{ + Consistency: fullyConsistent, + }) require.NoError(err) exportResults := make([]*v1.Relationship, 0) @@ -357,7 +359,9 @@ func TestBulkExportImport(t *testing.T) { require.Equal(uint64(4), importResponse.NumLoaded) // Validate that things were loaded - exportAfterImportResponse, err := emptyClient.PermissionsServiceClient.ExportBulkRelationships(context.Background(), &v1.ExportBulkRelationshipsRequest{}) + exportAfterImportResponse, err := emptyClient.PermissionsServiceClient.ExportBulkRelationships(context.Background(), &v1.ExportBulkRelationshipsRequest{ + Consistency: fullyConsistent, + }) require.NoError(err) exportAfterImportResults := make([]*v1.Relationship, 0) From be80b84ca884bd562e110d1cc61534d0002cacee Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Wed, 23 Oct 2024 09:50:10 -0600 Subject: [PATCH 10/13] Use WithCancel for good form --- v1/client_test.go | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/v1/client_test.go b/v1/client_test.go index f89edaf..43adcc1 100644 --- a/v1/client_test.go +++ b/v1/client_test.go @@ -65,6 +65,9 @@ func TestBasicSchema(t *testing.T) { client := testClient(t) defer client.Conn.Close() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + schema := ` definition document { relation reader: user @@ -72,11 +75,11 @@ func TestBasicSchema(t *testing.T) { definition user {} ` - writeResponse, err := client.SchemaServiceClient.WriteSchema(context.Background(), &v1.WriteSchemaRequest{Schema: schema}) + writeResponse, err := client.SchemaServiceClient.WriteSchema(ctx, &v1.WriteSchemaRequest{Schema: schema}) require.NoError(err) require.NotEmpty(writeResponse.WrittenAt.String()) - readResponse, err := client.SchemaServiceClient.ReadSchema(context.Background(), &v1.ReadSchemaRequest{}) + readResponse, err := client.SchemaServiceClient.ReadSchema(ctx, &v1.ReadSchemaRequest{}) require.NoError(err) require.Contains(readResponse.SchemaText, "definition document") require.Contains(readResponse.SchemaText, "definition user") @@ -98,12 +101,14 @@ func TestCheck(t *testing.T) { client := testClient(t) defer client.Conn.Close() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) err := WriteTestSchema(client) require.NoError(err) emilia, beatrice, postOne, _, err := WriteTestTuples(client) require.NoError(err) - firstResponse, err := client.PermissionsServiceClient.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ + firstResponse, err := client.PermissionsServiceClient.CheckPermission(ctx, &v1.CheckPermissionRequest{ Resource: postOne, Permission: "view", Subject: emilia, @@ -112,7 +117,7 @@ func TestCheck(t *testing.T) { require.NoError(err) require.Equal(v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION, firstResponse.Permissionship) - secondResponse, err := client.PermissionsServiceClient.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ + secondResponse, err := client.PermissionsServiceClient.CheckPermission(ctx, &v1.CheckPermissionRequest{ Resource: postOne, Permission: "write", Subject: emilia, @@ -121,7 +126,7 @@ func TestCheck(t *testing.T) { require.NoError(err) require.Equal(v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION, secondResponse.Permissionship) - thirdResponse, err := client.PermissionsServiceClient.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ + thirdResponse, err := client.PermissionsServiceClient.CheckPermission(ctx, &v1.CheckPermissionRequest{ Resource: postOne, Permission: "view", Subject: beatrice, @@ -130,7 +135,7 @@ func TestCheck(t *testing.T) { require.NoError(err) require.Equal(v1.CheckPermissionResponse_PERMISSIONSHIP_HAS_PERMISSION, thirdResponse.Permissionship) - fourthResponse, err := client.PermissionsServiceClient.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ + fourthResponse, err := client.PermissionsServiceClient.CheckPermission(ctx, &v1.CheckPermissionRequest{ Resource: postOne, Permission: "write", Subject: beatrice, @@ -146,6 +151,8 @@ func TestCaveatedCheck(t *testing.T) { client := testClient(t) defer client.Conn.Close() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) err := WriteTestSchema(client) require.NoError(err) _, beatrice, postOne, _, err := WriteTestTuples(client) @@ -154,7 +161,7 @@ func TestCaveatedCheck(t *testing.T) { // Likes Harry Potter likesContext, err := structpb.NewStruct(map[string]any{"likes": true}) require.NoError(err) - firstResponse, err := client.PermissionsServiceClient.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ + firstResponse, err := client.PermissionsServiceClient.CheckPermission(ctx, &v1.CheckPermissionRequest{ Resource: postOne, Permission: "view_as_fan", Subject: beatrice, @@ -167,7 +174,7 @@ func TestCaveatedCheck(t *testing.T) { // No longer likes Harry Potter dislikesContext, err := structpb.NewStruct(map[string]any{"likes": false}) require.NoError(err) - secondResponse, err := client.PermissionsServiceClient.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ + secondResponse, err := client.PermissionsServiceClient.CheckPermission(ctx, &v1.CheckPermissionRequest{ Resource: postOne, Permission: "view_as_fan", Subject: beatrice, @@ -179,7 +186,7 @@ func TestCaveatedCheck(t *testing.T) { // Fandom is in question require.NoError(err) - thirdResponse, err := client.PermissionsServiceClient.CheckPermission(context.Background(), &v1.CheckPermissionRequest{ + thirdResponse, err := client.PermissionsServiceClient.CheckPermission(ctx, &v1.CheckPermissionRequest{ Resource: postOne, Permission: "view_as_fan", Subject: beatrice, @@ -196,6 +203,8 @@ func TestLookupResources(t *testing.T) { client := testClient(t) defer client.Conn.Close() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) err := WriteTestSchema(client) require.NoError(err) emilia, _, postOne, postTwo, err := WriteTestTuples(client) @@ -210,7 +219,7 @@ func TestLookupResources(t *testing.T) { resultBuffer := make([]string, 0) for { - response, err := client.PermissionsServiceClient.LookupResources(context.Background(), &v1.LookupResourcesRequest{ + response, err := client.PermissionsServiceClient.LookupResources(ctx, &v1.LookupResourcesRequest{ ResourceObjectType: "post", Permission: "write", Subject: emilia, @@ -249,6 +258,8 @@ func TestLookupSubjects(t *testing.T) { client := testClient(t) defer client.Conn.Close() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) err := WriteTestSchema(client) require.NoError(err) emilia, beatrice, postOne, _, err := WriteTestTuples(client) @@ -258,7 +269,7 @@ func TestLookupSubjects(t *testing.T) { // doesn't support cursoring. resultBuffer := make([]string, 0) - response, err := client.PermissionsServiceClient.LookupSubjects(context.Background(), &v1.LookupSubjectsRequest{ + response, err := client.PermissionsServiceClient.LookupSubjects(ctx, &v1.LookupSubjectsRequest{ SubjectObjectType: "user", Permission: "view", Resource: postOne, @@ -286,12 +297,14 @@ func TestCheckBulkPermissions(t *testing.T) { client := testClient(t) defer client.Conn.Close() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) err := WriteTestSchema(client) require.NoError(err) emilia, _, postOne, _, err := WriteTestTuples(client) require.NoError(err) - response, err := client.PermissionsServiceClient.CheckBulkPermissions(context.Background(), &v1.CheckBulkPermissionsRequest{ + response, err := client.PermissionsServiceClient.CheckBulkPermissions(ctx, &v1.CheckBulkPermissionsRequest{ Consistency: fullyConsistent, Items: []*v1.CheckBulkPermissionsRequestItem{ { @@ -319,13 +332,15 @@ func TestBulkExportImport(t *testing.T) { client := testClient(t) defer client.Conn.Close() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) err := WriteTestSchema(client) require.NoError(err) _, _, _, _, err = WriteTestTuples(client) require.NoError(err) // Validate export - exportResponse, err := client.PermissionsServiceClient.ExportBulkRelationships(context.Background(), &v1.ExportBulkRelationshipsRequest{ + exportResponse, err := client.PermissionsServiceClient.ExportBulkRelationships(ctx, &v1.ExportBulkRelationshipsRequest{ Consistency: fullyConsistent, }) require.NoError(err) @@ -348,7 +363,7 @@ func TestBulkExportImport(t *testing.T) { err = WriteTestSchema(emptyClient) require.NoError(err) - stream, err := emptyClient.PermissionsServiceClient.ImportBulkRelationships(context.Background()) + stream, err := emptyClient.PermissionsServiceClient.ImportBulkRelationships(ctx) require.NoError(err) err = stream.Send(&v1.ImportBulkRelationshipsRequest{ Relationships: exportResults, @@ -359,7 +374,7 @@ func TestBulkExportImport(t *testing.T) { require.Equal(uint64(4), importResponse.NumLoaded) // Validate that things were loaded - exportAfterImportResponse, err := emptyClient.PermissionsServiceClient.ExportBulkRelationships(context.Background(), &v1.ExportBulkRelationshipsRequest{ + exportAfterImportResponse, err := emptyClient.PermissionsServiceClient.ExportBulkRelationships(ctx, &v1.ExportBulkRelationshipsRequest{ Consistency: fullyConsistent, }) require.NoError(err) From b1edb937461ce062afd3bbfaeaf23e63cce4687c Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Wed, 23 Oct 2024 10:22:34 -0600 Subject: [PATCH 11/13] Move client connection closing into helper --- v1/client_test.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/v1/client_test.go b/v1/client_test.go index 43adcc1..54afe0c 100644 --- a/v1/client_test.go +++ b/v1/client_test.go @@ -56,6 +56,7 @@ func testClient(t *testing.T) *authzed.Client { grpcutil.WithInsecureBearerToken(token), ) require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, client.Conn.Close()) }) return client } @@ -63,7 +64,6 @@ func TestBasicSchema(t *testing.T) { t.Parallel() require := require.New(t) client := testClient(t) - defer client.Conn.Close() ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -89,7 +89,6 @@ func TestSchemaWithCaveats(t *testing.T) { t.Parallel() require := require.New(t) client := testClient(t) - defer client.Conn.Close() err := WriteTestSchema(client) require.NoError(err) @@ -99,7 +98,6 @@ func TestCheck(t *testing.T) { t.Parallel() require := require.New(t) client := testClient(t) - defer client.Conn.Close() ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -149,7 +147,6 @@ func TestCaveatedCheck(t *testing.T) { t.Parallel() require := require.New(t) client := testClient(t) - defer client.Conn.Close() ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -201,7 +198,6 @@ func TestLookupResources(t *testing.T) { t.Parallel() require := require.New(t) client := testClient(t) - defer client.Conn.Close() ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -256,7 +252,6 @@ func TestLookupSubjects(t *testing.T) { t.Parallel() require := require.New(t) client := testClient(t) - defer client.Conn.Close() ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -295,7 +290,6 @@ func TestCheckBulkPermissions(t *testing.T) { t.Parallel() require := require.New(t) client := testClient(t) - defer client.Conn.Close() ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) @@ -330,7 +324,6 @@ func TestBulkExportImport(t *testing.T) { t.Parallel() require := require.New(t) client := testClient(t) - defer client.Conn.Close() ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) From 27500c0efe77bf15019ef878702de3a7f9b4a632 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Wed, 23 Oct 2024 10:35:04 -0600 Subject: [PATCH 12/13] Make client private --- v1/client.go | 6 +++++- v1/client_test.go | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/v1/client.go b/v1/client.go index bae29ee..ce1cd01 100644 --- a/v1/client.go +++ b/v1/client.go @@ -13,12 +13,16 @@ import ( type Client struct { // Provide a handle on the underlying connection to enable cleanup // behaviors (among others) - Conn *grpc.ClientConn + conn *grpc.ClientConn v1.SchemaServiceClient v1.PermissionsServiceClient v1.WatchServiceClient } +func (c *Client) Close() error { + return c.conn.Close() +} + // ClientWithExperimental represents and open connection to Authzed with // experimental services available. // diff --git a/v1/client_test.go b/v1/client_test.go index 54afe0c..f8594df 100644 --- a/v1/client_test.go +++ b/v1/client_test.go @@ -56,7 +56,7 @@ func testClient(t *testing.T) *authzed.Client { grpcutil.WithInsecureBearerToken(token), ) require.NoError(t, err) - t.Cleanup(func() { require.NoError(t, client.Conn.Close()) }) + t.Cleanup(func() { require.NoError(t, client.Close()) }) return client } From 8af910105e6e0751c18afd6d8e37e91983a6d2ab Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Wed, 23 Oct 2024 10:54:01 -0600 Subject: [PATCH 13/13] Add merge queue to set of triggers --- .github/workflows/test.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 54c802b..7a0ca6a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -6,6 +6,11 @@ on: - "main" pull_request: branches: ["*"] + # NOTE: this is required for checks to be run in the + # merge queue. + merge_group: + types: + - "checks_requested" jobs: tests: name: "Unit and Integration Tests"