From a54f93cac72a6ad83ec420f67b0e024be496be11 Mon Sep 17 00:00:00 2001 From: Nick Z <2420177+nickzelei@users.noreply.github.com> Date: Fri, 13 Dec 2024 13:32:50 -0800 Subject: [PATCH] Gets api key tests working again --- .../mgmt/v1alpha1/api-key-service/api-keys.go | 24 +- .../v1alpha1/api-key-service/api-keys_test.go | 880 +++++++++--------- 2 files changed, 449 insertions(+), 455 deletions(-) diff --git a/backend/services/mgmt/v1alpha1/api-key-service/api-keys.go b/backend/services/mgmt/v1alpha1/api-key-service/api-keys.go index 9ed4302a3e..03611c3a01 100644 --- a/backend/services/mgmt/v1alpha1/api-key-service/api-keys.go +++ b/backend/services/mgmt/v1alpha1/api-key-service/api-keys.go @@ -58,11 +58,6 @@ func (s *Service) GetAccountApiKey( return nil, err } - user, err := s.userdataclient.GetUser(ctx) - if err != nil { - return nil, err - } - apiKey, err := s.db.Q.GetAccountApiKeyById(ctx, s.db.Db, apiKeyUuid) if err != nil && !neosyncdb.IsNoRows(err) { return nil, err @@ -70,6 +65,10 @@ func (s *Service) GetAccountApiKey( return nil, nucleuserrors.NewNotFound("unable to find api key") } + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } if err := user.EnforceAccount(ctx, userdata.NewIdentifier(neosyncdb.UUIDString(apiKey.AccountID)), rbac.AccountAction_View); err != nil { return nil, err } @@ -186,14 +185,6 @@ func (s *Service) DeleteAccountApiKey( return nil, err } - user, err := s.userdataclient.GetUser(ctx) - if err != nil { - return nil, err - } - if user.IsApiKey() { - return nil, nucleuserrors.NewUnauthorized("api key user cannot delete api keys") - } - apiKey, err := s.db.Q.GetAccountApiKeyById(ctx, s.db.Db, apiKeyUuid) if err != nil && !neosyncdb.IsNoRows(err) { return nil, err @@ -201,6 +192,13 @@ func (s *Service) DeleteAccountApiKey( return connect.NewResponse(&mgmtv1alpha1.DeleteAccountApiKeyResponse{}), nil } + user, err := s.userdataclient.GetUser(ctx) + if err != nil { + return nil, err + } + if user.IsApiKey() { + return nil, nucleuserrors.NewUnauthorized("api key user cannot delete api keys") + } if err := user.EnforceAccount(ctx, userdata.NewIdentifier(neosyncdb.UUIDString(apiKey.AccountID)), rbac.AccountAction_Edit); err != nil { return nil, err } diff --git a/backend/services/mgmt/v1alpha1/api-key-service/api-keys_test.go b/backend/services/mgmt/v1alpha1/api-key-service/api-keys_test.go index 483efb9b95..a6fc4e0961 100644 --- a/backend/services/mgmt/v1alpha1/api-key-service/api-keys_test.go +++ b/backend/services/mgmt/v1alpha1/api-key-service/api-keys_test.go @@ -1,444 +1,440 @@ package v1alpha1_apikeyservice -// import ( -// "context" -// "testing" -// "time" - -// "connectrpc.com/connect" -// "github.com/google/uuid" -// "github.com/jackc/pgx/v5" -// "github.com/jackc/pgx/v5/pgtype" -// db_queries "github.com/nucleuscloud/neosync/backend/gen/go/db" -// mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" -// "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1/mgmtv1alpha1connect" -// "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" -// pgxmock "github.com/nucleuscloud/neosync/internal/mocks/github.com/jackc/pgx/v5" -// "github.com/stretchr/testify/assert" -// "github.com/stretchr/testify/mock" -// "google.golang.org/protobuf/types/known/timestamppb" -// ) - -// func Test_Service_GetAccountApiKeys(t *testing.T) { -// mockDbtx := neosyncdb.NewMockDBTX(t) -// mockQuerier := db_queries.NewMockQuerier(t) -// mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) - -// svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) - -// rawData := []db_queries.NeosyncApiAccountApiKey{ -// { -// ID: newPgUuid(t), -// AccountID: newPgUuid(t), -// KeyValue: "foo", -// CreatedByID: newPgUuid(t), -// UpdatedByID: newPgUuid(t), -// CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, -// KeyName: "foo", -// }, -// { -// ID: newPgUuid(t), -// AccountID: newPgUuid(t), -// KeyValue: "foobar", -// CreatedByID: newPgUuid(t), -// UpdatedByID: newPgUuid(t), -// CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, -// KeyName: "foobar", -// }, -// } -// mockQuerier.On("GetAccountApiKeys", mock.Anything, mock.Anything, mock.Anything). -// Return(rawData, nil) -// mockIsUserInAccount(mockUserAccountService, true) - -// resp, err := svc.GetAccountApiKeys(context.Background(), connect.NewRequest(&mgmtv1alpha1.GetAccountApiKeysRequest{ -// AccountId: uuid.NewString(), -// })) -// assert.NoError(t, err) -// assert.NotNil(t, resp) -// assert.NotEmpty(t, resp.Msg.ApiKeys) -// assert.Len( -// t, -// resp.Msg.ApiKeys, -// len(rawData), -// ) -// for idx, apiKey := range resp.Msg.ApiKeys { -// dbApikey := rawData[idx] -// assert.Equal(t, apiKey.Id, neosyncdb.UUIDString(dbApikey.ID)) -// assert.Nil(t, apiKey.KeyValue) -// assert.Equal(t, apiKey.Name, dbApikey.KeyName) -// } -// } - -// func Test_Service_GetAccountApiKeys_ForbiddenAccount(t *testing.T) { -// mockDbtx := neosyncdb.NewMockDBTX(t) -// mockQuerier := db_queries.NewMockQuerier(t) -// mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) - -// svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) - -// mockIsUserInAccount(mockUserAccountService, false) - -// resp, err := svc.GetAccountApiKeys(context.Background(), connect.NewRequest(&mgmtv1alpha1.GetAccountApiKeysRequest{ -// AccountId: uuid.NewString(), -// })) -// assert.Error(t, err) -// assert.Nil(t, resp) -// } - -// func Test_Service_GetAccountApiKey_Found(t *testing.T) { -// mockDbtx := neosyncdb.NewMockDBTX(t) -// mockQuerier := db_queries.NewMockQuerier(t) -// mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) - -// svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) - -// rawData := db_queries.NeosyncApiAccountApiKey{ -// ID: newPgUuid(t), -// AccountID: newPgUuid(t), -// KeyValue: "foo", -// CreatedByID: newPgUuid(t), -// UpdatedByID: newPgUuid(t), -// CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, -// KeyName: "foo", -// } -// mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). -// Return(rawData, nil) -// mockIsUserInAccount(mockUserAccountService, true) - -// resp, err := svc.GetAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.GetAccountApiKeyRequest{ -// Id: uuid.NewString(), -// })) -// assert.NoError(t, err) -// assert.NotNil(t, resp) -// assert.Equal(t, resp.Msg.ApiKey.Id, neosyncdb.UUIDString(rawData.ID)) -// assert.Nil(t, resp.Msg.ApiKey.KeyValue) -// } - -// func Test_Service_GetAccountApiKey_NotFound(t *testing.T) { -// mockDbtx := neosyncdb.NewMockDBTX(t) -// mockQuerier := db_queries.NewMockQuerier(t) -// mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) - -// svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) - -// mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). -// Return(db_queries.NeosyncApiAccountApiKey{}, pgx.ErrNoRows) - -// resp, err := svc.GetAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.GetAccountApiKeyRequest{ -// Id: uuid.NewString(), -// })) -// assert.Error(t, err) -// assert.Nil(t, resp) -// } - -// func Test_Service_GetAccountApiKey_Found_ForbiddenAccount(t *testing.T) { -// mockDbtx := neosyncdb.NewMockDBTX(t) -// mockQuerier := db_queries.NewMockQuerier(t) -// mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) - -// svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) - -// rawData := db_queries.NeosyncApiAccountApiKey{ -// ID: newPgUuid(t), -// AccountID: newPgUuid(t), -// KeyValue: "foo", -// CreatedByID: newPgUuid(t), -// UpdatedByID: newPgUuid(t), -// CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, -// KeyName: "foo", -// } -// mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). -// Return(rawData, nil) -// mockIsUserInAccount(mockUserAccountService, false) - -// resp, err := svc.GetAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.GetAccountApiKeyRequest{ -// Id: uuid.NewString(), -// })) -// assert.Error(t, err) -// assert.Nil(t, resp) -// } - -// func Test_Service_CreateAccountApiKey(t *testing.T) { -// mockDbtx := neosyncdb.NewMockDBTX(t) -// mockQuerier := db_queries.NewMockQuerier(t) -// mockTx := pgxmock.NewMockTx(t) -// mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) - -// svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) - -// mockIsUserInAccount(mockUserAccountService, true) -// mockUserAccountCalls(mockUserAccountService, true, uuid.NewString()) -// mockDbtx.On("Begin", mock.Anything).Return(mockTx, nil) -// mockTx.On("Commit", mock.Anything).Return(nil) -// mockTx.On("Rollback", mock.Anything).Return(nil) -// user := db_queries.NeosyncApiUser{ -// ID: newPgUuid(t), -// UserType: 1, -// } -// mockQuerier.On("CreateMachineUser", mock.Anything, mock.Anything, mock.Anything). -// Return(user, nil) -// rawData := db_queries.NeosyncApiAccountApiKey{ -// ID: newPgUuid(t), -// AccountID: newPgUuid(t), -// KeyValue: "foo", -// CreatedByID: newPgUuid(t), -// UpdatedByID: newPgUuid(t), -// CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, -// KeyName: "foo", -// UserID: user.ID, -// } -// mockQuerier.On("CreateAccountApiKey", mock.Anything, mock.Anything, mock.Anything). -// Return(rawData, nil) - -// resp, err := svc.CreateAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.CreateAccountApiKeyRequest{ -// AccountId: uuid.NewString(), -// Name: "foo", -// ExpiresAt: timestamppb.New(time.Now().Add(24 * time.Hour)), -// })) -// assert.NoError(t, err) -// assert.NotNil(t, resp) -// assert.NotNil(t, resp.Msg.ApiKey.KeyValue) -// assert.NotEqual(t, resp.Msg.ApiKey.KeyValue, rawData.KeyValue, "KeyValue return should be the clear text, not the hash") -// } - -// func Test_Service_RegenerateAccountApiKey(t *testing.T) { -// mockDbtx := neosyncdb.NewMockDBTX(t) -// mockQuerier := db_queries.NewMockQuerier(t) -// mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) - -// svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) - -// mockIsUserInAccount(mockUserAccountService, true) -// mockUserAccountCalls(mockUserAccountService, true, uuid.NewString()) -// rawData := db_queries.NeosyncApiAccountApiKey{ -// ID: newPgUuid(t), -// AccountID: newPgUuid(t), -// KeyValue: "foo", -// CreatedByID: newPgUuid(t), -// UpdatedByID: newPgUuid(t), -// CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, -// KeyName: "foo", -// } -// mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). -// Return(rawData, nil) -// mockQuerier.On("UpdateAccountApiKeyValue", mock.Anything, mock.Anything, mock.Anything). -// Return(rawData, nil) - -// resp, err := svc.RegenerateAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.RegenerateAccountApiKeyRequest{ -// Id: uuid.NewString(), -// ExpiresAt: timestamppb.New(time.Now().Add(24 * time.Hour)), -// })) -// assert.NoError(t, err) -// assert.NotNil(t, resp) -// assert.NotNil(t, resp.Msg.ApiKey.KeyValue) -// assert.NotEqual(t, resp.Msg.ApiKey.KeyValue, rawData.KeyValue, "KeyValue return should be the clear text, not the hash") -// } - -// func Test_Service_RegenerateAccountApiKey_ForbiddenAccount(t *testing.T) { -// mockDbtx := neosyncdb.NewMockDBTX(t) -// mockQuerier := db_queries.NewMockQuerier(t) -// mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) - -// svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) - -// mockIsUserInAccount(mockUserAccountService, false) -// rawData := db_queries.NeosyncApiAccountApiKey{ -// ID: newPgUuid(t), -// AccountID: newPgUuid(t), -// KeyValue: "foo", -// CreatedByID: newPgUuid(t), -// UpdatedByID: newPgUuid(t), -// CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, -// KeyName: "foo", -// } -// mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). -// Return(rawData, nil) - -// resp, err := svc.RegenerateAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.RegenerateAccountApiKeyRequest{ -// Id: uuid.NewString(), -// ExpiresAt: timestamppb.New(time.Now().Add(24 * time.Hour)), -// })) -// assert.Error(t, err) -// assert.Nil(t, resp) -// } - -// func Test_Service_RegenerateAccountApiKey_NotFound(t *testing.T) { -// mockDbtx := neosyncdb.NewMockDBTX(t) -// mockQuerier := db_queries.NewMockQuerier(t) -// mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) - -// svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) - -// mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). -// Return(db_queries.NeosyncApiAccountApiKey{}, pgx.ErrNoRows) - -// resp, err := svc.RegenerateAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.RegenerateAccountApiKeyRequest{ -// Id: uuid.NewString(), -// ExpiresAt: timestamppb.New(time.Now().Add(24 * time.Hour)), -// })) -// assert.Error(t, err) -// assert.Nil(t, resp) -// } - -// func Test_Service_CreateAccountApiKey_ForbiddenAccount(t *testing.T) { -// mockDbtx := neosyncdb.NewMockDBTX(t) -// mockQuerier := db_queries.NewMockQuerier(t) -// mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) - -// svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) - -// mockIsUserInAccount(mockUserAccountService, false) - -// resp, err := svc.CreateAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.CreateAccountApiKeyRequest{ -// AccountId: uuid.NewString(), -// Name: "foo", -// ExpiresAt: timestamppb.New(time.Now().Add(24 * time.Hour)), -// })) -// assert.Error(t, err) -// assert.Nil(t, resp) -// } - -// func Test_Service_DeleteAccountApiKey_Existing(t *testing.T) { -// mockDbtx := neosyncdb.NewMockDBTX(t) -// mockQuerier := db_queries.NewMockQuerier(t) -// mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) - -// svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) - -// rawData := db_queries.NeosyncApiAccountApiKey{ -// ID: newPgUuid(t), -// AccountID: newPgUuid(t), -// KeyValue: "foo", -// CreatedByID: newPgUuid(t), -// UpdatedByID: newPgUuid(t), -// CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, -// KeyName: "foo", -// } -// mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). -// Return(rawData, nil) -// mockIsUserInAccount(mockUserAccountService, true) -// mockQuerier.On("RemoveAccountApiKey", mock.Anything, mock.Anything, mock.Anything).Return(nil) - -// resp, err := svc.DeleteAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.DeleteAccountApiKeyRequest{ -// Id: uuid.NewString(), -// })) -// assert.NoError(t, err) -// assert.NotNil(t, resp) -// } - -// func Test_Service_DeleteAccountApiKey_Existing_ForbiddenAccount(t *testing.T) { -// mockDbtx := neosyncdb.NewMockDBTX(t) -// mockQuerier := db_queries.NewMockQuerier(t) -// mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) - -// svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) - -// rawData := db_queries.NeosyncApiAccountApiKey{ -// ID: newPgUuid(t), -// AccountID: newPgUuid(t), -// KeyValue: "foo", -// CreatedByID: newPgUuid(t), -// UpdatedByID: newPgUuid(t), -// CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, -// KeyName: "foo", -// } -// mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). -// Return(rawData, nil) -// mockIsUserInAccount(mockUserAccountService, false) - -// resp, err := svc.DeleteAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.DeleteAccountApiKeyRequest{ -// Id: uuid.NewString(), -// })) -// assert.Error(t, err) -// assert.Nil(t, resp) -// } - -// func Test_Service_DeleteAccountApiKey_NotFound(t *testing.T) { -// mockDbtx := neosyncdb.NewMockDBTX(t) -// mockQuerier := db_queries.NewMockQuerier(t) -// mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) - -// svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) - -// mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). -// Return(db_queries.NeosyncApiAccountApiKey{}, pgx.ErrNoRows) - -// resp, err := svc.DeleteAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.DeleteAccountApiKeyRequest{ -// Id: uuid.NewString(), -// })) -// assert.NoError(t, err) -// assert.NotNil(t, resp) -// } - -// func Test_Service_DeleteAccountApiKey_Existing_DeleteRace(t *testing.T) { -// mockDbtx := neosyncdb.NewMockDBTX(t) -// mockQuerier := db_queries.NewMockQuerier(t) -// mockUserAccountService := mgmtv1alpha1connect.NewMockUserAccountServiceClient(t) - -// svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserAccountService) - -// rawData := db_queries.NeosyncApiAccountApiKey{ -// ID: newPgUuid(t), -// AccountID: newPgUuid(t), -// KeyValue: "foo", -// CreatedByID: newPgUuid(t), -// UpdatedByID: newPgUuid(t), -// CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, -// ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, -// KeyName: "foo", -// } -// mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). -// Return(rawData, nil) -// mockIsUserInAccount(mockUserAccountService, true) -// mockQuerier.On("RemoveAccountApiKey", mock.Anything, mock.Anything, mock.Anything).Return(pgx.ErrNoRows) - -// resp, err := svc.DeleteAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.DeleteAccountApiKeyRequest{ -// Id: uuid.NewString(), -// })) -// assert.NoError(t, err) -// assert.NotNil(t, resp) -// } - -// func newPgUuid(t *testing.T) pgtype.UUID { -// t.Helper() -// newuuid := uuid.NewString() -// val, err := neosyncdb.ToUuid(newuuid) -// assert.NoError(t, err) -// return val -// } - -// func mockIsUserInAccount(userAccountServiceMock *mgmtv1alpha1connect.MockUserAccountServiceClient, isInAccount bool) { -// userAccountServiceMock.On("IsUserInAccount", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.IsUserInAccountResponse{ -// Ok: isInAccount, -// }), nil) -// } - -// func mockUserAccountCalls( -// userAccountServiceMock *mgmtv1alpha1connect.MockUserAccountServiceClient, -// isInAccount bool, -// userId string, -// ) { -// mockIsUserInAccount(userAccountServiceMock, isInAccount) -// userAccountServiceMock.On("GetUser", mock.Anything, mock.Anything).Return(connect.NewResponse(&mgmtv1alpha1.GetUserResponse{ -// UserId: userId, -// }), nil) -// } +import ( + "context" + "errors" + "testing" + "time" + + "connectrpc.com/connect" + "github.com/google/uuid" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" + db_queries "github.com/nucleuscloud/neosync/backend/gen/go/db" + mgmtv1alpha1 "github.com/nucleuscloud/neosync/backend/gen/go/protos/mgmt/v1alpha1" + "github.com/nucleuscloud/neosync/backend/internal/neosyncdb" + "github.com/nucleuscloud/neosync/backend/internal/userdata" + pgxmock "github.com/nucleuscloud/neosync/internal/mocks/github.com/jackc/pgx/v5" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func Test_Service_GetAccountApiKeys(t *testing.T) { + mockDbtx := neosyncdb.NewMockDBTX(t) + mockQuerier := db_queries.NewMockQuerier(t) + mockUserService := userdata.NewMockInterface(t) + + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) + + rawData := []db_queries.NeosyncApiAccountApiKey{ + { + ID: newPgUuid(t), + AccountID: newPgUuid(t), + KeyValue: "foo", + CreatedByID: newPgUuid(t), + UpdatedByID: newPgUuid(t), + CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, + KeyName: "foo", + }, + { + ID: newPgUuid(t), + AccountID: newPgUuid(t), + KeyValue: "foobar", + CreatedByID: newPgUuid(t), + UpdatedByID: newPgUuid(t), + CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, + KeyName: "foobar", + }, + } + mockQuerier.On("GetAccountApiKeys", mock.Anything, mock.Anything, mock.Anything). + Return(rawData, nil) + mockIsUserInAccount(t, mockUserService, true) + + resp, err := svc.GetAccountApiKeys(context.Background(), connect.NewRequest(&mgmtv1alpha1.GetAccountApiKeysRequest{ + AccountId: uuid.NewString(), + })) + assert.NoError(t, err) + assert.NotNil(t, resp) + assert.NotEmpty(t, resp.Msg.ApiKeys) + assert.Len( + t, + resp.Msg.ApiKeys, + len(rawData), + ) + for idx, apiKey := range resp.Msg.ApiKeys { + dbApikey := rawData[idx] + assert.Equal(t, apiKey.Id, neosyncdb.UUIDString(dbApikey.ID)) + assert.Nil(t, apiKey.KeyValue) + assert.Equal(t, apiKey.Name, dbApikey.KeyName) + } +} + +func Test_Service_GetAccountApiKeys_ForbiddenAccount(t *testing.T) { + mockDbtx := neosyncdb.NewMockDBTX(t) + mockQuerier := db_queries.NewMockQuerier(t) + mockUserService := userdata.NewMockInterface(t) + + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) + + mockIsUserInAccount(t, mockUserService, false) + + resp, err := svc.GetAccountApiKeys(context.Background(), connect.NewRequest(&mgmtv1alpha1.GetAccountApiKeysRequest{ + AccountId: uuid.NewString(), + })) + assert.Error(t, err) + assert.Nil(t, resp) +} + +func Test_Service_GetAccountApiKey_Found(t *testing.T) { + mockDbtx := neosyncdb.NewMockDBTX(t) + mockQuerier := db_queries.NewMockQuerier(t) + mockUserService := userdata.NewMockInterface(t) + + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) + + rawData := db_queries.NeosyncApiAccountApiKey{ + ID: newPgUuid(t), + AccountID: newPgUuid(t), + KeyValue: "foo", + CreatedByID: newPgUuid(t), + UpdatedByID: newPgUuid(t), + CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, + KeyName: "foo", + } + mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). + Return(rawData, nil) + mockIsUserInAccount(t, mockUserService, true) + + resp, err := svc.GetAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.GetAccountApiKeyRequest{ + Id: uuid.NewString(), + })) + assert.NoError(t, err) + assert.NotNil(t, resp) + assert.Equal(t, resp.Msg.ApiKey.Id, neosyncdb.UUIDString(rawData.ID)) + assert.Nil(t, resp.Msg.ApiKey.KeyValue) +} + +func Test_Service_GetAccountApiKey_NotFound(t *testing.T) { + mockDbtx := neosyncdb.NewMockDBTX(t) + mockQuerier := db_queries.NewMockQuerier(t) + mockUserService := userdata.NewMockInterface(t) + + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) + + mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). + Return(db_queries.NeosyncApiAccountApiKey{}, pgx.ErrNoRows) + + resp, err := svc.GetAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.GetAccountApiKeyRequest{ + Id: uuid.NewString(), + })) + assert.Error(t, err) + assert.Nil(t, resp) +} + +func Test_Service_GetAccountApiKey_Found_ForbiddenAccount(t *testing.T) { + mockDbtx := neosyncdb.NewMockDBTX(t) + mockQuerier := db_queries.NewMockQuerier(t) + mockUserService := userdata.NewMockInterface(t) + + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) + + rawData := db_queries.NeosyncApiAccountApiKey{ + ID: newPgUuid(t), + AccountID: newPgUuid(t), + KeyValue: "foo", + CreatedByID: newPgUuid(t), + UpdatedByID: newPgUuid(t), + CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, + KeyName: "foo", + } + mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). + Return(rawData, nil) + mockIsUserInAccount(t, mockUserService, false) + + resp, err := svc.GetAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.GetAccountApiKeyRequest{ + Id: uuid.NewString(), + })) + assert.Error(t, err) + assert.Nil(t, resp) +} + +func Test_Service_CreateAccountApiKey(t *testing.T) { + mockDbtx := neosyncdb.NewMockDBTX(t) + mockQuerier := db_queries.NewMockQuerier(t) + mockTx := pgxmock.NewMockTx(t) + mockUserService := userdata.NewMockInterface(t) + + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) + + mockIsUserInAccount(t, mockUserService, true) + + mockDbtx.On("Begin", mock.Anything).Return(mockTx, nil) + mockTx.On("Commit", mock.Anything).Return(nil) + mockTx.On("Rollback", mock.Anything).Return(nil) + user := db_queries.NeosyncApiUser{ + ID: newPgUuid(t), + UserType: 1, + } + mockQuerier.On("CreateMachineUser", mock.Anything, mock.Anything, mock.Anything). + Return(user, nil) + rawData := db_queries.NeosyncApiAccountApiKey{ + ID: newPgUuid(t), + AccountID: newPgUuid(t), + KeyValue: "foo", + CreatedByID: newPgUuid(t), + UpdatedByID: newPgUuid(t), + CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, + KeyName: "foo", + UserID: user.ID, + } + mockQuerier.On("CreateAccountApiKey", mock.Anything, mock.Anything, mock.Anything). + Return(rawData, nil) + + resp, err := svc.CreateAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.CreateAccountApiKeyRequest{ + AccountId: uuid.NewString(), + Name: "foo", + ExpiresAt: timestamppb.New(time.Now().Add(24 * time.Hour)), + })) + assert.NoError(t, err) + assert.NotNil(t, resp) + assert.NotNil(t, resp.Msg.ApiKey.KeyValue) + assert.NotEqual(t, resp.Msg.ApiKey.KeyValue, rawData.KeyValue, "KeyValue return should be the clear text, not the hash") +} + +func Test_Service_RegenerateAccountApiKey(t *testing.T) { + mockDbtx := neosyncdb.NewMockDBTX(t) + mockQuerier := db_queries.NewMockQuerier(t) + mockUserService := userdata.NewMockInterface(t) + + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) + + mockIsUserInAccount(t, mockUserService, true) + + rawData := db_queries.NeosyncApiAccountApiKey{ + ID: newPgUuid(t), + AccountID: newPgUuid(t), + KeyValue: "foo", + CreatedByID: newPgUuid(t), + UpdatedByID: newPgUuid(t), + CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, + KeyName: "foo", + } + mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). + Return(rawData, nil) + mockQuerier.On("UpdateAccountApiKeyValue", mock.Anything, mock.Anything, mock.Anything). + Return(rawData, nil) + + resp, err := svc.RegenerateAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.RegenerateAccountApiKeyRequest{ + Id: uuid.NewString(), + ExpiresAt: timestamppb.New(time.Now().Add(24 * time.Hour)), + })) + assert.NoError(t, err) + assert.NotNil(t, resp) + assert.NotNil(t, resp.Msg.ApiKey.KeyValue) + assert.NotEqual(t, resp.Msg.ApiKey.KeyValue, rawData.KeyValue, "KeyValue return should be the clear text, not the hash") +} + +func Test_Service_RegenerateAccountApiKey_ForbiddenAccount(t *testing.T) { + mockDbtx := neosyncdb.NewMockDBTX(t) + mockQuerier := db_queries.NewMockQuerier(t) + mockUserService := userdata.NewMockInterface(t) + + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) + + mockIsUserInAccount(t, mockUserService, false) + rawData := db_queries.NeosyncApiAccountApiKey{ + ID: newPgUuid(t), + AccountID: newPgUuid(t), + KeyValue: "foo", + CreatedByID: newPgUuid(t), + UpdatedByID: newPgUuid(t), + CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, + KeyName: "foo", + } + mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). + Return(rawData, nil) + + resp, err := svc.RegenerateAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.RegenerateAccountApiKeyRequest{ + Id: uuid.NewString(), + ExpiresAt: timestamppb.New(time.Now().Add(24 * time.Hour)), + })) + assert.Error(t, err) + assert.Nil(t, resp) +} + +func Test_Service_RegenerateAccountApiKey_NotFound(t *testing.T) { + mockDbtx := neosyncdb.NewMockDBTX(t) + mockQuerier := db_queries.NewMockQuerier(t) + mockUserService := userdata.NewMockInterface(t) + + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) + + mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). + Return(db_queries.NeosyncApiAccountApiKey{}, pgx.ErrNoRows) + + resp, err := svc.RegenerateAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.RegenerateAccountApiKeyRequest{ + Id: uuid.NewString(), + ExpiresAt: timestamppb.New(time.Now().Add(24 * time.Hour)), + })) + assert.Error(t, err) + assert.Nil(t, resp) +} + +func Test_Service_CreateAccountApiKey_ForbiddenAccount(t *testing.T) { + mockDbtx := neosyncdb.NewMockDBTX(t) + mockQuerier := db_queries.NewMockQuerier(t) + mockUserService := userdata.NewMockInterface(t) + + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) + + mockIsUserInAccount(t, mockUserService, false) + + resp, err := svc.CreateAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.CreateAccountApiKeyRequest{ + AccountId: uuid.NewString(), + Name: "foo", + ExpiresAt: timestamppb.New(time.Now().Add(24 * time.Hour)), + })) + assert.Error(t, err) + assert.Nil(t, resp) +} + +func Test_Service_DeleteAccountApiKey_Existing(t *testing.T) { + mockDbtx := neosyncdb.NewMockDBTX(t) + mockQuerier := db_queries.NewMockQuerier(t) + mockUserService := userdata.NewMockInterface(t) + + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) + + rawData := db_queries.NeosyncApiAccountApiKey{ + ID: newPgUuid(t), + AccountID: newPgUuid(t), + KeyValue: "foo", + CreatedByID: newPgUuid(t), + UpdatedByID: newPgUuid(t), + CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, + KeyName: "foo", + } + mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). + Return(rawData, nil) + mockIsUserInAccount(t, mockUserService, true) + mockQuerier.On("RemoveAccountApiKey", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + resp, err := svc.DeleteAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.DeleteAccountApiKeyRequest{ + Id: uuid.NewString(), + })) + assert.NoError(t, err) + assert.NotNil(t, resp) +} + +func Test_Service_DeleteAccountApiKey_Existing_ForbiddenAccount(t *testing.T) { + mockDbtx := neosyncdb.NewMockDBTX(t) + mockQuerier := db_queries.NewMockQuerier(t) + mockUserService := userdata.NewMockInterface(t) + + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) + + rawData := db_queries.NeosyncApiAccountApiKey{ + ID: newPgUuid(t), + AccountID: newPgUuid(t), + KeyValue: "foo", + CreatedByID: newPgUuid(t), + UpdatedByID: newPgUuid(t), + CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, + KeyName: "foo", + } + mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). + Return(rawData, nil) + mockIsUserInAccount(t, mockUserService, false) + + resp, err := svc.DeleteAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.DeleteAccountApiKeyRequest{ + Id: uuid.NewString(), + })) + assert.Error(t, err) + assert.Nil(t, resp) +} + +func Test_Service_DeleteAccountApiKey_NotFound(t *testing.T) { + mockDbtx := neosyncdb.NewMockDBTX(t) + mockQuerier := db_queries.NewMockQuerier(t) + mockUserService := userdata.NewMockInterface(t) + + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) + + mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). + Return(db_queries.NeosyncApiAccountApiKey{}, pgx.ErrNoRows) + + resp, err := svc.DeleteAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.DeleteAccountApiKeyRequest{ + Id: uuid.NewString(), + })) + assert.NoError(t, err) + assert.NotNil(t, resp) +} + +func Test_Service_DeleteAccountApiKey_Existing_DeleteRace(t *testing.T) { + mockDbtx := neosyncdb.NewMockDBTX(t) + mockQuerier := db_queries.NewMockQuerier(t) + mockUserService := userdata.NewMockInterface(t) + + svc := New(&Config{}, neosyncdb.New(mockDbtx, mockQuerier), mockUserService) + + rawData := db_queries.NeosyncApiAccountApiKey{ + ID: newPgUuid(t), + AccountID: newPgUuid(t), + KeyValue: "foo", + CreatedByID: newPgUuid(t), + UpdatedByID: newPgUuid(t), + CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + ExpiresAt: pgtype.Timestamp{Time: time.Now().Add(24 * time.Hour), Valid: true}, + KeyName: "foo", + } + mockQuerier.On("GetAccountApiKeyById", mock.Anything, mock.Anything, mock.Anything). + Return(rawData, nil) + mockIsUserInAccount(t, mockUserService, true) + mockQuerier.On("RemoveAccountApiKey", mock.Anything, mock.Anything, mock.Anything).Return(pgx.ErrNoRows) + + resp, err := svc.DeleteAccountApiKey(context.Background(), connect.NewRequest(&mgmtv1alpha1.DeleteAccountApiKeyRequest{ + Id: uuid.NewString(), + })) + assert.NoError(t, err) + assert.NotNil(t, resp) +} + +func newPgUuid(t *testing.T) pgtype.UUID { + t.Helper() + newuuid := uuid.NewString() + val, err := neosyncdb.ToUuid(newuuid) + assert.NoError(t, err) + return val +} + +func mockIsUserInAccount(t testing.TB, userServiceMock *userdata.MockInterface, isInAccount bool) { + mockEntityEnforcer := userdata.NewMockEntityEnforcer(t) + if isInAccount { + mockEntityEnforcer.On("EnforceAccount", mock.Anything, mock.Anything, mock.Anything).Once().Return(nil) + } else { + mockEntityEnforcer.On("EnforceAccount", mock.Anything, mock.Anything, mock.Anything).Once().Return(errors.New("test: not in account")) + } + userServiceMock.On("GetUser", mock.Anything).Once().Return(&userdata.User{ + EntityEnforcer: mockEntityEnforcer, + }, nil) +}