package services import ( "context" "database/sql/driver" "strconv" "testing" "github.com/DATA-DOG/go-sqlmock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/utils/ptr" "edge-infra.dev/pkg/edge/api/graph/model" sqlquery "edge-infra.dev/pkg/edge/api/sql" "edge-infra.dev/pkg/edge/api/utils" ) const ( testBannerEdgeID101 = "3396a52c-6a22-4049-9593-5a63b596a101" testClusterEdgeID200 = "3396a52c-6a22-4049-9593-5a63b596a200" testRegistryEdgeID = "018ea9c2-ca5d-7a8a-830c-d533e8b52e71" testClusterRegistryEdgeID = "019eac81-da8d-3a2d-122c-c264e7b53c90" testURL = "registry.io" updatedURL = "updated.registry.io" ) var ( testDescription = "Registry" updatedDescription = "Updated registry" registryADescription = "Registry A" registryBDescription = "Registry B" registryCDescription = "Registry C" artifactRegistriesColumns = []string{"registry_edge_id", "banner_edge_id", "description", "registry_url"} clusterArtifactRegistriesColumns = []string{"cluster_registry_edge_id", "cluster_edge_id", "registry_edge_id"} existsRows = []string{"exists"} testArtifactRegistries = []*model.ArtifactRegistry{ { RegistryEdgeID: "018ea9c2-ca5d-7a8a-830c-d533e8b52e71", BannerEdgeID: "3396a52c-6a22-4049-9593-5a63b596a101", Description: ptr.To(registryADescription), URL: "a.registry.io", }, { RegistryEdgeID: "018ea9c2-ca5d-7a8a-830c-d533e8b52e72", BannerEdgeID: "3396a52c-6a22-4049-9593-5a63b596a101", Description: ptr.To(registryBDescription), URL: "b.registry.io", }, { RegistryEdgeID: "018ea9c2-ca5d-7a8a-830c-d533e8b52e73", BannerEdgeID: "3396a52c-6a22-4049-9593-5a63b596a102", Description: ptr.To(registryCDescription), URL: "c.registry.io", }, } testClusterArtifactRegistries = []*model.ClusterArtifactRegistry{ { ClusterRegistryEdgeID: "019eac81-da8d-3a2d-122c-c264e7b53c90", ClusterEdgeID: "3396a52c-6a22-4049-9593-5a63b596a200", RegistryEdgeID: "018ea9c2-ca5d-7a8a-830c-d533e8b52e71", }, { ClusterRegistryEdgeID: "019eac81-da8d-3a2d-122c-c264e7b53c91", ClusterEdgeID: "3396a52c-6a22-4049-9593-5a63b596a200", RegistryEdgeID: "018ea9c2-ca5d-7a8a-830c-d533e8b52e72", }, { ClusterRegistryEdgeID: "019eac81-da8d-3a2d-122c-c264e7b53c92", ClusterEdgeID: "3396a52c-6a22-4049-9593-5a63b596a201", RegistryEdgeID: "018ea9c2-ca5d-7a8a-830c-d533e8b52e71", }, } ) func TestGetArtifactRegistry(t *testing.T) { sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer sqlDB.Close() returnArtifactRegistry := &model.ArtifactRegistry{ RegistryEdgeID: testRegistryEdgeID, BannerEdgeID: testBannerEdgeID, Description: ptr.To(testDescription), URL: testURL, } mockGetArtifactRegistryByIDQuery(mock, testRegistryEdgeID, returnArtifactRegistry) s := NewArtifactRegistryService(sqlDB) artifactRegistry, err := s.GetArtifactRegistry(context.Background(), testRegistryEdgeID) require.NoError(t, err) assert.Equal(t, testRegistryEdgeID, artifactRegistry.RegistryEdgeID) assert.Equal(t, testBannerEdgeID, artifactRegistry.BannerEdgeID) require.NotNil(t, artifactRegistry.Description) assert.Equal(t, testDescription, *artifactRegistry.Description) assert.Equal(t, testURL, artifactRegistry.URL) require.NoError(t, mock.ExpectationsWereMet()) } func TestGetArtifactRegistriesForBanner(t *testing.T) { sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer sqlDB.Close() targetBannerEdgeID := testBannerEdgeID101 returnArtifactRegistries := []*model.ArtifactRegistry{} for _, artifactRegistry := range testArtifactRegistries { if artifactRegistry.BannerEdgeID == targetBannerEdgeID { returnArtifactRegistries = append(returnArtifactRegistries, artifactRegistry) } } mockGetArtifactRegistriesForBannerQuery(mock, targetBannerEdgeID, returnArtifactRegistries) s := NewArtifactRegistryService(sqlDB) artifactRegistries, err := s.GetArtifactRegistriesForBanner(context.Background(), targetBannerEdgeID) require.NoError(t, err) require.Len(t, artifactRegistries, 2) for _, artifactRegistry := range artifactRegistries { assert.Equal(t, targetBannerEdgeID, artifactRegistry.BannerEdgeID) } require.NoError(t, mock.ExpectationsWereMet()) } func TestGetArtifactRegistriesForCluster(t *testing.T) { sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer sqlDB.Close() targetBannerEdgeID := testBannerEdgeID101 targetClusterEdgeID := testClusterEdgeID200 returnArtifactRegistries := testArtifactRegistries[:2] mockGetArtifactRegistriesForClusterQuery(mock, targetClusterEdgeID, returnArtifactRegistries) s := NewArtifactRegistryService(sqlDB) artifactRegistries, err := s.GetArtifactRegistriesForCluster(context.Background(), targetClusterEdgeID) require.NoError(t, err) require.Len(t, artifactRegistries, 2) for _, artifactRegistry := range artifactRegistries { assert.Equal(t, targetBannerEdgeID, artifactRegistry.BannerEdgeID) } require.NoError(t, mock.ExpectationsWereMet()) } func TestCreateArtifactRegistry(t *testing.T) { sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer sqlDB.Close() createInput := &model.ArtifactRegistryCreateInput{ BannerEdgeID: testBannerEdgeID, Description: ptr.To(testDescription), URL: testURL, } mock.ExpectBegin() mockArtifactRegistryCreateQuery(mock, createInput, sqlmock.NewResult(1, 1)) mock.ExpectCommit() s := NewArtifactRegistryService(sqlDB) artifactRegistry, err := s.CreateArtifactRegistryEntry(context.Background(), createInput) require.NoError(t, err) assert.Equal(t, testBannerEdgeID, artifactRegistry.BannerEdgeID) require.NotNil(t, artifactRegistry.Description) assert.Equal(t, testDescription, *artifactRegistry.Description) assert.Equal(t, testURL, artifactRegistry.URL) require.NoError(t, mock.ExpectationsWereMet()) } func TestCreateArtifactRegistryFailsIfURLIsEmpty(t *testing.T) { // no need to mock as validation should fail straight away sqlDB, _, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer sqlDB.Close() createInput := &model.ArtifactRegistryCreateInput{ BannerEdgeID: testBannerEdgeID, URL: "", } s := NewArtifactRegistryService(sqlDB) artifactRegistry, err := s.CreateArtifactRegistryEntry(context.Background(), createInput) require.ErrorIs(t, err, utils.ErrRegistryURLCannotBeEmpty) assert.Nil(t, artifactRegistry) } func TestUpdateArtifactRegistry(t *testing.T) { sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer sqlDB.Close() originalArtifactRegistry := &model.ArtifactRegistry{ RegistryEdgeID: testRegistryEdgeID, BannerEdgeID: testBannerEdgeID, Description: ptr.To(testDescription), URL: testURL, } updateInput := &model.ArtifactRegistryUpdateInput{ Description: &updatedDescription, URL: updatedURL, } mockGetArtifactRegistryByIDQuery(mock, testRegistryEdgeID, originalArtifactRegistry) mock.ExpectBegin() mockArtifactRegistryUpdateQuery(mock, testRegistryEdgeID, updateInput, sqlmock.NewResult(1, 1)) mock.ExpectCommit() s := NewArtifactRegistryService(sqlDB) artifactRegistry, err := s.UpdateArtifactRegistryEntry(context.Background(), testRegistryEdgeID, updateInput) require.NoError(t, err) require.NotNil(t, artifactRegistry.Description) assert.Equal(t, updatedDescription, *artifactRegistry.Description) assert.Equal(t, updatedURL, artifactRegistry.URL) require.NoError(t, mock.ExpectationsWereMet()) } func TestUpdateArtifactRegistryFailsIfURLIsEmpty(t *testing.T) { // no need to mock as validation should fail straight away sqlDB, _, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer sqlDB.Close() updateInput := &model.ArtifactRegistryUpdateInput{ URL: "", } s := NewArtifactRegistryService(sqlDB) artifactRegistry, err := s.UpdateArtifactRegistryEntry(context.Background(), testRegistryEdgeID, updateInput) require.ErrorIs(t, err, utils.ErrRegistryURLCannotBeEmpty) assert.Nil(t, artifactRegistry) } func TestUpdateArtifactRegistryDoesNotUpdateIfNoChangesAreMade(t *testing.T) { sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer sqlDB.Close() originalArtifactRegistry := &model.ArtifactRegistry{ RegistryEdgeID: testRegistryEdgeID, BannerEdgeID: testBannerEdgeID, Description: ptr.To(testDescription), URL: testURL, } updateInput := &model.ArtifactRegistryUpdateInput{ Description: ptr.To(testDescription), URL: testURL, } mockGetArtifactRegistryByIDQuery(mock, testRegistryEdgeID, originalArtifactRegistry) // (no mock call for transaction commit) s := NewArtifactRegistryService(sqlDB) artifactRegistry, err := s.UpdateArtifactRegistryEntry(context.Background(), testRegistryEdgeID, updateInput) require.NoError(t, err) require.NotNil(t, artifactRegistry.Description) assert.Equal(t, testDescription, *artifactRegistry.Description) assert.Equal(t, testURL, artifactRegistry.URL) require.NoError(t, mock.ExpectationsWereMet()) } func TestDeleteArtifactRegistry(t *testing.T) { sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer sqlDB.Close() mockArtifactRegistryExistsQuery(mock, testRegistryEdgeID, true) mockArtifactRegistryDeleteQuery(mock, testRegistryEdgeID, sqlmock.NewResult(1, 1)) s := NewArtifactRegistryService(sqlDB) deleted, err := s.DeleteArtifactRegistryEntry(context.Background(), testRegistryEdgeID) require.NoError(t, err) assert.True(t, deleted) // when entry does not exist, no delete query should be made mockArtifactRegistryExistsQuery(mock, testRegistryEdgeID, false) deleted, err = s.DeleteArtifactRegistryEntry(context.Background(), testRegistryEdgeID) assert.NoError(t, err) assert.False(t, deleted) require.NoError(t, mock.ExpectationsWereMet()) } func TestGetClusterArtifactRegistry(t *testing.T) { sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer sqlDB.Close() returnClusterArtifactRegistry := &model.ClusterArtifactRegistry{ RegistryEdgeID: testRegistryEdgeID, ClusterEdgeID: testClusterEdgeID, ClusterRegistryEdgeID: testClusterRegistryEdgeID, } mockGetClusterArtifactRegistryByIDQuery(mock, testClusterRegistryEdgeID, returnClusterArtifactRegistry) s := NewArtifactRegistryService(sqlDB) clusterArtifactRegistry, err := s.GetClusterArtifactRegistry(context.Background(), testClusterRegistryEdgeID) require.NoError(t, err) assert.Equal(t, testClusterRegistryEdgeID, clusterArtifactRegistry.ClusterRegistryEdgeID) assert.Equal(t, testClusterEdgeID, clusterArtifactRegistry.ClusterEdgeID) assert.Equal(t, testRegistryEdgeID, clusterArtifactRegistry.RegistryEdgeID) require.NoError(t, mock.ExpectationsWereMet()) } func TestGetClusterArtifactRegistriesForCluster(t *testing.T) { sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer sqlDB.Close() targetClusterEdgeID := testClusterEdgeID200 returnClusterArtifactRegistries := []*model.ClusterArtifactRegistry{} for _, clusterArtifactRegistry := range testClusterArtifactRegistries { if clusterArtifactRegistry.ClusterEdgeID == targetClusterEdgeID { returnClusterArtifactRegistries = append(returnClusterArtifactRegistries, clusterArtifactRegistry) } } mockGetClusterArtifactRegistriesForClusterQuery(mock, targetClusterEdgeID, returnClusterArtifactRegistries) s := NewArtifactRegistryService(sqlDB) clusterArtifactRegistries, err := s.GetClusterArtifactRegistries(context.Background(), targetClusterEdgeID) require.NoError(t, err) require.Len(t, clusterArtifactRegistries, 2) for _, clusterArtifactRegistry := range clusterArtifactRegistries { assert.Equal(t, targetClusterEdgeID, clusterArtifactRegistry.ClusterEdgeID) } require.NoError(t, mock.ExpectationsWereMet()) } func TestCreateClusterArtifactRegistry(t *testing.T) { sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer sqlDB.Close() createInput := &model.ClusterArtifactRegistryCreateInput{ ClusterEdgeID: testClusterEdgeID, RegistryEdgeID: testRegistryEdgeID, } mock.ExpectBegin() mockClusterArtifactRegistryCreateQuery(mock, createInput, sqlmock.NewResult(1, 1)) mock.ExpectCommit() s := NewArtifactRegistryService(sqlDB) clusterArtifactRegistry, err := s.CreateClusterArtifactRegistryEntry(context.Background(), createInput) require.NoError(t, err) assert.Equal(t, testClusterEdgeID, clusterArtifactRegistry.ClusterEdgeID) assert.Equal(t, testRegistryEdgeID, clusterArtifactRegistry.RegistryEdgeID) require.NoError(t, mock.ExpectationsWereMet()) } func TestDeleteClusterArtifactRegistry(t *testing.T) { sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual)) require.NoError(t, err) defer sqlDB.Close() mockClusterArtifactRegistryExistsQuery(mock, testClusterRegistryEdgeID, true) mockClusterArtifactRegistryDeleteQuery(mock, testClusterRegistryEdgeID, sqlmock.NewResult(1, 1)) s := NewArtifactRegistryService(sqlDB) deleted, err := s.DeleteClusterArtifactRegistryEntry(context.Background(), testClusterRegistryEdgeID) require.NoError(t, err) assert.True(t, deleted) // when entry does not exists, no delete query should be made mockClusterArtifactRegistryExistsQuery(mock, testClusterRegistryEdgeID, false) deleted, err = s.DeleteClusterArtifactRegistryEntry(context.Background(), testClusterRegistryEdgeID) assert.NoError(t, err) assert.False(t, deleted) require.NoError(t, mock.ExpectationsWereMet()) } func mockGetArtifactRegistryByIDQuery(mock sqlmock.Sqlmock, registryEdgeID string, returnArtifactRegistry *model.ArtifactRegistry) { rows := mock.NewRows(artifactRegistriesColumns).AddRow(returnArtifactRegistry.RegistryEdgeID, returnArtifactRegistry.BannerEdgeID, returnArtifactRegistry.Description, returnArtifactRegistry.URL, ) mock.ExpectQuery(sqlquery.GetArtifactRegistryByIDQuery).WithArgs(registryEdgeID).WillReturnRows(rows) } func mockGetArtifactRegistriesForBannerQuery(mock sqlmock.Sqlmock, bannerEdgeID string, returnArtifactRegistries []*model.ArtifactRegistry) { rows := mock.NewRows(artifactRegistriesColumns) for _, artifactRegistry := range returnArtifactRegistries { rows.AddRow(artifactRegistry.RegistryEdgeID, artifactRegistry.BannerEdgeID, artifactRegistry.Description, artifactRegistry.URL) } mock.ExpectQuery(sqlquery.GetArtifactRegistriesForBannerQuery).WithArgs(bannerEdgeID).WillReturnRows(rows) } func mockGetArtifactRegistriesForClusterQuery(mock sqlmock.Sqlmock, clusterEdgeID string, returnArtifactRegistries []*model.ArtifactRegistry) { rows := mock.NewRows(artifactRegistriesColumns) for _, artifactRegistry := range returnArtifactRegistries { rows.AddRow(artifactRegistry.RegistryEdgeID, artifactRegistry.BannerEdgeID, artifactRegistry.Description, artifactRegistry.URL) } mock.ExpectQuery(sqlquery.GetArtifactRegistriesForClusterQuery).WithArgs(clusterEdgeID).WillReturnRows(rows) } func mockArtifactRegistryCreateQuery(mock sqlmock.Sqlmock, createArtifactRegistry *model.ArtifactRegistryCreateInput, result driver.Result) { args := []driver.Value{ sqlmock.AnyArg(), // RegistryEdgeID is a random UUID createArtifactRegistry.BannerEdgeID, createArtifactRegistry.Description, createArtifactRegistry.URL, } mock.ExpectExec(sqlquery.ArtifactRegistryCreateQuery).WithArgs(args...).WillReturnResult(result) } func mockArtifactRegistryUpdateQuery(mock sqlmock.Sqlmock, registryEdgeID string, updateArtifactRegistry *model.ArtifactRegistryUpdateInput, result driver.Result) { args := []driver.Value{ updateArtifactRegistry.Description, updateArtifactRegistry.URL, registryEdgeID, } mock.ExpectExec(sqlquery.ArtifactRegistryUpdateQuery).WithArgs(args...).WillReturnResult(result) } func mockArtifactRegistryExistsQuery(mock sqlmock.Sqlmock, registryEdgeID string, exists bool) { rows := mock.NewRows(existsRows).AddRow(strconv.FormatBool(exists)) mock.ExpectQuery(sqlquery.ArtifactRegistryExistsQuery).WithArgs(registryEdgeID).WillReturnRows(rows) } func mockArtifactRegistryDeleteQuery(mock sqlmock.Sqlmock, registryEdgeID string, result driver.Result) { mock.ExpectExec(sqlquery.ArtifactRegistryDeleteQuery).WithArgs(registryEdgeID).WillReturnResult(result) } func mockGetClusterArtifactRegistryByIDQuery(mock sqlmock.Sqlmock, clusterRegistryEdgeID string, returnClusterArtifactRegistry *model.ClusterArtifactRegistry) { rows := mock.NewRows(clusterArtifactRegistriesColumns).AddRow( returnClusterArtifactRegistry.ClusterRegistryEdgeID, returnClusterArtifactRegistry.ClusterEdgeID, returnClusterArtifactRegistry.RegistryEdgeID, ) mock.ExpectQuery(sqlquery.GetClusterArtifactRegistryByIDQuery).WithArgs(clusterRegistryEdgeID).WillReturnRows(rows) } func mockGetClusterArtifactRegistriesForClusterQuery(mock sqlmock.Sqlmock, clusterEdgeID string, returnClusterArtifactRegistries []*model.ClusterArtifactRegistry) { rows := mock.NewRows(clusterArtifactRegistriesColumns) for _, clusterArtifactRegistry := range returnClusterArtifactRegistries { rows.AddRow(clusterArtifactRegistry.ClusterRegistryEdgeID, clusterArtifactRegistry.ClusterEdgeID, clusterArtifactRegistry.RegistryEdgeID) } mock.ExpectQuery(sqlquery.GetClusterArtifactRegistriesForClusterQuery).WithArgs(clusterEdgeID).WillReturnRows(rows) } func mockClusterArtifactRegistryCreateQuery(mock sqlmock.Sqlmock, createClusterArtifactRegistry *model.ClusterArtifactRegistryCreateInput, result driver.Result) { args := []driver.Value{ sqlmock.AnyArg(), // ClusterRegistryEdgeID is a random UUID createClusterArtifactRegistry.ClusterEdgeID, createClusterArtifactRegistry.RegistryEdgeID, } mock.ExpectExec(sqlquery.ClusterArtifactRegistryCreateQuery).WithArgs(args...).WillReturnResult(result) } func mockClusterArtifactRegistryExistsQuery(mock sqlmock.Sqlmock, clusterRegistryEdgeID string, exists bool) { rows := mock.NewRows(existsRows).AddRow(strconv.FormatBool(exists)) mock.ExpectQuery(sqlquery.ClusterArtifactRegistryExistsQuery).WithArgs(clusterRegistryEdgeID).WillReturnRows(rows) } func mockClusterArtifactRegistryDeleteQuery(mock sqlmock.Sqlmock, clusterRegistryEdgeID string, result driver.Result) { mock.ExpectExec(sqlquery.ClusterArtifactRegistryDeleteQuery).WithArgs(clusterRegistryEdgeID).WillReturnResult(result) }