github.com/openfga/go-sdk@v0.3.5/client/client_test.go (about) 1 /** 2 * Go SDK for OpenFGA 3 * 4 * API version: 0.1 5 * Website: https://openfga.dev 6 * Documentation: https://openfga.dev/docs 7 * Support: https://discord.gg/8naAwJfWN6 8 * License: [Apache-2.0](https://github.com/openfga/go-sdk/blob/main/LICENSE) 9 * 10 * NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. 11 */ 12 13 package client_test 14 15 import ( 16 "context" 17 "encoding/json" 18 "fmt" 19 "net/http" 20 "testing" 21 22 "github.com/jarcoal/httpmock" 23 "github.com/openfga/go-sdk" 24 . "github.com/openfga/go-sdk/client" 25 ) 26 27 type TestDefinition struct { 28 Name string 29 JsonResponse string 30 ResponseStatus int 31 Method string 32 RequestPath string 33 } 34 35 func TestOpenFgaClient(t *testing.T) { 36 fgaClient, err := NewSdkClient(&ClientConfiguration{ 37 ApiUrl: "https://api.fga.example", 38 StoreId: "01GXSB9YR785C4FYS3C0RTG7B2", 39 }) 40 if err != nil { 41 t.Fatalf("%v", err) 42 } 43 44 t.Run("Allow client to have no store ID specified", func(t *testing.T) { 45 _, err := NewSdkClient(&ClientConfiguration{ 46 ApiHost: "api.fga.example", 47 }) 48 if err != nil { 49 t.Fatalf("Expect no error when store id is not specified but got %v", err) 50 } 51 }) 52 53 t.Run("Allow client to have ApiUrl specified", func(t *testing.T) { 54 _, err := NewSdkClient(&ClientConfiguration{ 55 ApiUrl: "https://api.fga.example", 56 }) 57 if err != nil { 58 t.Fatalf("Expect no error when api url is specified but got %v", err) 59 } 60 }) 61 62 t.Run("Ensure that ApiUrl is well formed", func(t *testing.T) { 63 urls := []string{ 64 "api.fga.example", 65 "https//api.fga.example", 66 "https://api.fga.example:notavalidport", 67 "https://https://api.fga.example", 68 "notavalidscheme://api.fga.example", 69 } 70 for _, uri := range urls { 71 _, err := NewSdkClient(&ClientConfiguration{ 72 ApiUrl: uri, 73 }) 74 if err == nil { 75 t.Fatalf("Expected an error for invalid uri (%s) but got nil", uri) 76 } 77 78 expectedErrorString := fmt.Sprintf("Configuration.ApiUrl (%s) does not form a valid uri", uri) 79 if err.Error() != expectedErrorString { 80 t.Fatalf("Expected error (%s) but got (%s)", expectedErrorString, err.Error()) 81 } 82 } 83 }) 84 85 t.Run("Allow client to have empty store ID specified", func(t *testing.T) { 86 _, err := NewSdkClient(&ClientConfiguration{ 87 ApiHost: "api.fga.example", 88 StoreId: "", 89 }) 90 if err != nil { 91 t.Fatalf("Expect no error when store id is empty but has %v", err) 92 } 93 }) 94 95 t.Run("Validate store ID when specified", func(t *testing.T) { 96 _, err := NewSdkClient(&ClientConfiguration{ 97 ApiHost: "api.fga.example", 98 StoreId: "error", 99 }) 100 if err == nil { 101 t.Fatalf("Expect invalid store ID to result in error but there is none") 102 } 103 }) 104 105 t.Run("Validate auth model ID when specified", func(t *testing.T) { 106 _, err := NewSdkClient(&ClientConfiguration{ 107 ApiHost: "api.fga.example", 108 StoreId: "01GXSB9YR785C4FYS3C0RTG7B2", 109 AuthorizationModelId: "BadAuthID", 110 }) 111 if err == nil { 112 t.Fatalf("Expect invalid auth mode ID to result in error but there is none") 113 } 114 }) 115 116 t.Run("Allow auth model ID to be empty when specified", func(t *testing.T) { 117 _, err := NewSdkClient(&ClientConfiguration{ 118 ApiHost: "api.fga.example", 119 StoreId: "01GXSB9YR785C4FYS3C0RTG7B2", 120 AuthorizationModelId: "", 121 }) 122 if err != nil { 123 t.Fatalf("Expect no error when auth model id is empty but has %v", err) 124 } 125 }) 126 127 t.Run("Allow specifying an ApiUrl with a port and a base path", func(t *testing.T) { 128 _, err := NewSdkClient(&ClientConfiguration{ 129 ApiUrl: "https://api.fga.example:8080/fga", 130 StoreId: "01GXSB9YR785C4FYS3C0RTG7B2", 131 }) 132 if err != nil { 133 t.Fatalf("Expect no error when auth model id is empty but has %v", err) 134 } 135 136 test := TestDefinition{ 137 Name: "ListStores", 138 JsonResponse: `{"stores":[]}`, 139 ResponseStatus: http.StatusOK, 140 Method: http.MethodGet, 141 } 142 143 var expectedResponse openfga.ListStoresResponse 144 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 145 t.Fatalf("%v", err) 146 } 147 148 httpmock.Activate() 149 defer httpmock.DeactivateAndReset() 150 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores", fgaClient.GetConfig().ApiUrl), 151 func(req *http.Request) (*http.Response, error) { 152 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 153 if err != nil { 154 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 155 } 156 return resp, nil 157 }, 158 ) 159 160 got, err := fgaClient.ListStores(context.Background()).Execute() 161 if err != nil { 162 t.Fatalf("%v", err) 163 } 164 if len(got.Stores) != 0 { 165 t.Fatalf("expected stores of length 0, got %v", len(got.Stores)) 166 } 167 }) 168 169 /* Stores */ 170 t.Run("ListStores", func(t *testing.T) { 171 test := TestDefinition{ 172 Name: "ListStores", 173 JsonResponse: `{"stores":[{"id":"01GXSA8YR785C4FYS3C0RTG7B1","name":"Test Store","created_at":"2023-01-01T23:23:23.000000000Z","updated_at":"2023-01-01T23:23:23.000000000Z"}]}`, 174 ResponseStatus: http.StatusOK, 175 Method: http.MethodGet, 176 } 177 178 var expectedResponse openfga.ListStoresResponse 179 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 180 t.Fatalf("%v", err) 181 } 182 183 httpmock.Activate() 184 defer httpmock.DeactivateAndReset() 185 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores", fgaClient.GetConfig().ApiUrl), 186 func(req *http.Request) (*http.Response, error) { 187 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 188 if err != nil { 189 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 190 } 191 return resp, nil 192 }, 193 ) 194 195 options := ClientListStoresOptions{ 196 PageSize: openfga.PtrInt32(10), 197 ContinuationToken: openfga.PtrString("..."), 198 } 199 got, err := fgaClient.ListStores(context.Background()).Options(options).Execute() 200 if err != nil { 201 t.Fatalf("%v", err) 202 } 203 204 if len(got.Stores) != 1 { 205 t.Fatalf("%v", err) 206 } 207 208 if got.Stores[0].Id != expectedResponse.Stores[0].Id { 209 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, got.Stores[0].Id, expectedResponse.Stores[0].Id) 210 } 211 // ListStores without options should work 212 _, err = fgaClient.ListStores(context.Background()).Execute() 213 if err != nil { 214 t.Fatalf("%v", err) 215 } 216 }) 217 218 t.Run("CreateStore", func(t *testing.T) { 219 test := TestDefinition{ 220 Name: "CreateStore", 221 JsonResponse: `{"id":"01GXSA8YR785C4FYS3C0RTG7B1","name":"Test Store","created_at":"2023-01-01T23:23:23.000000000Z","updated_at":"2023-01-01T23:23:23.000000000Z"}`, 222 ResponseStatus: http.StatusOK, 223 Method: http.MethodPost, 224 } 225 requestBody := ClientCreateStoreRequest{ 226 Name: "Test Store", 227 } 228 229 var expectedResponse openfga.CreateStoreResponse 230 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 231 t.Fatalf("%v", err) 232 } 233 234 httpmock.Activate() 235 defer httpmock.DeactivateAndReset() 236 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores", fgaClient.GetConfig().ApiUrl), 237 func(req *http.Request) (*http.Response, error) { 238 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 239 if err != nil { 240 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 241 } 242 return resp, nil 243 }, 244 ) 245 got, err := fgaClient.CreateStore(context.Background()).Body(requestBody).Execute() 246 if err != nil { 247 t.Fatalf("%v", err) 248 } 249 250 _, err = got.MarshalJSON() 251 if err != nil { 252 t.Fatalf("%v", err) 253 } 254 if got.Name != expectedResponse.Name { 255 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, got.Name, expectedResponse.Name) 256 } 257 // CreateStore without options should work 258 _, err = fgaClient.CreateStore(context.Background()).Body(requestBody).Execute() 259 if err != nil { 260 t.Fatalf("%v", err) 261 } 262 }) 263 264 t.Run("GetStore", func(t *testing.T) { 265 test := TestDefinition{ 266 Name: "GetStore", 267 JsonResponse: `{"id":"01GXSA8YR785C4FYS3C0RTG7B1","name":"Test Store","created_at":"2023-01-01T23:23:23.000000000Z","updated_at":"2023-01-01T23:23:23.000000000Z"}`, 268 ResponseStatus: http.StatusOK, 269 Method: http.MethodGet, 270 } 271 272 var expectedResponse openfga.GetStoreResponse 273 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 274 t.Fatalf("%v", err) 275 } 276 277 httpmock.Activate() 278 defer httpmock.DeactivateAndReset() 279 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId), 280 func(req *http.Request) (*http.Response, error) { 281 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 282 if err != nil { 283 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 284 } 285 return resp, nil 286 }, 287 ) 288 289 got, err := fgaClient.GetStore(context.Background()).Execute() 290 if err != nil { 291 t.Fatalf("%v", err) 292 } 293 294 if got.Id != expectedResponse.Id { 295 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, got.Id, expectedResponse.Id) 296 } 297 // GetStore without options should work 298 _, err = fgaClient.GetStore(context.Background()).Execute() 299 if err != nil { 300 t.Fatalf("%v", err) 301 } 302 }) 303 304 t.Run("GetStoreAfterSettingStoreId", func(t *testing.T) { 305 test := TestDefinition{ 306 Name: "GetStoreAfterSettingStoreId", 307 JsonResponse: `{"id":"01GXSA8YR785C4FYS3C0RTG7B1","name":"Test Store","created_at":"2023-01-01T23:23:23.000000000Z","updated_at":"2023-01-01T23:23:23.000000000Z"}`, 308 ResponseStatus: http.StatusOK, 309 Method: http.MethodPost, 310 } 311 312 requestBody := ClientCreateStoreRequest{ 313 Name: "Test Store", 314 } 315 316 var expectedResponse openfga.CreateStoreResponse 317 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 318 t.Fatalf("%v", err) 319 } 320 321 httpmock.Activate() 322 defer httpmock.DeactivateAndReset() 323 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores", fgaClient.GetConfig().ApiUrl), 324 func(req *http.Request) (*http.Response, error) { 325 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 326 if err != nil { 327 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 328 } 329 return resp, nil 330 }, 331 ) 332 333 got1, err1 := fgaClient.CreateStore(context.Background()).Body(requestBody).Execute() 334 if err1 != nil { 335 t.Fatalf("%v", err1) 336 } 337 338 _, err = got1.MarshalJSON() 339 if err != nil { 340 t.Fatalf("%v", err) 341 } 342 if got1.Name != expectedResponse.Name { 343 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, got1.Name, expectedResponse.Name) 344 } 345 346 storeId := got1.Id 347 fgaClient.SetStoreId(storeId) 348 349 httpmock.Activate() 350 defer httpmock.DeactivateAndReset() 351 httpmock.RegisterResponder(http.MethodGet, fmt.Sprintf("%s/stores/%s", fgaClient.GetConfig().ApiUrl, storeId), 352 func(req *http.Request) (*http.Response, error) { 353 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 354 if err != nil { 355 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 356 } 357 return resp, nil 358 }, 359 ) 360 361 got2, err2 := fgaClient.GetStore(context.Background()).Execute() 362 if err2 != nil { 363 t.Fatalf("%v", err2) 364 } 365 366 if got2.Id != storeId { 367 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, got2.Id, storeId) 368 } 369 }) 370 371 t.Run("DeleteStore", func(t *testing.T) { 372 test := TestDefinition{ 373 Name: "DeleteStore", 374 JsonResponse: ``, 375 ResponseStatus: http.StatusNoContent, 376 Method: http.MethodDelete, 377 } 378 379 httpmock.Activate() 380 defer httpmock.DeactivateAndReset() 381 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId), 382 func(req *http.Request) (*http.Response, error) { 383 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, "{}") 384 if err != nil { 385 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 386 } 387 return resp, nil 388 }, 389 ) 390 _, err := fgaClient.DeleteStore(context.Background()).Execute() 391 if err != nil { 392 t.Fatalf("%v", err) 393 } 394 // DeleteStore without options should work 395 _, err = fgaClient.DeleteStore(context.Background()).Execute() 396 if err != nil { 397 t.Fatalf("%v", err) 398 } 399 }) 400 401 /* Authorization Models */ 402 t.Run("ReadAuthorizationModels", func(t *testing.T) { 403 test := TestDefinition{ 404 Name: "ReadAuthorizationModels", 405 JsonResponse: `{"authorization_models":[{"id":"01GXSA8YR785C4FYS3C0RTG7B1","schema_version":"1.1","type_definitions":[]}]}`, 406 ResponseStatus: http.StatusOK, 407 Method: http.MethodGet, 408 RequestPath: "authorization-models", 409 } 410 411 var expectedResponse openfga.ReadAuthorizationModelsResponse 412 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 413 t.Fatalf("%v", err) 414 } 415 416 httpmock.Activate() 417 defer httpmock.DeactivateAndReset() 418 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 419 func(req *http.Request) (*http.Response, error) { 420 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 421 if err != nil { 422 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 423 } 424 return resp, nil 425 }, 426 ) 427 428 options := ClientReadAuthorizationModelsOptions{ 429 PageSize: openfga.PtrInt32(10), 430 ContinuationToken: openfga.PtrString("..."), 431 } 432 got, err := fgaClient.ReadAuthorizationModels(context.Background()).Options(options).Execute() 433 if err != nil { 434 t.Fatalf("%v", err) 435 } 436 437 if len(got.AuthorizationModels) != 1 { 438 t.Fatalf("%v", err) 439 } 440 441 if got.AuthorizationModels[0].Id != expectedResponse.AuthorizationModels[0].Id { 442 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, got.AuthorizationModels[0].Id, expectedResponse.AuthorizationModels[0].Id) 443 } 444 // ReadAuthorizationModels without options should work 445 _, err = fgaClient.ReadAuthorizationModels(context.Background()).Execute() 446 if err != nil { 447 t.Fatalf("%v", err) 448 } 449 }) 450 451 t.Run("WriteAuthorizationModel", func(t *testing.T) { 452 test := TestDefinition{ 453 Name: "WriteAuthorizationModel", 454 JsonResponse: `{"authorization_model_id":"01GXSA8YR785C4FYS3C0RTG7B1"}`, 455 ResponseStatus: http.StatusOK, 456 Method: http.MethodPost, 457 RequestPath: "authorization-models", 458 } 459 requestBody := ClientWriteAuthorizationModelRequest{ 460 SchemaVersion: "1.1", 461 TypeDefinitions: []openfga.TypeDefinition{ 462 {Type: "user", Relations: &map[string]openfga.Userset{}}, 463 { 464 Type: "document", 465 Relations: &map[string]openfga.Userset{ 466 "writer": { 467 This: &map[string]interface{}{}, 468 }, 469 "viewer": {Union: &openfga.Usersets{ 470 Child: []openfga.Userset{ 471 {This: &map[string]interface{}{}}, 472 {ComputedUserset: &openfga.ObjectRelation{ 473 Object: openfga.PtrString(""), 474 Relation: openfga.PtrString("writer"), 475 }}, 476 }, 477 }}, 478 }, 479 Metadata: &openfga.Metadata{ 480 Relations: &map[string]openfga.RelationMetadata{ 481 "writer": { 482 DirectlyRelatedUserTypes: &[]openfga.RelationReference{ 483 {Type: "user"}, 484 }, 485 }, 486 "viewer": { 487 DirectlyRelatedUserTypes: &[]openfga.RelationReference{ 488 {Type: "user"}, 489 }, 490 }, 491 }, 492 }, 493 }}, 494 } 495 496 var expectedResponse openfga.WriteAuthorizationModelResponse 497 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 498 t.Fatalf("%v", err) 499 } 500 501 httpmock.Activate() 502 defer httpmock.DeactivateAndReset() 503 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 504 func(req *http.Request) (*http.Response, error) { 505 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 506 if err != nil { 507 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 508 } 509 return resp, nil 510 }, 511 ) 512 options := ClientWriteAuthorizationModelOptions{} 513 got, err := fgaClient.WriteAuthorizationModel(context.Background()).Body(requestBody).Options(options).Execute() 514 if err != nil { 515 t.Fatalf("%v", err) 516 } 517 518 _, err = got.MarshalJSON() 519 if err != nil { 520 t.Fatalf("%v", err) 521 } 522 523 if got.GetAuthorizationModelId() != expectedResponse.GetAuthorizationModelId() { 524 t.Fatalf("OpenFgaClient.%v() / AuthorizationModelId = %v, want %v", test.Name, got.GetAuthorizationModelId(), expectedResponse.GetAuthorizationModelId()) 525 } 526 527 // WriteAuthorizationModel without options should work 528 _, err = fgaClient.WriteAuthorizationModel(context.Background()).Body(requestBody).Execute() 529 if err != nil { 530 t.Fatalf("%v", err) 531 } 532 }) 533 534 t.Run("WriteAuthorizationModelWithCondition", func(t *testing.T) { 535 test := TestDefinition{ 536 Name: "WriteAuthorizationModelWithCondition", 537 JsonResponse: `{"authorization_model_id":"01GXSA8YR785C4FYS3C0RTG7B1"}`, 538 ResponseStatus: http.StatusOK, 539 Method: http.MethodPost, 540 RequestPath: "authorization-models", 541 } 542 requestBody := ClientWriteAuthorizationModelRequest{ 543 SchemaVersion: "1.1", 544 TypeDefinitions: []openfga.TypeDefinition{ 545 { 546 Type: "user", 547 Relations: &map[string]openfga.Userset{}, 548 }, 549 { 550 Type: "document", 551 Relations: &map[string]openfga.Userset{ 552 "writer": {This: &map[string]interface{}{}}, 553 "viewer": {Union: &openfga.Usersets{ 554 Child: []openfga.Userset{ 555 {This: &map[string]interface{}{}}, 556 {ComputedUserset: &openfga.ObjectRelation{ 557 Object: openfga.PtrString(""), 558 Relation: openfga.PtrString("writer"), 559 }}, 560 }, 561 }}, 562 }, 563 Metadata: &openfga.Metadata{ 564 Relations: &map[string]openfga.RelationMetadata{ 565 "writer": { 566 DirectlyRelatedUserTypes: &[]openfga.RelationReference{ 567 {Type: "user"}, 568 {Type: "user", Condition: openfga.PtrString("ViewCountLessThan200")}, 569 }, 570 }, 571 "viewer": { 572 DirectlyRelatedUserTypes: &[]openfga.RelationReference{ 573 {Type: "user"}, 574 }, 575 }, 576 }, 577 }, 578 }, 579 }, 580 Conditions: &map[string]openfga.Condition{ 581 "ViewCountLessThan200": { 582 Name: "ViewCountLessThan200", 583 Expression: "ViewCount < 200", 584 Parameters: &map[string]openfga.ConditionParamTypeRef{ 585 "ViewCount": { 586 TypeName: openfga.INT, 587 }, 588 "Type": { 589 TypeName: openfga.STRING, 590 }, 591 "Name": { 592 TypeName: openfga.STRING, 593 }, 594 }, 595 }, 596 }, 597 } 598 599 var expectedResponse openfga.WriteAuthorizationModelResponse 600 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 601 t.Fatalf("%v", err) 602 } 603 604 httpmock.Activate() 605 defer httpmock.DeactivateAndReset() 606 httpmock.RegisterMatcherResponder( 607 test.Method, 608 fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 609 httpmock.BodyContainsString(`"ViewCountLessThan200"`), 610 func(req *http.Request) (*http.Response, error) { 611 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 612 if err != nil { 613 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 614 } 615 return resp, nil 616 }, 617 ) 618 options := ClientWriteAuthorizationModelOptions{} 619 got, err := fgaClient.WriteAuthorizationModel(context.Background()).Body(requestBody).Options(options).Execute() 620 if err != nil { 621 t.Fatalf("%v", err) 622 } 623 624 _, err = got.MarshalJSON() 625 if err != nil { 626 t.Fatalf("%v", err) 627 } 628 629 if got.GetAuthorizationModelId() != expectedResponse.GetAuthorizationModelId() { 630 t.Fatalf("OpenFgaClient.%v() / AuthorizationModelId = %v, want %v", test.Name, got.GetAuthorizationModelId(), expectedResponse.GetAuthorizationModelId()) 631 } 632 633 // WriteAuthorizationModel without options should work 634 _, err = fgaClient.WriteAuthorizationModel(context.Background()).Body(requestBody).Execute() 635 if err != nil { 636 t.Fatalf("%v", err) 637 } 638 }) 639 640 t.Run("WriteAuthorizationModelWithCondition2", func(t *testing.T) { 641 test := TestDefinition{ 642 Name: "WriteAuthorizationModelWithCondition2", 643 JsonResponse: `{"authorization_model_id":"01GXSA8YR785C4FYS3C0RTG7B1"}`, 644 ResponseStatus: http.StatusOK, 645 Method: http.MethodPost, 646 RequestPath: "authorization-models", 647 } 648 649 schemaVersion := "1.1" 650 typeDefs := []openfga.TypeDefinition{ 651 { 652 Type: "user", 653 Relations: &map[string]openfga.Userset{}, 654 }, 655 { 656 Type: "document", 657 Relations: &map[string]openfga.Userset{ 658 "writer": {This: &map[string]interface{}{}}, 659 "viewer": {Union: &openfga.Usersets{ 660 Child: []openfga.Userset{ 661 {This: &map[string]interface{}{}}, 662 {ComputedUserset: &openfga.ObjectRelation{ 663 Object: openfga.PtrString(""), 664 Relation: openfga.PtrString("writer"), 665 }}, 666 }, 667 }}, 668 }, 669 Metadata: &openfga.Metadata{ 670 Relations: &map[string]openfga.RelationMetadata{ 671 "writer": { 672 DirectlyRelatedUserTypes: &[]openfga.RelationReference{ 673 {Type: "user"}, 674 {Type: "user", Condition: openfga.PtrString("ViewCountLessThan200")}, 675 }, 676 }, 677 "viewer": { 678 DirectlyRelatedUserTypes: &[]openfga.RelationReference{ 679 {Type: "user"}, 680 }, 681 }, 682 }, 683 }, 684 }, 685 } 686 conditions := map[string]openfga.Condition{ 687 "ViewCountLessThan200": { 688 Name: "ViewCountLessThan200", 689 Expression: "ViewCount < 200", 690 Parameters: &map[string]openfga.ConditionParamTypeRef{ 691 "ViewCount": { 692 TypeName: openfga.INT, 693 }, 694 "Type": { 695 TypeName: openfga.STRING, 696 }, 697 "Name": { 698 TypeName: openfga.STRING, 699 }, 700 }, 701 }, 702 } 703 requestBody := ClientWriteAuthorizationModelRequest{ 704 SchemaVersion: schemaVersion, 705 TypeDefinitions: typeDefs, 706 Conditions: &conditions, 707 } 708 709 var expectedResponse openfga.WriteAuthorizationModelResponse 710 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 711 t.Fatalf("%v", err) 712 } 713 714 httpmock.Activate() 715 defer httpmock.DeactivateAndReset() 716 httpmock.RegisterMatcherResponder( 717 test.Method, 718 fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 719 httpmock.BodyContainsString(`"ViewCountLessThan200"`), 720 func(req *http.Request) (*http.Response, error) { 721 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 722 if err != nil { 723 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 724 } 725 return resp, nil 726 }, 727 ) 728 options := ClientWriteAuthorizationModelOptions{} 729 got, err := fgaClient.WriteAuthorizationModel(context.Background()).Body(requestBody).Options(options).Execute() 730 if err != nil { 731 t.Fatalf("%v", err) 732 } 733 734 _, err = got.MarshalJSON() 735 if err != nil { 736 t.Fatalf("%v", err) 737 } 738 739 if got.GetAuthorizationModelId() != expectedResponse.GetAuthorizationModelId() { 740 t.Fatalf("OpenFgaClient.%v() / AuthorizationModelId = %v, want %v", test.Name, got.GetAuthorizationModelId(), expectedResponse.GetAuthorizationModelId()) 741 } 742 743 // WriteAuthorizationModel without options should work 744 _, err = fgaClient.WriteAuthorizationModel(context.Background()).Body(requestBody).Execute() 745 if err != nil { 746 t.Fatalf("%v", err) 747 } 748 }) 749 750 t.Run("ReadAuthorizationModel", func(t *testing.T) { 751 test := TestDefinition{ 752 Name: "ReadAuthorizationModel", 753 JsonResponse: `{"authorization_model":{"id":"01GXSA8YR785C4FYS3C0RTG7B1","schema_version":"1.1","type_definitions":[{"type":"github-repo", "relations":{"viewer":{"this":{}}}}]}}`, 754 ResponseStatus: http.StatusOK, 755 Method: http.MethodGet, 756 RequestPath: "authorization-models", 757 } 758 759 var expectedResponse openfga.ReadAuthorizationModelResponse 760 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 761 t.Fatalf("%v", err) 762 } 763 modelId := expectedResponse.AuthorizationModel.Id 764 765 httpmock.Activate() 766 defer httpmock.DeactivateAndReset() 767 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath, modelId), 768 func(req *http.Request) (*http.Response, error) { 769 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 770 if err != nil { 771 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 772 } 773 return resp, nil 774 }, 775 ) 776 options := ClientReadAuthorizationModelOptions{ 777 AuthorizationModelId: openfga.PtrString(modelId), 778 } 779 got, err := fgaClient.ReadAuthorizationModel(context.Background()).Options(options).Execute() 780 if err != nil { 781 t.Fatalf("%v", err) 782 } 783 784 responseJson, err := got.MarshalJSON() 785 if err != nil { 786 t.Fatalf("%v", err) 787 } 788 789 if got.AuthorizationModel.Id != modelId { 790 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, string(responseJson), test.JsonResponse) 791 } 792 // ReadAuthorizationModel without options should not work 793 _, err = fgaClient.ReadAuthorizationModel(context.Background()).Execute() 794 expectedError := "Required parameter AuthorizationModelId was not provided" 795 if err == nil || err.Error() != expectedError { 796 t.Fatalf("Expected error:%v, got: %v", expectedError, err) 797 } 798 // ReadAuthorizationModel with options of empty string should not work 799 badOptions := ClientReadAuthorizationModelOptions{ 800 AuthorizationModelId: openfga.PtrString(""), 801 } 802 _, err = fgaClient.ReadAuthorizationModel(context.Background()).Options(badOptions).Execute() 803 if err == nil || err.Error() != expectedError { 804 t.Fatalf("Expected error:%v, got: %v", expectedError, err) 805 } 806 }) 807 808 t.Run("ReadLatestAuthorizationModel", func(t *testing.T) { 809 test := TestDefinition{ 810 Name: "ReadAuthorizationModels", 811 JsonResponse: `{"authorization_models":[{"id":"01GXSA8YR785C4FYS3C0RTG7B1","schema_version":"1.1","type_definitions":[{"type":"github-repo", "relations":{"viewer":{"this":{}}}}]}]}`, 812 ResponseStatus: http.StatusOK, 813 Method: http.MethodGet, 814 RequestPath: "authorization-models", 815 } 816 817 var expectedResponse openfga.ReadAuthorizationModelsResponse 818 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 819 t.Fatalf("%v", err) 820 } 821 modelId := expectedResponse.AuthorizationModels[0].Id 822 823 httpmock.Activate() 824 defer httpmock.DeactivateAndReset() 825 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 826 func(req *http.Request) (*http.Response, error) { 827 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 828 if err != nil { 829 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 830 } 831 return resp, nil 832 }, 833 ) 834 options := ClientReadLatestAuthorizationModelOptions{} 835 got, err := fgaClient.ReadLatestAuthorizationModel(context.Background()).Options(options).Execute() 836 if err != nil { 837 t.Fatalf("%v", err) 838 } 839 840 responseJson, err := got.MarshalJSON() 841 if err != nil { 842 t.Fatalf("%v", err) 843 } 844 845 if got.AuthorizationModel.GetId() != modelId { 846 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, string(responseJson), test.JsonResponse) 847 } 848 // ReadLatestAuthorizationModel without options should work 849 _, err = fgaClient.ReadLatestAuthorizationModel(context.Background()).Execute() 850 if err != nil { 851 t.Fatalf("%v", err) 852 } 853 }) 854 855 /* Relationship Tuples */ 856 t.Run("ReadChanges", func(t *testing.T) { 857 test := TestDefinition{ 858 Name: "ReadChanges", 859 JsonResponse: `{"changes":[{"tuple_key":{"user":"user:81684243-9356-4421-8fbf-a4f8d36aa31b","relation":"viewer","object":"document:roadmap"},"operation":"TUPLE_OPERATION_WRITE","timestamp": "2000-01-01T00:00:00Z"}],"continuation_token":"eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ=="}`, 860 ResponseStatus: http.StatusOK, 861 Method: http.MethodGet, 862 RequestPath: "changes", 863 } 864 865 var expectedResponse openfga.ReadChangesResponse 866 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 867 t.Fatalf("%v", err) 868 } 869 870 httpmock.Activate() 871 defer httpmock.DeactivateAndReset() 872 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 873 func(req *http.Request) (*http.Response, error) { 874 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 875 if err != nil { 876 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 877 } 878 return resp, nil 879 }, 880 ) 881 body := ClientReadChangesRequest{ 882 Type: "document", 883 } 884 options := ClientReadChangesOptions{ContinuationToken: openfga.PtrString("eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ=="), PageSize: openfga.PtrInt32(25)} 885 got, err := fgaClient.ReadChanges(context.Background()).Body(body).Options(options).Execute() 886 if err != nil { 887 t.Fatalf("%v", err) 888 } 889 890 responseJson, err := got.MarshalJSON() 891 if err != nil { 892 t.Fatalf("%v", err) 893 } 894 895 if len(got.Changes) != len(expectedResponse.Changes) { 896 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, string(responseJson), test.JsonResponse) 897 } 898 // ReadChanges without options should work 899 _, err = fgaClient.ReadChanges(context.Background()).Body(body).Execute() 900 if err != nil { 901 t.Fatalf("%v", err) 902 } 903 }) 904 905 t.Run("Read", func(t *testing.T) { 906 test := TestDefinition{ 907 Name: "Read", 908 JsonResponse: `{"tuples":[{"key":{"user":"user:81684243-9356-4421-8fbf-a4f8d36aa31b","relation":"viewer","object":"document:roadmap"},"timestamp": "2000-01-01T00:00:00Z"}]}`, 909 ResponseStatus: http.StatusOK, 910 Method: http.MethodPost, 911 RequestPath: "read", 912 } 913 914 requestBody := ClientReadRequest{ 915 User: openfga.PtrString("user:81684243-9356-4421-8fbf-a4f8d36aa31b"), 916 Relation: openfga.PtrString("viewer"), 917 Object: openfga.PtrString("document:roadmap"), 918 } 919 920 var expectedResponse openfga.ReadResponse 921 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 922 t.Fatalf("%v", err) 923 } 924 925 httpmock.Activate() 926 defer httpmock.DeactivateAndReset() 927 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 928 func(req *http.Request) (*http.Response, error) { 929 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 930 if err != nil { 931 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 932 } 933 return resp, nil 934 }, 935 ) 936 937 options := ClientReadOptions{ 938 PageSize: openfga.PtrInt32(10), 939 ContinuationToken: openfga.PtrString("eyJwayI6IkxBVEVTVF9OU0NPTkZJR19hdXRoMHN0b3JlIiwic2siOiIxem1qbXF3MWZLZExTcUoyN01MdTdqTjh0cWgifQ=="), 940 } 941 got, err := fgaClient.Read(context.Background()).Body(requestBody).Options(options).Execute() 942 if err != nil { 943 t.Fatalf("%v", err) 944 } 945 946 responseJson, err := got.MarshalJSON() 947 if err != nil { 948 t.Fatalf("%v", err) 949 } 950 951 if len(got.Tuples) != len(expectedResponse.Tuples) { 952 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, string(responseJson), test.JsonResponse) 953 } 954 // Read without options should work 955 _, err = fgaClient.Read(context.Background()).Body(requestBody).Execute() 956 if err != nil { 957 t.Fatalf("%v", err) 958 } 959 }) 960 961 t.Run("Write", func(t *testing.T) { 962 test := TestDefinition{ 963 Name: "Write", 964 JsonResponse: `{}`, 965 ResponseStatus: http.StatusOK, 966 Method: http.MethodPost, 967 RequestPath: "write", 968 } 969 requestBody := ClientWriteRequest{ 970 Writes: []ClientTupleKey{{ 971 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 972 Relation: "viewer", 973 Object: "document:roadmap", 974 }}, 975 Deletes: []ClientTupleKeyWithoutCondition{}, 976 } 977 options := ClientWriteOptions{ 978 AuthorizationModelId: openfga.PtrString("01GAHCE4YVKPQEKZQHT2R89MQV"), 979 } 980 981 var expectedResponse map[string]interface{} 982 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 983 t.Fatalf("%v", err) 984 } 985 986 httpmock.Activate() 987 defer httpmock.DeactivateAndReset() 988 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 989 func(req *http.Request) (*http.Response, error) { 990 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 991 if err != nil { 992 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 993 } 994 return resp, nil 995 }, 996 ) 997 data, err := fgaClient.Write(context.Background()).Body(requestBody).Options(options).Execute() 998 if err != nil { 999 t.Fatalf("%v", err) 1000 } 1001 1002 if len(data.Writes) != 1 { 1003 t.Fatalf("OpenFgaClient.%v() - expected %v Writes, got %v", test.Name, 1, len(data.Writes)) 1004 } 1005 1006 if len(data.Deletes) != 0 { 1007 t.Fatalf("OpenFgaClient.%v() - expected %v Deletes, got %v", test.Name, 0, len(data.Deletes)) 1008 } 1009 1010 for index := 0; index < len(data.Writes); index++ { 1011 response := data.Writes[index] 1012 if response.Error != nil { 1013 t.Fatalf("OpenFgaClient.%v()|%d/ %v", test.Name, index, response.Error) 1014 } 1015 if response.HttpResponse.StatusCode != test.ResponseStatus { 1016 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, response.HttpResponse.StatusCode, test.ResponseStatus) 1017 } 1018 1019 _, err := response.MarshalJSON() 1020 if err != nil { 1021 t.Fatalf("%v", err) 1022 } 1023 } 1024 1025 for index := 0; index < len(data.Deletes); index++ { 1026 response := data.Deletes[index] 1027 if response.Error != nil { 1028 t.Fatalf("OpenFgaClient.%v()|%d/ %v", test.Name, index, response.Error) 1029 } 1030 if response.HttpResponse.StatusCode != test.ResponseStatus { 1031 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, response.HttpResponse.StatusCode, test.ResponseStatus) 1032 } 1033 1034 _, err := response.MarshalJSON() 1035 if err != nil { 1036 t.Fatalf("%v", err) 1037 } 1038 } 1039 // Write without options should work 1040 _, err = fgaClient.Write(context.Background()).Body(requestBody).Execute() 1041 if err != nil { 1042 t.Fatalf("%v", err) 1043 } 1044 }) 1045 1046 t.Run("Write with invalid auth model id", func(t *testing.T) { 1047 test := TestDefinition{ 1048 Name: "Write", 1049 JsonResponse: `{}`, 1050 ResponseStatus: http.StatusOK, 1051 Method: http.MethodPost, 1052 RequestPath: "write", 1053 } 1054 requestBody := ClientWriteRequest{ 1055 Writes: []ClientTupleKey{{ 1056 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1057 Relation: "viewer", 1058 Object: "document:roadmap", 1059 }}, 1060 } 1061 options := ClientWriteOptions{ 1062 AuthorizationModelId: openfga.PtrString("INVALID"), 1063 } 1064 1065 var expectedResponse map[string]interface{} 1066 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 1067 t.Fatalf("%v", err) 1068 } 1069 1070 _, err := fgaClient.Write(context.Background()).Body(requestBody).Options(options).Execute() 1071 if err == nil { 1072 t.Fatalf("Expect error due to invalid auth model ID but there is none") 1073 } 1074 }) 1075 1076 t.Run("WriteNonTransaction", func(t *testing.T) { 1077 test := TestDefinition{ 1078 Name: "Write", 1079 JsonResponse: `{}`, 1080 ResponseStatus: http.StatusOK, 1081 Method: http.MethodPost, 1082 RequestPath: "write", 1083 } 1084 requestBody := ClientWriteRequest{ 1085 Writes: []ClientTupleKey{{ 1086 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1087 Relation: "viewer", 1088 Object: "document:roadmap", 1089 }, { 1090 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1091 Relation: "viewer", 1092 Object: "document:budget", 1093 }}, 1094 Deletes: []ClientTupleKeyWithoutCondition{{ 1095 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1096 Relation: "viewer", 1097 Object: "document:planning", 1098 }}, 1099 } 1100 const authModelId = "01GAHCE4YVKPQEKZQHT2R89MQV" 1101 options := ClientWriteOptions{ 1102 AuthorizationModelId: openfga.PtrString(authModelId), 1103 Transaction: &TransactionOptions{ 1104 Disable: true, 1105 MaxParallelRequests: 5, 1106 MaxPerChunk: 1, 1107 }, 1108 } 1109 1110 var expectedResponse map[string]interface{} 1111 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 1112 t.Fatalf("%v", err) 1113 } 1114 1115 httpmock.Activate() 1116 defer httpmock.DeactivateAndReset() 1117 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 1118 func(req *http.Request) (*http.Response, error) { 1119 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 1120 if err != nil { 1121 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 1122 } 1123 return resp, nil 1124 }, 1125 ) 1126 httpmock.RegisterResponder("GET", fmt.Sprintf("%s/stores/%s/authorization-models/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, authModelId), 1127 func(req *http.Request) (*http.Response, error) { 1128 return httpmock.NewStringResponse(http.StatusOK, ""), nil 1129 }, 1130 ) 1131 data, err := fgaClient.Write(context.Background()).Body(requestBody).Options(options).Execute() 1132 if err != nil { 1133 t.Fatalf("%v", err) 1134 } 1135 1136 if len(data.Writes) != 2 { 1137 t.Fatalf("OpenFgaClient.%v() - expected %v Writes, got %v", test.Name, 2, len(data.Writes)) 1138 } 1139 1140 if len(data.Deletes) != 1 { 1141 t.Fatalf("OpenFgaClient.%v() - expected %v Deletes, got %v", test.Name, 1, len(data.Deletes)) 1142 } 1143 1144 for index := 0; index < len(data.Writes); index++ { 1145 response := data.Writes[index] 1146 if response.Error != nil { 1147 t.Fatalf("OpenFgaClient.%v()|%d/ %v", test.Name, index, response.Error) 1148 } 1149 if response.HttpResponse.StatusCode != test.ResponseStatus { 1150 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, response.HttpResponse.StatusCode, test.ResponseStatus) 1151 } 1152 1153 _, err := response.MarshalJSON() 1154 if err != nil { 1155 t.Fatalf("%v", err) 1156 } 1157 } 1158 1159 for index := 0; index < len(data.Deletes); index++ { 1160 response := data.Deletes[index] 1161 if response.Error != nil { 1162 t.Fatalf("OpenFgaClient.%v()|%d/ %v", test.Name, index, response.Error) 1163 } 1164 if response.HttpResponse.StatusCode != test.ResponseStatus { 1165 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, response.HttpResponse.StatusCode, test.ResponseStatus) 1166 } 1167 1168 _, err := response.MarshalJSON() 1169 if err != nil { 1170 t.Fatalf("%v", err) 1171 } 1172 } 1173 }) 1174 1175 t.Run("WriteTuples", func(t *testing.T) { 1176 test := TestDefinition{ 1177 Name: "Write", 1178 JsonResponse: `{}`, 1179 ResponseStatus: http.StatusOK, 1180 Method: http.MethodPost, 1181 RequestPath: "write", 1182 } 1183 requestBody := []ClientTupleKey{{ 1184 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1185 Relation: "viewer", 1186 Object: "document:roadmap", 1187 }} 1188 options := ClientWriteOptions{ 1189 AuthorizationModelId: openfga.PtrString("01GAHCE4YVKPQEKZQHT2R89MQV"), 1190 } 1191 1192 var expectedResponse map[string]interface{} 1193 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 1194 t.Fatalf("%v", err) 1195 } 1196 1197 httpmock.Activate() 1198 defer httpmock.DeactivateAndReset() 1199 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 1200 func(req *http.Request) (*http.Response, error) { 1201 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 1202 if err != nil { 1203 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 1204 } 1205 return resp, nil 1206 }, 1207 ) 1208 data, err := fgaClient.WriteTuples(context.Background()).Body(requestBody).Options(options).Execute() 1209 if err != nil { 1210 t.Fatalf("%v", err) 1211 } 1212 1213 if len(data.Writes) != 1 { 1214 t.Fatalf("OpenFgaClient.%v() - expected %v Writes, got %v", test.Name, 1, len(data.Writes)) 1215 } 1216 1217 if len(data.Deletes) != 0 { 1218 t.Fatalf("OpenFgaClient.%v() - expected %v Deletes, got %v", test.Name, 0, len(data.Deletes)) 1219 } 1220 1221 for index := 0; index < len(data.Writes); index++ { 1222 response := data.Writes[index] 1223 if response.Error != nil { 1224 t.Fatalf("OpenFgaClient.%v()|%d/ %v", test.Name, index, response.Error) 1225 } 1226 if response.HttpResponse.StatusCode != test.ResponseStatus { 1227 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, response.HttpResponse.StatusCode, test.ResponseStatus) 1228 } 1229 1230 _, err := response.MarshalJSON() 1231 if err != nil { 1232 t.Fatalf("%v", err) 1233 } 1234 } 1235 1236 for index := 0; index < len(data.Deletes); index++ { 1237 response := data.Deletes[index] 1238 if response.Error != nil { 1239 t.Fatalf("OpenFgaClient.%v()|%d/ %v", test.Name, index, response.Error) 1240 } 1241 if response.HttpResponse.StatusCode != test.ResponseStatus { 1242 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, response.HttpResponse.StatusCode, test.ResponseStatus) 1243 } 1244 1245 _, err := response.MarshalJSON() 1246 if err != nil { 1247 t.Fatalf("%v", err) 1248 } 1249 } 1250 // WriteTuples without options should work 1251 _, err = fgaClient.WriteTuples(context.Background()).Body(requestBody).Execute() 1252 if err != nil { 1253 t.Fatalf("%v", err) 1254 } 1255 }) 1256 1257 t.Run("DeleteTuples", func(t *testing.T) { 1258 test := TestDefinition{ 1259 Name: "Write", 1260 JsonResponse: `{}`, 1261 ResponseStatus: http.StatusOK, 1262 Method: http.MethodPost, 1263 RequestPath: "write", 1264 } 1265 1266 requestBody := []ClientTupleKeyWithoutCondition{{ 1267 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1268 Relation: "viewer", 1269 Object: "document:roadmap", 1270 }} 1271 options := ClientWriteOptions{ 1272 AuthorizationModelId: openfga.PtrString("01GAHCE4YVKPQEKZQHT2R89MQV"), 1273 } 1274 1275 var expectedResponse map[string]interface{} 1276 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 1277 t.Fatalf("%v", err) 1278 } 1279 1280 httpmock.Activate() 1281 defer httpmock.DeactivateAndReset() 1282 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 1283 func(req *http.Request) (*http.Response, error) { 1284 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 1285 if err != nil { 1286 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 1287 } 1288 return resp, nil 1289 }, 1290 ) 1291 data, err := fgaClient.DeleteTuples(context.Background()).Body(requestBody).Options(options).Execute() 1292 if err != nil { 1293 t.Fatalf("%v", err) 1294 } 1295 1296 if len(data.Writes) != 0 { 1297 t.Fatalf("OpenFgaClient.%v() - expected no Writes, got %v", test.Name, len(data.Writes)) 1298 } 1299 1300 if len(data.Deletes) != 1 { 1301 t.Fatalf("OpenFgaClient.%v() - expected no Deletes, got %v", test.Name, len(data.Deletes)) 1302 } 1303 1304 for index := 0; index < len(data.Writes); index++ { 1305 response := data.Writes[index] 1306 if response.Error != nil { 1307 t.Fatalf("OpenFgaClient.%v()|%d/ %v", test.Name, index, response.Error) 1308 } 1309 if response.HttpResponse.StatusCode != test.ResponseStatus { 1310 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, response.HttpResponse.StatusCode, test.ResponseStatus) 1311 } 1312 1313 _, err := response.MarshalJSON() 1314 if err != nil { 1315 t.Fatalf("%v", err) 1316 } 1317 } 1318 1319 for index := 0; index < len(data.Deletes); index++ { 1320 response := data.Deletes[index] 1321 if response.Error != nil { 1322 t.Fatalf("OpenFgaClient.%v()|%d/ %v", test.Name, index, response.Error) 1323 } 1324 if response.HttpResponse.StatusCode != test.ResponseStatus { 1325 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, response.HttpResponse.StatusCode, test.ResponseStatus) 1326 } 1327 1328 _, err := response.MarshalJSON() 1329 if err != nil { 1330 t.Fatalf("%v", err) 1331 } 1332 } 1333 // DeleteTuples without options should work 1334 _, err = fgaClient.DeleteTuples(context.Background()).Body(requestBody).Execute() 1335 if err != nil { 1336 t.Fatalf("%v", err) 1337 } 1338 }) 1339 1340 /* Relationship Queries */ 1341 t.Run("Check", func(t *testing.T) { 1342 test := TestDefinition{ 1343 Name: "Check", 1344 JsonResponse: `{"allowed":true, "resolution":""}`, 1345 ResponseStatus: http.StatusOK, 1346 Method: http.MethodPost, 1347 RequestPath: "check", 1348 } 1349 requestBody := ClientCheckRequest{ 1350 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1351 Relation: "viewer", 1352 Object: "document:roadmap", 1353 ContextualTuples: []ClientContextualTupleKey{{ 1354 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1355 Relation: "editor", 1356 Object: "document:roadmap", 1357 }}, 1358 } 1359 1360 options := ClientCheckOptions{ 1361 AuthorizationModelId: openfga.PtrString("01GAHCE4YVKPQEKZQHT2R89MQV"), 1362 } 1363 1364 var expectedResponse openfga.CheckResponse 1365 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 1366 t.Fatalf("%v", err) 1367 } 1368 1369 httpmock.Activate() 1370 defer httpmock.DeactivateAndReset() 1371 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 1372 func(req *http.Request) (*http.Response, error) { 1373 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 1374 if err != nil { 1375 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 1376 } 1377 return resp, nil 1378 }, 1379 ) 1380 got, err := fgaClient.Check(context.Background()).Body(requestBody).Options(options).Execute() 1381 if err != nil { 1382 t.Fatalf("%v", err) 1383 } 1384 1385 responseJson, err := got.MarshalJSON() 1386 if err != nil { 1387 t.Fatalf("%v", err) 1388 } 1389 1390 if got.GetAllowed() != *expectedResponse.Allowed { 1391 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, string(responseJson), test.JsonResponse) 1392 } 1393 // Check without options should work 1394 _, err = fgaClient.Check(context.Background()).Body(requestBody).Execute() 1395 if err != nil { 1396 t.Fatalf("%v", err) 1397 } 1398 1399 // check with invalid auth model id should result in error 1400 badOptions := ClientCheckOptions{ 1401 AuthorizationModelId: openfga.PtrString("INVALID"), 1402 } 1403 _, err = fgaClient.Check(context.Background()).Body(requestBody).Options(badOptions).Execute() 1404 if err == nil { 1405 t.Fatalf("Expect error with bad auth model id but there is none") 1406 } 1407 1408 }) 1409 1410 t.Run("BatchCheck", func(t *testing.T) { 1411 test := TestDefinition{ 1412 Name: "Check", 1413 JsonResponse: `{"allowed":true, "resolution":""}`, 1414 ResponseStatus: http.StatusOK, 1415 Method: http.MethodPost, 1416 RequestPath: "check", 1417 } 1418 requestBody := ClientBatchCheckBody{{ 1419 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1420 Relation: "viewer", 1421 Object: "document:roadmap", 1422 ContextualTuples: []ClientContextualTupleKey{{ 1423 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1424 Relation: "editor", 1425 Object: "document:roadmap", 1426 }}, 1427 }, { 1428 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1429 Relation: "admin", 1430 Object: "document:roadmap", 1431 ContextualTuples: []ClientContextualTupleKey{{ 1432 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1433 Relation: "editor", 1434 Object: "document:roadmap", 1435 }}, 1436 }, { 1437 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1438 Relation: "creator", 1439 Object: "document:roadmap", 1440 }, { 1441 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1442 Relation: "deleter", 1443 Object: "document:roadmap", 1444 }} 1445 1446 const authModelId = "01GAHCE4YVKPQEKZQHT2R89MQV" 1447 1448 options := ClientBatchCheckOptions{ 1449 AuthorizationModelId: openfga.PtrString(authModelId), 1450 MaxParallelRequests: openfga.PtrInt32(5), 1451 } 1452 1453 var expectedResponse openfga.CheckResponse 1454 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 1455 t.Fatalf("%v", err) 1456 } 1457 1458 httpmock.Activate() 1459 defer httpmock.DeactivateAndReset() 1460 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 1461 func(req *http.Request) (*http.Response, error) { 1462 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 1463 if err != nil { 1464 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 1465 } 1466 return resp, nil 1467 }, 1468 ) 1469 httpmock.RegisterResponder("GET", fmt.Sprintf("%s/stores/%s/authorization-models", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId), 1470 func(req *http.Request) (*http.Response, error) { 1471 return httpmock.NewStringResponse(http.StatusOK, ""), nil 1472 }, 1473 ) 1474 httpmock.RegisterResponder("GET", fmt.Sprintf("%s/stores/%s/authorization-models/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, authModelId), 1475 func(req *http.Request) (*http.Response, error) { 1476 return httpmock.NewStringResponse(http.StatusOK, ""), nil 1477 }, 1478 ) 1479 got, err := fgaClient.BatchCheck(context.Background()).Body(requestBody).Options(options).Execute() 1480 if err != nil { 1481 t.Fatalf("%v", err) 1482 } 1483 1484 if httpmock.GetTotalCallCount() != 5 { 1485 t.Fatalf("OpenFgaClient.%v() - wanted %v calls to /check + 1 call to validate auth model, got %v", test.Name, 4, httpmock.GetTotalCallCount()) 1486 } 1487 1488 if len(*got) != len(requestBody) { 1489 t.Fatalf("OpenFgaClient.%v() - Response Length = %v, want %v", test.Name, len(*got), len(requestBody)) 1490 } 1491 1492 for index := 0; index < len(*got); index++ { 1493 response := (*got)[index] 1494 if response.Error != nil { 1495 t.Fatalf("OpenFgaClient.%v()|%d/ %v", test.Name, index, response.Error) 1496 } 1497 if response.HttpResponse.StatusCode != test.ResponseStatus { 1498 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, response.HttpResponse.StatusCode, test.ResponseStatus) 1499 } 1500 1501 responseJson, err := response.MarshalJSON() 1502 if err != nil { 1503 t.Fatalf("%v", err) 1504 } 1505 1506 if *response.Allowed != *expectedResponse.Allowed { 1507 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, string(responseJson), test.JsonResponse) 1508 } 1509 } 1510 // BatchCheck without options should work 1511 _, err = fgaClient.BatchCheck(context.Background()).Body(requestBody).Execute() 1512 if err != nil { 1513 t.Fatalf("%v", err) 1514 } 1515 // BatchCheck with invalid auth model ID should fail 1516 badOptions := ClientBatchCheckOptions{ 1517 AuthorizationModelId: openfga.PtrString("INVALID"), 1518 MaxParallelRequests: openfga.PtrInt32(5), 1519 } 1520 _, err = fgaClient.BatchCheck(context.Background()).Body(requestBody).Options(badOptions).Execute() 1521 if err == nil { 1522 t.Fatalf("Expect error with invalid auth model id but there is none") 1523 } 1524 1525 }) 1526 1527 t.Run("BatchCheckConnectionProblem", func(t *testing.T) { 1528 test := TestDefinition{ 1529 Name: "Check", 1530 JsonResponse: `{"allowed":true, "resolution":""}`, 1531 ResponseStatus: http.StatusOK, 1532 Method: http.MethodPost, 1533 RequestPath: "check", 1534 } 1535 requestBody := ClientBatchCheckBody{{ 1536 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1537 Relation: "viewer", 1538 Object: "document:roadmap", 1539 ContextualTuples: []ClientContextualTupleKey{{ 1540 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1541 Relation: "editor", 1542 Object: "document:roadmap", 1543 }}, 1544 }, { 1545 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1546 Relation: "admin", 1547 Object: "document:roadmap", 1548 ContextualTuples: []ClientContextualTupleKey{{ 1549 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1550 Relation: "editor", 1551 Object: "document:roadmap", 1552 }}, 1553 }, { 1554 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1555 Relation: "creator", 1556 Object: "document:roadmap", 1557 }, { 1558 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1559 Relation: "deleter", 1560 Object: "document:roadmap", 1561 }} 1562 1563 const authModelId = "01GAHCE4YVKPQEKZQHT2R89MQV" 1564 1565 options := ClientBatchCheckOptions{ 1566 AuthorizationModelId: openfga.PtrString(authModelId), 1567 MaxParallelRequests: openfga.PtrInt32(5), 1568 } 1569 1570 var expectedResponse openfga.CheckResponse 1571 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 1572 t.Fatalf("%v", err) 1573 } 1574 1575 httpmock.Activate() 1576 defer httpmock.DeactivateAndReset() 1577 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 1578 func(req *http.Request) (*http.Response, error) { 1579 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 1580 if err != nil { 1581 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 1582 } 1583 return resp, nil 1584 }, 1585 ) 1586 httpmock.RegisterResponder("GET", fmt.Sprintf("%s/stores/%s/authorization-models", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId), 1587 func(req *http.Request) (*http.Response, error) { 1588 return httpmock.NewStringResponse(http.StatusUnauthorized, ""), nil 1589 }, 1590 ) 1591 httpmock.RegisterResponder("GET", fmt.Sprintf("%s/stores/%s/authorization-models/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, authModelId), 1592 func(req *http.Request) (*http.Response, error) { 1593 return httpmock.NewStringResponse(http.StatusUnauthorized, ""), nil 1594 }, 1595 ) 1596 _, err := fgaClient.BatchCheck(context.Background()).Body(requestBody).Options(options).Execute() 1597 if err == nil { 1598 t.Fatalf("Expect error but there is none") 1599 } 1600 1601 }) 1602 1603 t.Run("Expand", func(t *testing.T) { 1604 test := TestDefinition{ 1605 Name: "Expand", 1606 JsonResponse: `{"tree":{"root":{"name":"document:roadmap#viewer","union":{"nodes":[{"name": "document:roadmap#viewer","leaf":{"users":{"users":["user:81684243-9356-4421-8fbf-a4f8d36aa31b"]}}}]}}}}`, 1607 ResponseStatus: http.StatusOK, 1608 Method: http.MethodPost, 1609 RequestPath: "expand", 1610 } 1611 1612 requestBody := ClientExpandRequest{ 1613 Relation: "viewer", 1614 Object: "document:roadmap", 1615 } 1616 options := ClientExpandOptions{ 1617 AuthorizationModelId: openfga.PtrString("01GAHCE4YVKPQEKZQHT2R89MQV"), 1618 } 1619 1620 var expectedResponse openfga.ExpandResponse 1621 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 1622 t.Fatalf("%v", err) 1623 } 1624 1625 httpmock.Activate() 1626 defer httpmock.DeactivateAndReset() 1627 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 1628 func(req *http.Request) (*http.Response, error) { 1629 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 1630 if err != nil { 1631 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 1632 } 1633 return resp, nil 1634 }, 1635 ) 1636 got, err := fgaClient.Expand(context.Background()).Body(requestBody).Options(options).Execute() 1637 if err != nil { 1638 t.Fatalf("%v", err) 1639 } 1640 1641 _, err = got.MarshalJSON() 1642 if err != nil { 1643 t.Fatalf("%v", err) 1644 } 1645 // Expand without options should work 1646 _, err = fgaClient.Expand(context.Background()).Body(requestBody).Execute() 1647 if err != nil { 1648 t.Fatalf("%v", err) 1649 } 1650 // Invalid auth model ID should result in error 1651 badOptions := ClientExpandOptions{ 1652 AuthorizationModelId: openfga.PtrString("INVALID"), 1653 } 1654 _, err = fgaClient.Expand(context.Background()).Body(requestBody).Options(badOptions).Execute() 1655 if err == nil { 1656 t.Fatalf("Expect error for invalid auth model id but there is none") 1657 } 1658 }) 1659 1660 t.Run("ListObjects", func(t *testing.T) { 1661 test := TestDefinition{ 1662 Name: "ListObjects", 1663 JsonResponse: `{"objects":["document:roadmap"]}`, 1664 ResponseStatus: http.StatusOK, 1665 Method: http.MethodPost, 1666 RequestPath: "list-objects", 1667 } 1668 1669 requestBody := ClientListObjectsRequest{ 1670 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1671 Relation: "can_read", 1672 Type: "document", 1673 ContextualTuples: []ClientContextualTupleKey{{ 1674 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1675 Relation: "editor", 1676 Object: "folder:product", 1677 }, { 1678 User: "folder:product", 1679 Relation: "parent", 1680 Object: "document:roadmap", 1681 }}, 1682 } 1683 options := ClientListObjectsOptions{ 1684 AuthorizationModelId: openfga.PtrString("01GAHCE4YVKPQEKZQHT2R89MQV"), 1685 } 1686 1687 var expectedResponse openfga.ListObjectsResponse 1688 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 1689 t.Fatalf("%v", err) 1690 } 1691 1692 httpmock.Activate() 1693 defer httpmock.DeactivateAndReset() 1694 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 1695 func(req *http.Request) (*http.Response, error) { 1696 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 1697 if err != nil { 1698 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 1699 } 1700 return resp, nil 1701 }, 1702 ) 1703 got, err := fgaClient.ListObjects(context.Background()). 1704 Body(requestBody). 1705 Options(options). 1706 Execute() 1707 if err != nil { 1708 t.Fatalf("%v", err) 1709 } 1710 1711 responseJson, err := got.MarshalJSON() 1712 if err != nil { 1713 t.Fatalf("%v", err) 1714 } 1715 1716 if len(got.Objects) != len(expectedResponse.Objects) || (got.Objects)[0] != (expectedResponse.Objects)[0] { 1717 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, string(responseJson), test.JsonResponse) 1718 } 1719 // ListObjects without options should work 1720 _, err = fgaClient.ListObjects(context.Background()).Body(requestBody).Execute() 1721 if err != nil { 1722 t.Fatalf("%v", err) 1723 } 1724 // Invalid auth model id should result in error 1725 badOptions := ClientListObjectsOptions{ 1726 AuthorizationModelId: openfga.PtrString("INVALID"), 1727 } 1728 _, err = fgaClient.ListObjects(context.Background()). 1729 Body(requestBody). 1730 Options(badOptions). 1731 Execute() 1732 if err == nil { 1733 t.Fatalf("Expect error with invalid auth model id but there is none") 1734 } 1735 }) 1736 1737 t.Run("ListRelations", func(t *testing.T) { 1738 test := TestDefinition{ 1739 Name: "ListRelations", 1740 JsonResponse: `{"allowed":true, "resolution":""}`, 1741 ResponseStatus: http.StatusOK, 1742 Method: http.MethodPost, 1743 RequestPath: "check", 1744 } 1745 1746 requestBody := ClientListRelationsRequest{ 1747 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1748 Object: "document:roadmap", 1749 Relations: []string{"can_view", "can_edit", "can_delete", "can_rename"}, 1750 ContextualTuples: []ClientContextualTupleKey{{ 1751 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1752 Relation: "editor", 1753 Object: "document:roadmap", 1754 }}, 1755 } 1756 const authModelId = "01GAHCE4YVKPQEKZQHT2R89MQV" 1757 options := ClientListRelationsOptions{ 1758 AuthorizationModelId: openfga.PtrString(authModelId), 1759 } 1760 1761 var expectedResponse openfga.CheckResponse 1762 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 1763 t.Fatalf("%v", err) 1764 } 1765 1766 httpmock.Activate() 1767 defer httpmock.DeactivateAndReset() 1768 httpmock.RegisterMatcherResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 1769 httpmock.BodyContainsString(`"relation":"can_delete"`), 1770 func(req *http.Request) (*http.Response, error) { 1771 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, openfga.CheckResponse{Allowed: openfga.PtrBool(false)}) 1772 if err != nil { 1773 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 1774 } 1775 return resp, nil 1776 }, 1777 ) 1778 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath), 1779 func(req *http.Request) (*http.Response, error) { 1780 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 1781 if err != nil { 1782 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 1783 } 1784 return resp, nil 1785 }, 1786 ) 1787 httpmock.RegisterResponder("GET", fmt.Sprintf("%s/stores/%s/authorization-models", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId), 1788 func(req *http.Request) (*http.Response, error) { 1789 return httpmock.NewStringResponse(http.StatusOK, ""), nil 1790 }, 1791 ) 1792 httpmock.RegisterResponder("GET", fmt.Sprintf("%s/stores/%s/authorization-models/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, authModelId), 1793 func(req *http.Request) (*http.Response, error) { 1794 return httpmock.NewStringResponse(http.StatusOK, ""), nil 1795 }, 1796 ) 1797 1798 got, err := fgaClient.ListRelations(context.Background()). 1799 Body(requestBody). 1800 Options(options). 1801 Execute() 1802 if err != nil { 1803 t.Fatalf("%v", err) 1804 } 1805 1806 if httpmock.GetTotalCallCount() != 5 { 1807 t.Fatalf("OpenFgaClient.%v() - wanted %v calls to /check + 1 call to validate auth model, got %v", test.Name, 4, httpmock.GetTotalCallCount()) 1808 } 1809 1810 _, err = got.MarshalJSON() 1811 if err != nil { 1812 t.Fatalf("%v", err) 1813 } 1814 1815 if len(got.Relations) != 3 { 1816 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, len(got.Relations), 3) 1817 } 1818 // ListRelations without options should work 1819 _, err = fgaClient.ListRelations(context.Background()).Body(requestBody).Execute() 1820 if err != nil { 1821 t.Fatalf("%v", err) 1822 } 1823 // Invalid auth model ID should result in error 1824 badOptions := ClientListRelationsOptions{ 1825 AuthorizationModelId: openfga.PtrString("INVALID"), 1826 } 1827 _, err = fgaClient.ListRelations(context.Background()). 1828 Body(requestBody). 1829 Options(badOptions). 1830 Execute() 1831 if err == nil { 1832 t.Fatalf("Expect error with invalid auth model id but there is none") 1833 } 1834 1835 }) 1836 1837 t.Run("ListRelationsNoRelationsProvided", func(t *testing.T) { 1838 test := TestDefinition{ 1839 Name: "ListRelations", 1840 JsonResponse: ``, 1841 ResponseStatus: http.StatusOK, 1842 Method: http.MethodPost, 1843 RequestPath: "check", 1844 } 1845 1846 requestBody := ClientListRelationsRequest{ 1847 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1848 Object: "document:roadmap", 1849 Relations: []string{}, 1850 ContextualTuples: []ClientContextualTupleKey{{ 1851 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1852 Relation: "editor", 1853 Object: "document:roadmap", 1854 }}, 1855 } 1856 options := ClientListRelationsOptions{ 1857 AuthorizationModelId: openfga.PtrString("01GAHCE4YVKPQEKZQHT2R89MQV"), 1858 } 1859 1860 httpmock.Activate() 1861 defer httpmock.DeactivateAndReset() 1862 _, err := fgaClient.ListRelations(context.Background()). 1863 Body(requestBody). 1864 Options(options). 1865 Execute() 1866 1867 if err == nil { 1868 t.Fatalf("OpenFgaClient.%v() - expected an error but received none", test.Name) 1869 } 1870 }) 1871 1872 /* Assertions */ 1873 t.Run("ReadAssertions", func(t *testing.T) { 1874 modelId := "01GAHCE4YVKPQEKZQHT2R89MQV" 1875 test := TestDefinition{ 1876 Name: "ReadAssertions", 1877 JsonResponse: fmt.Sprintf(`{"assertions":[{"tuple_key":{"user":"user:anna","relation":"can_view","object":"document:roadmap"},"expectation":true}],"authorization_model_id":"%s"}`, modelId), 1878 ResponseStatus: http.StatusOK, 1879 Method: http.MethodGet, 1880 RequestPath: "assertions", 1881 } 1882 1883 options := ClientReadAssertionsOptions{ 1884 AuthorizationModelId: openfga.PtrString(modelId), 1885 } 1886 1887 var expectedResponse openfga.ReadAssertionsResponse 1888 if err := json.Unmarshal([]byte(test.JsonResponse), &expectedResponse); err != nil { 1889 t.Fatalf("%v", err) 1890 } 1891 1892 httpmock.Activate() 1893 defer httpmock.DeactivateAndReset() 1894 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath, modelId), 1895 func(req *http.Request) (*http.Response, error) { 1896 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, expectedResponse) 1897 if err != nil { 1898 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 1899 } 1900 return resp, nil 1901 }, 1902 ) 1903 got, err := fgaClient.ReadAssertions(context.Background()). 1904 Options(options). 1905 Execute() 1906 if err != nil { 1907 t.Fatalf("%v", err) 1908 } 1909 1910 responseJson, err := got.MarshalJSON() 1911 if err != nil { 1912 t.Fatalf("%v", err) 1913 } 1914 1915 if len(*got.Assertions) != len(*expectedResponse.Assertions) || (*got.Assertions)[0].Expectation != (*expectedResponse.Assertions)[0].Expectation { 1916 t.Fatalf("OpenFgaClient.%v() = %v, want %v", test.Name, string(responseJson), test.JsonResponse) 1917 } 1918 // ReadAssertions without options should work 1919 _, err = fgaClient.ReadAssertions(context.Background()).Execute() 1920 expectedError := "Required parameter AuthorizationModelId was not provided" 1921 if err == nil || err.Error() != expectedError { 1922 t.Fatalf("Expected error:%v, got: %v", expectedError, err) 1923 } 1924 1925 // Invalid auth model id should result in error 1926 badOptions := ClientReadAssertionsOptions{ 1927 AuthorizationModelId: openfga.PtrString("INVALID"), 1928 } 1929 _, err = fgaClient.ReadAssertions(context.Background()). 1930 Options(badOptions). 1931 Execute() 1932 if err == nil { 1933 t.Fatalf("Invalid auth model ID should result in error") 1934 } 1935 }) 1936 1937 t.Run("WriteAssertions", func(t *testing.T) { 1938 modelId := "01GAHCE4YVKPQEKZQHT2R89MQV" 1939 test := TestDefinition{ 1940 Name: "WriteAssertions", 1941 JsonResponse: "", 1942 ResponseStatus: http.StatusNoContent, 1943 Method: http.MethodPut, 1944 RequestPath: "assertions", 1945 } 1946 1947 requestBody := ClientWriteAssertionsRequest{ 1948 { 1949 User: "user:81684243-9356-4421-8fbf-a4f8d36aa31b", 1950 Relation: "can_view", 1951 Object: "document:roadmap", 1952 Expectation: true, 1953 }, 1954 } 1955 options := ClientWriteAssertionsOptions{ 1956 AuthorizationModelId: openfga.PtrString(modelId), 1957 } 1958 1959 httpmock.Activate() 1960 defer httpmock.DeactivateAndReset() 1961 httpmock.RegisterResponder(test.Method, fmt.Sprintf("%s/stores/%s/%s/%s", fgaClient.GetConfig().ApiUrl, fgaClient.GetConfig().StoreId, test.RequestPath, modelId), 1962 func(req *http.Request) (*http.Response, error) { 1963 resp, err := httpmock.NewJsonResponse(test.ResponseStatus, "") 1964 if err != nil { 1965 return httpmock.NewStringResponse(http.StatusInternalServerError, ""), nil 1966 } 1967 return resp, nil 1968 }, 1969 ) 1970 _, err := fgaClient.WriteAssertions(context.Background()). 1971 Body(requestBody). 1972 Options(options). 1973 Execute() 1974 if err != nil { 1975 t.Fatalf("%v", err) 1976 } 1977 // WriteAssertions without options should work 1978 _, err = fgaClient.WriteAssertions(context.Background()).Body(requestBody).Execute() 1979 expectedError := "Required parameter AuthorizationModelId was not provided" 1980 if err == nil || err.Error() != expectedError { 1981 t.Fatalf("Expected error:%v, got: %v", expectedError, err) 1982 } 1983 badOptions := ClientWriteAssertionsOptions{ 1984 AuthorizationModelId: openfga.PtrString("INVALID"), 1985 } 1986 _, err = fgaClient.WriteAssertions(context.Background()). 1987 Body(requestBody). 1988 Options(badOptions). 1989 Execute() 1990 if err == nil { 1991 t.Fatalf("Invalid auth model id should result in error but there is none") 1992 } 1993 }) 1994 }