github.com/hashicorp/vault/sdk@v0.13.0/database/dbplugin/v5/grpc_server_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package dbplugin 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "reflect" 11 "testing" 12 "time" 13 14 "github.com/golang/protobuf/ptypes" 15 "github.com/golang/protobuf/ptypes/timestamp" 16 "github.com/hashicorp/vault/sdk/database/dbplugin/v5/proto" 17 "github.com/hashicorp/vault/sdk/helper/pluginutil" 18 "github.com/hashicorp/vault/sdk/logical" 19 "google.golang.org/grpc/codes" 20 "google.golang.org/grpc/metadata" 21 "google.golang.org/grpc/status" 22 "google.golang.org/protobuf/types/known/structpb" 23 ) 24 25 // Before minValidSeconds in ptypes package 26 var invalidExpiration = time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC) 27 28 func TestGRPCServer_Initialize(t *testing.T) { 29 type testCase struct { 30 db Database 31 req *proto.InitializeRequest 32 expectedResp *proto.InitializeResponse 33 expectErr bool 34 expectCode codes.Code 35 grpcSetupFunc func(*testing.T, Database) (context.Context, gRPCServer) 36 } 37 38 tests := map[string]testCase{ 39 "database errored": { 40 db: fakeDatabase{ 41 initErr: errors.New("initialization error"), 42 }, 43 req: &proto.InitializeRequest{}, 44 expectedResp: &proto.InitializeResponse{}, 45 expectErr: true, 46 expectCode: codes.Internal, 47 grpcSetupFunc: testGrpcServer, 48 }, 49 "newConfig can't marshal to JSON": { 50 db: fakeDatabase{ 51 initResp: InitializeResponse{ 52 Config: map[string]interface{}{ 53 "bad-data": badJSONValue{}, 54 }, 55 }, 56 }, 57 req: &proto.InitializeRequest{}, 58 expectedResp: &proto.InitializeResponse{}, 59 expectErr: true, 60 expectCode: codes.Internal, 61 grpcSetupFunc: testGrpcServer, 62 }, 63 "happy path with config data for multiplexed plugin": { 64 db: fakeDatabase{ 65 initResp: InitializeResponse{ 66 Config: map[string]interface{}{ 67 "foo": "bar", 68 }, 69 }, 70 }, 71 req: &proto.InitializeRequest{ 72 ConfigData: marshal(t, map[string]interface{}{ 73 "foo": "bar", 74 }), 75 }, 76 expectedResp: &proto.InitializeResponse{ 77 ConfigData: marshal(t, map[string]interface{}{ 78 "foo": "bar", 79 }), 80 }, 81 expectErr: false, 82 expectCode: codes.OK, 83 grpcSetupFunc: testGrpcServer, 84 }, 85 "happy path with config data for non-multiplexed plugin": { 86 db: fakeDatabase{ 87 initResp: InitializeResponse{ 88 Config: map[string]interface{}{ 89 "foo": "bar", 90 }, 91 }, 92 }, 93 req: &proto.InitializeRequest{ 94 ConfigData: marshal(t, map[string]interface{}{ 95 "foo": "bar", 96 }), 97 }, 98 expectedResp: &proto.InitializeResponse{ 99 ConfigData: marshal(t, map[string]interface{}{ 100 "foo": "bar", 101 }), 102 }, 103 expectErr: false, 104 expectCode: codes.OK, 105 grpcSetupFunc: testGrpcServerSingleImpl, 106 }, 107 } 108 109 for name, test := range tests { 110 t.Run(name, func(t *testing.T) { 111 idCtx, g := test.grpcSetupFunc(t, test.db) 112 resp, err := g.Initialize(idCtx, test.req) 113 114 if test.expectErr && err == nil { 115 t.Fatalf("err expected, got nil") 116 } 117 if !test.expectErr && err != nil { 118 t.Fatalf("no error expected, got: %s", err) 119 } 120 121 actualCode := status.Code(err) 122 if actualCode != test.expectCode { 123 t.Fatalf("Actual code: %s Expected code: %s", actualCode, test.expectCode) 124 } 125 126 if !reflect.DeepEqual(resp, test.expectedResp) { 127 t.Fatalf("Actual response: %#v\nExpected response: %#v", resp, test.expectedResp) 128 } 129 }) 130 } 131 } 132 133 func TestCoerceFloatsToInt(t *testing.T) { 134 type testCase struct { 135 input map[string]interface{} 136 expected map[string]interface{} 137 } 138 139 tests := map[string]testCase{ 140 "no numbers": { 141 input: map[string]interface{}{ 142 "foo": "bar", 143 }, 144 expected: map[string]interface{}{ 145 "foo": "bar", 146 }, 147 }, 148 "raw integers": { 149 input: map[string]interface{}{ 150 "foo": 42, 151 }, 152 expected: map[string]interface{}{ 153 "foo": 42, 154 }, 155 }, 156 "floats ": { 157 input: map[string]interface{}{ 158 "foo": 42.2, 159 }, 160 expected: map[string]interface{}{ 161 "foo": 42.2, 162 }, 163 }, 164 "floats coerced to ints": { 165 input: map[string]interface{}{ 166 "foo": float64(42), 167 }, 168 expected: map[string]interface{}{ 169 "foo": int64(42), 170 }, 171 }, 172 } 173 174 for name, test := range tests { 175 t.Run(name, func(t *testing.T) { 176 actual := copyMap(test.input) 177 coerceFloatsToInt(actual) 178 if !reflect.DeepEqual(actual, test.expected) { 179 t.Fatalf("Actual: %#v\nExpected: %#v", actual, test.expected) 180 } 181 }) 182 } 183 } 184 185 func copyMap(m map[string]interface{}) map[string]interface{} { 186 newMap := map[string]interface{}{} 187 for k, v := range m { 188 newMap[k] = v 189 } 190 return newMap 191 } 192 193 func TestGRPCServer_NewUser(t *testing.T) { 194 type testCase struct { 195 db Database 196 req *proto.NewUserRequest 197 expectedResp *proto.NewUserResponse 198 expectErr bool 199 expectCode codes.Code 200 } 201 202 tests := map[string]testCase{ 203 "missing username config": { 204 db: fakeDatabase{}, 205 req: &proto.NewUserRequest{}, 206 expectedResp: &proto.NewUserResponse{}, 207 expectErr: true, 208 expectCode: codes.InvalidArgument, 209 }, 210 "bad expiration": { 211 db: fakeDatabase{}, 212 req: &proto.NewUserRequest{ 213 UsernameConfig: &proto.UsernameConfig{ 214 DisplayName: "dispname", 215 RoleName: "rolename", 216 }, 217 Expiration: ×tamp.Timestamp{ 218 Seconds: invalidExpiration.Unix(), 219 }, 220 }, 221 expectedResp: &proto.NewUserResponse{}, 222 expectErr: true, 223 expectCode: codes.InvalidArgument, 224 }, 225 "database error": { 226 db: fakeDatabase{ 227 newUserErr: errors.New("new user error"), 228 }, 229 req: &proto.NewUserRequest{ 230 UsernameConfig: &proto.UsernameConfig{ 231 DisplayName: "dispname", 232 RoleName: "rolename", 233 }, 234 Expiration: ptypes.TimestampNow(), 235 }, 236 expectedResp: &proto.NewUserResponse{}, 237 expectErr: true, 238 expectCode: codes.Internal, 239 }, 240 "happy path with expiration": { 241 db: fakeDatabase{ 242 newUserResp: NewUserResponse{ 243 Username: "someuser_foo", 244 }, 245 }, 246 req: &proto.NewUserRequest{ 247 UsernameConfig: &proto.UsernameConfig{ 248 DisplayName: "dispname", 249 RoleName: "rolename", 250 }, 251 Expiration: ptypes.TimestampNow(), 252 }, 253 expectedResp: &proto.NewUserResponse{ 254 Username: "someuser_foo", 255 }, 256 expectErr: false, 257 expectCode: codes.OK, 258 }, 259 "happy path without expiration": { 260 db: fakeDatabase{ 261 newUserResp: NewUserResponse{ 262 Username: "someuser_foo", 263 }, 264 }, 265 req: &proto.NewUserRequest{ 266 UsernameConfig: &proto.UsernameConfig{ 267 DisplayName: "dispname", 268 RoleName: "rolename", 269 }, 270 }, 271 expectedResp: &proto.NewUserResponse{ 272 Username: "someuser_foo", 273 }, 274 expectErr: false, 275 expectCode: codes.OK, 276 }, 277 } 278 279 for name, test := range tests { 280 t.Run(name, func(t *testing.T) { 281 idCtx, g := testGrpcServer(t, test.db) 282 resp, err := g.NewUser(idCtx, test.req) 283 284 if test.expectErr && err == nil { 285 t.Fatalf("err expected, got nil") 286 } 287 if !test.expectErr && err != nil { 288 t.Fatalf("no error expected, got: %s", err) 289 } 290 291 actualCode := status.Code(err) 292 if actualCode != test.expectCode { 293 t.Fatalf("Actual code: %s Expected code: %s", actualCode, test.expectCode) 294 } 295 296 if !reflect.DeepEqual(resp, test.expectedResp) { 297 t.Fatalf("Actual response: %#v\nExpected response: %#v", resp, test.expectedResp) 298 } 299 }) 300 } 301 } 302 303 func TestGRPCServer_UpdateUser(t *testing.T) { 304 type testCase struct { 305 db Database 306 req *proto.UpdateUserRequest 307 expectedResp *proto.UpdateUserResponse 308 expectErr bool 309 expectCode codes.Code 310 } 311 312 tests := map[string]testCase{ 313 "missing username": { 314 db: fakeDatabase{}, 315 req: &proto.UpdateUserRequest{}, 316 expectedResp: &proto.UpdateUserResponse{}, 317 expectErr: true, 318 expectCode: codes.InvalidArgument, 319 }, 320 "missing changes": { 321 db: fakeDatabase{}, 322 req: &proto.UpdateUserRequest{ 323 Username: "someuser", 324 }, 325 expectedResp: &proto.UpdateUserResponse{}, 326 expectErr: true, 327 expectCode: codes.InvalidArgument, 328 }, 329 "database error": { 330 db: fakeDatabase{ 331 updateUserErr: errors.New("update user error"), 332 }, 333 req: &proto.UpdateUserRequest{ 334 Username: "someuser", 335 Password: &proto.ChangePassword{ 336 NewPassword: "90ughaino", 337 }, 338 }, 339 expectedResp: &proto.UpdateUserResponse{}, 340 expectErr: true, 341 expectCode: codes.Internal, 342 }, 343 "bad expiration date": { 344 db: fakeDatabase{}, 345 req: &proto.UpdateUserRequest{ 346 Username: "someuser", 347 Expiration: &proto.ChangeExpiration{ 348 NewExpiration: ×tamp.Timestamp{ 349 // Before minValidSeconds in ptypes package 350 Seconds: invalidExpiration.Unix(), 351 }, 352 }, 353 }, 354 expectedResp: &proto.UpdateUserResponse{}, 355 expectErr: true, 356 expectCode: codes.InvalidArgument, 357 }, 358 "change password happy path": { 359 db: fakeDatabase{}, 360 req: &proto.UpdateUserRequest{ 361 Username: "someuser", 362 Password: &proto.ChangePassword{ 363 NewPassword: "90ughaino", 364 }, 365 }, 366 expectedResp: &proto.UpdateUserResponse{}, 367 expectErr: false, 368 expectCode: codes.OK, 369 }, 370 "change expiration happy path": { 371 db: fakeDatabase{}, 372 req: &proto.UpdateUserRequest{ 373 Username: "someuser", 374 Expiration: &proto.ChangeExpiration{ 375 NewExpiration: ptypes.TimestampNow(), 376 }, 377 }, 378 expectedResp: &proto.UpdateUserResponse{}, 379 expectErr: false, 380 expectCode: codes.OK, 381 }, 382 } 383 384 for name, test := range tests { 385 t.Run(name, func(t *testing.T) { 386 idCtx, g := testGrpcServer(t, test.db) 387 resp, err := g.UpdateUser(idCtx, test.req) 388 389 if test.expectErr && err == nil { 390 t.Fatalf("err expected, got nil") 391 } 392 if !test.expectErr && err != nil { 393 t.Fatalf("no error expected, got: %s", err) 394 } 395 396 actualCode := status.Code(err) 397 if actualCode != test.expectCode { 398 t.Fatalf("Actual code: %s Expected code: %s", actualCode, test.expectCode) 399 } 400 401 if !reflect.DeepEqual(resp, test.expectedResp) { 402 t.Fatalf("Actual response: %#v\nExpected response: %#v", resp, test.expectedResp) 403 } 404 }) 405 } 406 } 407 408 func TestGRPCServer_DeleteUser(t *testing.T) { 409 type testCase struct { 410 db Database 411 req *proto.DeleteUserRequest 412 expectedResp *proto.DeleteUserResponse 413 expectErr bool 414 expectCode codes.Code 415 } 416 417 tests := map[string]testCase{ 418 "missing username": { 419 db: fakeDatabase{}, 420 req: &proto.DeleteUserRequest{}, 421 expectedResp: &proto.DeleteUserResponse{}, 422 expectErr: true, 423 expectCode: codes.InvalidArgument, 424 }, 425 "database error": { 426 db: fakeDatabase{ 427 deleteUserErr: errors.New("delete user error"), 428 }, 429 req: &proto.DeleteUserRequest{ 430 Username: "someuser", 431 }, 432 expectedResp: &proto.DeleteUserResponse{}, 433 expectErr: true, 434 expectCode: codes.Internal, 435 }, 436 "happy path": { 437 db: fakeDatabase{}, 438 req: &proto.DeleteUserRequest{ 439 Username: "someuser", 440 }, 441 expectedResp: &proto.DeleteUserResponse{}, 442 expectErr: false, 443 expectCode: codes.OK, 444 }, 445 } 446 447 for name, test := range tests { 448 t.Run(name, func(t *testing.T) { 449 idCtx, g := testGrpcServer(t, test.db) 450 resp, err := g.DeleteUser(idCtx, test.req) 451 452 if test.expectErr && err == nil { 453 t.Fatalf("err expected, got nil") 454 } 455 if !test.expectErr && err != nil { 456 t.Fatalf("no error expected, got: %s", err) 457 } 458 459 actualCode := status.Code(err) 460 if actualCode != test.expectCode { 461 t.Fatalf("Actual code: %s Expected code: %s", actualCode, test.expectCode) 462 } 463 464 if !reflect.DeepEqual(resp, test.expectedResp) { 465 t.Fatalf("Actual response: %#v\nExpected response: %#v", resp, test.expectedResp) 466 } 467 }) 468 } 469 } 470 471 func TestGRPCServer_Type(t *testing.T) { 472 type testCase struct { 473 db Database 474 expectedResp *proto.TypeResponse 475 expectErr bool 476 expectCode codes.Code 477 } 478 479 tests := map[string]testCase{ 480 "database error": { 481 db: fakeDatabase{ 482 typeErr: errors.New("type error"), 483 }, 484 expectedResp: &proto.TypeResponse{}, 485 expectErr: true, 486 expectCode: codes.Internal, 487 }, 488 "happy path": { 489 db: fakeDatabase{ 490 typeResp: "fake database", 491 }, 492 expectedResp: &proto.TypeResponse{ 493 Type: "fake database", 494 }, 495 expectErr: false, 496 expectCode: codes.OK, 497 }, 498 } 499 500 for name, test := range tests { 501 t.Run(name, func(t *testing.T) { 502 idCtx, g := testGrpcServer(t, test.db) 503 resp, err := g.Type(idCtx, &proto.Empty{}) 504 505 if test.expectErr && err == nil { 506 t.Fatalf("err expected, got nil") 507 } 508 if !test.expectErr && err != nil { 509 t.Fatalf("no error expected, got: %s", err) 510 } 511 512 actualCode := status.Code(err) 513 if actualCode != test.expectCode { 514 t.Fatalf("Actual code: %s Expected code: %s", actualCode, test.expectCode) 515 } 516 517 if !reflect.DeepEqual(resp, test.expectedResp) { 518 t.Fatalf("Actual response: %#v\nExpected response: %#v", resp, test.expectedResp) 519 } 520 }) 521 } 522 } 523 524 func TestGRPCServer_Close(t *testing.T) { 525 type testCase struct { 526 db Database 527 expectErr bool 528 expectCode codes.Code 529 grpcSetupFunc func(*testing.T, Database) (context.Context, gRPCServer) 530 assertFunc func(t *testing.T, g gRPCServer) 531 } 532 533 tests := map[string]testCase{ 534 "database error": { 535 db: fakeDatabase{ 536 closeErr: errors.New("close error"), 537 }, 538 expectErr: true, 539 expectCode: codes.Internal, 540 grpcSetupFunc: testGrpcServer, 541 assertFunc: nil, 542 }, 543 "happy path for multiplexed plugin": { 544 db: fakeDatabase{}, 545 expectErr: false, 546 expectCode: codes.OK, 547 grpcSetupFunc: testGrpcServer, 548 assertFunc: func(t *testing.T, g gRPCServer) { 549 if len(g.instances) != 0 { 550 t.Fatalf("err expected instances map to be empty") 551 } 552 }, 553 }, 554 "happy path for non-multiplexed plugin": { 555 db: fakeDatabase{}, 556 expectErr: false, 557 expectCode: codes.OK, 558 grpcSetupFunc: testGrpcServerSingleImpl, 559 assertFunc: nil, 560 }, 561 } 562 563 for name, test := range tests { 564 t.Run(name, func(t *testing.T) { 565 idCtx, g := test.grpcSetupFunc(t, test.db) 566 _, err := g.Close(idCtx, &proto.Empty{}) 567 568 if test.expectErr && err == nil { 569 t.Fatalf("err expected, got nil") 570 } 571 if !test.expectErr && err != nil { 572 t.Fatalf("no error expected, got: %s", err) 573 } 574 575 actualCode := status.Code(err) 576 if actualCode != test.expectCode { 577 t.Fatalf("Actual code: %s Expected code: %s", actualCode, test.expectCode) 578 } 579 580 if test.assertFunc != nil { 581 test.assertFunc(t, g) 582 } 583 }) 584 } 585 } 586 587 func TestGRPCServer_Version(t *testing.T) { 588 type testCase struct { 589 db Database 590 expectedResp string 591 expectErr bool 592 expectCode codes.Code 593 } 594 595 tests := map[string]testCase{ 596 "backend that does not implement version": { 597 db: fakeDatabase{}, 598 expectedResp: "", 599 expectErr: false, 600 expectCode: codes.OK, 601 }, 602 "backend with version": { 603 db: fakeDatabaseWithVersion{ 604 version: "v123", 605 }, 606 expectedResp: "v123", 607 expectErr: false, 608 expectCode: codes.OK, 609 }, 610 } 611 612 for name, test := range tests { 613 t.Run(name, func(t *testing.T) { 614 idCtx, g := testGrpcServer(t, test.db) 615 resp, err := g.Version(idCtx, &logical.Empty{}) 616 617 if test.expectErr && err == nil { 618 t.Fatalf("err expected, got nil") 619 } 620 if !test.expectErr && err != nil { 621 t.Fatalf("no error expected, got: %s", err) 622 } 623 624 actualCode := status.Code(err) 625 if actualCode != test.expectCode { 626 t.Fatalf("Actual code: %s Expected code: %s", actualCode, test.expectCode) 627 } 628 629 if !reflect.DeepEqual(resp.PluginVersion, test.expectedResp) { 630 t.Fatalf("Actual response: %#v\nExpected response: %#v", resp, test.expectedResp) 631 } 632 }) 633 } 634 } 635 636 // testGrpcServer is a test helper that returns a context with an ID set in its 637 // metadata and a gRPCServer instance for a multiplexed plugin 638 func testGrpcServer(t *testing.T, db Database) (context.Context, gRPCServer) { 639 t.Helper() 640 g := gRPCServer{ 641 factoryFunc: func() (interface{}, error) { 642 return db, nil 643 }, 644 instances: make(map[string]Database), 645 } 646 647 id := "12345" 648 idCtx := idCtx(t, id) 649 g.instances[id] = db 650 651 return idCtx, g 652 } 653 654 // testGrpcServerSingleImpl is a test helper that returns a context and a 655 // gRPCServer instance for a non-multiplexed plugin 656 func testGrpcServerSingleImpl(t *testing.T, db Database) (context.Context, gRPCServer) { 657 t.Helper() 658 return context.Background(), gRPCServer{ 659 singleImpl: db, 660 } 661 } 662 663 // idCtx is a test helper that will return a context with the IDs set in its 664 // metadata 665 func idCtx(t *testing.T, ids ...string) context.Context { 666 t.Helper() 667 // Context doesn't need to timeout since this is just passed through 668 ctx := context.Background() 669 md := metadata.MD{} 670 for _, id := range ids { 671 md.Append(pluginutil.MultiplexingCtxKey, id) 672 } 673 return metadata.NewIncomingContext(ctx, md) 674 } 675 676 func marshal(t *testing.T, m map[string]interface{}) *structpb.Struct { 677 t.Helper() 678 679 strct, err := mapToStruct(m) 680 if err != nil { 681 t.Fatalf("unable to marshal to protobuf: %s", err) 682 } 683 return strct 684 } 685 686 type badJSONValue struct{} 687 688 func (badJSONValue) MarshalJSON() ([]byte, error) { 689 return nil, fmt.Errorf("this cannot be marshalled to JSON") 690 } 691 692 func (badJSONValue) UnmarshalJSON([]byte) error { 693 return fmt.Errorf("this cannot be unmarshalled from JSON") 694 } 695 696 var _ Database = fakeDatabase{} 697 698 type fakeDatabase struct { 699 initResp InitializeResponse 700 initErr error 701 702 newUserResp NewUserResponse 703 newUserErr error 704 705 updateUserResp UpdateUserResponse 706 updateUserErr error 707 708 deleteUserResp DeleteUserResponse 709 deleteUserErr error 710 711 typeResp string 712 typeErr error 713 714 closeErr error 715 } 716 717 func (e fakeDatabase) Initialize(ctx context.Context, req InitializeRequest) (InitializeResponse, error) { 718 return e.initResp, e.initErr 719 } 720 721 func (e fakeDatabase) NewUser(ctx context.Context, req NewUserRequest) (NewUserResponse, error) { 722 return e.newUserResp, e.newUserErr 723 } 724 725 func (e fakeDatabase) UpdateUser(ctx context.Context, req UpdateUserRequest) (UpdateUserResponse, error) { 726 return e.updateUserResp, e.updateUserErr 727 } 728 729 func (e fakeDatabase) DeleteUser(ctx context.Context, req DeleteUserRequest) (DeleteUserResponse, error) { 730 return e.deleteUserResp, e.deleteUserErr 731 } 732 733 func (e fakeDatabase) Type() (string, error) { 734 return e.typeResp, e.typeErr 735 } 736 737 func (e fakeDatabase) Close() error { 738 return e.closeErr 739 } 740 741 var _ Database = &recordingDatabase{} 742 743 type recordingDatabase struct { 744 initializeCalls int 745 newUserCalls int 746 updateUserCalls int 747 deleteUserCalls int 748 typeCalls int 749 closeCalls int 750 751 // recordingDatabase can act as middleware so we can record the calls to other test Database implementations 752 next Database 753 } 754 755 func (f *recordingDatabase) Initialize(ctx context.Context, req InitializeRequest) (InitializeResponse, error) { 756 f.initializeCalls++ 757 if f.next == nil { 758 return InitializeResponse{}, nil 759 } 760 return f.next.Initialize(ctx, req) 761 } 762 763 func (f *recordingDatabase) NewUser(ctx context.Context, req NewUserRequest) (NewUserResponse, error) { 764 f.newUserCalls++ 765 if f.next == nil { 766 return NewUserResponse{}, nil 767 } 768 return f.next.NewUser(ctx, req) 769 } 770 771 func (f *recordingDatabase) UpdateUser(ctx context.Context, req UpdateUserRequest) (UpdateUserResponse, error) { 772 f.updateUserCalls++ 773 if f.next == nil { 774 return UpdateUserResponse{}, nil 775 } 776 return f.next.UpdateUser(ctx, req) 777 } 778 779 func (f *recordingDatabase) DeleteUser(ctx context.Context, req DeleteUserRequest) (DeleteUserResponse, error) { 780 f.deleteUserCalls++ 781 if f.next == nil { 782 return DeleteUserResponse{}, nil 783 } 784 return f.next.DeleteUser(ctx, req) 785 } 786 787 func (f *recordingDatabase) Type() (string, error) { 788 f.typeCalls++ 789 if f.next == nil { 790 return "recordingDatabase", nil 791 } 792 return f.next.Type() 793 } 794 795 func (f *recordingDatabase) Close() error { 796 f.closeCalls++ 797 if f.next == nil { 798 return nil 799 } 800 return f.next.Close() 801 } 802 803 type fakeDatabaseWithVersion struct { 804 version string 805 } 806 807 func (e fakeDatabaseWithVersion) PluginVersion() logical.PluginVersion { 808 return logical.PluginVersion{Version: e.version} 809 } 810 811 func (e fakeDatabaseWithVersion) Initialize(_ context.Context, _ InitializeRequest) (InitializeResponse, error) { 812 return InitializeResponse{}, nil 813 } 814 815 func (e fakeDatabaseWithVersion) NewUser(_ context.Context, _ NewUserRequest) (NewUserResponse, error) { 816 return NewUserResponse{}, nil 817 } 818 819 func (e fakeDatabaseWithVersion) UpdateUser(_ context.Context, _ UpdateUserRequest) (UpdateUserResponse, error) { 820 return UpdateUserResponse{}, nil 821 } 822 823 func (e fakeDatabaseWithVersion) DeleteUser(_ context.Context, _ DeleteUserRequest) (DeleteUserResponse, error) { 824 return DeleteUserResponse{}, nil 825 } 826 827 func (e fakeDatabaseWithVersion) Type() (string, error) { 828 return "", nil 829 } 830 831 func (e fakeDatabaseWithVersion) Close() error { 832 return nil 833 } 834 835 var ( 836 _ Database = (*fakeDatabaseWithVersion)(nil) 837 _ logical.PluginVersioner = (*fakeDatabaseWithVersion)(nil) 838 )