diff --git a/pkg/gofr/datasource/pubsub/google/google_test.go b/pkg/gofr/datasource/pubsub/google/google_test.go index 3ee610d36..f9f2ec294 100644 --- a/pkg/gofr/datasource/pubsub/google/google_test.go +++ b/pkg/gofr/datasource/pubsub/google/google_test.go @@ -21,7 +21,8 @@ import ( ) var ( - errTopicExists = errors.New("topic already exists") + errTopicExists = errors.New("topic already exists") + errTestSentinel = errors.New("test-error") ) @@ -47,6 +48,7 @@ func TestGoogleClient_New_InvalidConfig(t *testing.T) { var g *googleClient ctrl := gomock.NewController(t) + defer ctrl.Finish() out := testutil.StderrOutputForFunc(func() { @@ -56,33 +58,41 @@ func TestGoogleClient_New_InvalidConfig(t *testing.T) { }) assert.Nil(t, g) + assert.Contains(t, out, "could not configure google pubsub") } func TestGoogleClient_New_EmptyClient(t *testing.T) { ctrl := gomock.NewController(t) + defer ctrl.Finish() mockMetrics := NewMockMetrics(ctrl) + logger := logging.NewMockLogger(logging.DEBUG) + config := Config{ProjectID: "test", SubscriptionName: "test"} client := New(config, logger, mockMetrics) require.Nil(t, client.client, "TestGoogleClient_New_EmptyClient Failed!") + require.Equal(t, config, client.Config, "TestGoogleClient_New_EmptyClient Failed!") } func TestGoogleClient_Publish_Success(t *testing.T) { client := getGoogleClient(t) + defer client.Close() ctrl := gomock.NewController(t) + defer ctrl.Finish() mockMetrics := NewMockMetrics(ctrl) topic := "test-topic" + message := []byte("test message") out := testutil.StdoutOutputForFunc(func() { @@ -97,6 +107,7 @@ func TestGoogleClient_Publish_Success(t *testing.T) { } mockMetrics.EXPECT().IncrementCounter(gomock.Any(), "app_pubsub_publish_total_count", "topic", topic) + mockMetrics.EXPECT().IncrementCounter(gomock.Any(), "app_pubsub_publish_success_count", "topic", topic) err := g.Publish(t.Context(), topic, message) @@ -113,6 +124,7 @@ func TestGoogleClient_Publish_Success(t *testing.T) { func TestGoogleClient_PublishTopic_Error(t *testing.T) { ctrl := gomock.NewController(t) + defer ctrl.Finish() mockMetrics := NewMockMetrics(ctrl) @@ -121,6 +133,7 @@ func TestGoogleClient_PublishTopic_Error(t *testing.T) { ProjectID: "test", SubscriptionName: "sub", }, metrics: mockMetrics, logger: logging.NewMockLogger(logging.DEBUG)} + defer g.client.Close() ctx, cancel := context.WithCancel(t.Context()) @@ -130,6 +143,7 @@ func TestGoogleClient_PublishTopic_Error(t *testing.T) { mockMetrics.EXPECT().IncrementCounter(gomock.Any(), "app_pubsub_publish_total_count", "topic", "test-topic") err := g.Publish(ctx, "test-topic", []byte("")) + require.ErrorContains(t, err, "context canceled") } @@ -138,27 +152,32 @@ func TestGoogleClient_getTopic_Success(t *testing.T) { ProjectID: "test", SubscriptionName: "sub", }} + defer g.client.Close() topic, err := g.getTopic(t.Context(), "test-topic") require.NoError(t, err) + assert.Equal(t, "test-topic", topic.ID()) } func TestGoogleClient_getTopic_Error(t *testing.T) { ctx, cancel := context.WithCancel(t.Context()) + cancel() g := &googleClient{client: getGoogleClient(t), Config: Config{ ProjectID: "test", SubscriptionName: "sub", }} + defer g.client.Close() topic, err := g.getTopic(ctx, "test-topic") assert.Nil(t, topic) + require.ErrorContains(t, err, "context canceled") } @@ -167,6 +186,7 @@ func TestGoogleClient_getSubscription(t *testing.T) { ProjectID: "test", SubscriptionName: "sub", }} + defer g.client.Close() topic, _ := g.client.CreateTopic(t.Context(), "test-topic") @@ -174,6 +194,7 @@ func TestGoogleClient_getSubscription(t *testing.T) { sub, err := g.getSubscription(t.Context(), topic) require.NoError(t, err) + assert.NotNil(t, sub) } @@ -190,7 +211,6 @@ func Test_validateConfigs(t *testing.T) { for _, tc := range testCases { err := validateConfigs(tc.input) - require.ErrorIs(t, err, tc.expErr) } } @@ -215,9 +235,11 @@ func TestGoogleClient_CloseReturnsError(t *testing.T) { func TestGoogleClient_CreateTopic_Success(t *testing.T) { ctrl := gomock.NewController(t) + defer ctrl.Finish() mockClient := NewMockClient(ctrl) + g := &googleClient{client: mockClient, Config: Config{ProjectID: "test", SubscriptionName: "sub"}} tests := []struct { @@ -247,9 +269,7 @@ func TestGoogleClient_CreateTopic_Success(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.mockBehavior() - err := g.CreateTopic(t.Context(), tt.topicName) - require.ErrorIs(t, err, tt.expectedErr, "expected no error, but got one") }) } @@ -257,9 +277,11 @@ func TestGoogleClient_CreateTopic_Success(t *testing.T) { func TestGoogleClient_CreateTopic_Error(t *testing.T) { ctrl := gomock.NewController(t) + defer ctrl.Finish() mockClient := NewMockClient(ctrl) + g := &googleClient{client: mockClient, Config: Config{ProjectID: "test", SubscriptionName: "sub"}} mockClient.EXPECT().CreateTopic(t.Context(), "test-topic"). @@ -272,6 +294,7 @@ func TestGoogleClient_CreateTopic_Error(t *testing.T) { func TestGoogleClient_CreateTopic_EmptyClient(t *testing.T) { ctrl := gomock.NewController(t) + defer ctrl.Finish() g := &googleClient{client: nil, Config: Config{ProjectID: "test", SubscriptionName: "sub"}} @@ -285,11 +308,12 @@ func TestGoogleClient_DeleteTopic(t *testing.T) { ctx := t.Context() client := getGoogleClient(t) + defer client.Close() g := &googleClient{client: client, Config: Config{ProjectID: "test", SubscriptionName: "sub"}} - // Test successful topic creation + // Test successful topic creation t.Run("DeleteTopic_Success", func(t *testing.T) { err := g.CreateTopic(ctx, "test-topic") require.NoError(t, err) @@ -301,13 +325,13 @@ func TestGoogleClient_DeleteTopic(t *testing.T) { // Test topic deletion with topic not found t.Run("DeleteTopic_NotFound", func(t *testing.T) { err := g.DeleteTopic(ctx, "test-topic") - require.ErrorContains(t, err, "NotFound", "expected NotFound error for non existing topic deletion") }) } func TestGoogleClient_DeleteTopic_EmptyClient(t *testing.T) { ctrl := gomock.NewController(t) + defer ctrl.Finish() g := &googleClient{client: nil, Config: Config{ProjectID: "test", SubscriptionName: "sub"}} @@ -319,15 +343,19 @@ func TestGoogleClient_DeleteTopic_EmptyClient(t *testing.T) { func TestGoogleClient_Query(t *testing.T) { client := getGoogleClient(t) + defer client.Close() ctrl := gomock.NewController(t) + defer ctrl.Finish() mockMetrics := NewMockMetrics(ctrl) + logger := logging.NewMockLogger(logging.DEBUG) topic := "test-topic-query" + message := []byte("test message") g := &googleClient{ @@ -341,25 +369,148 @@ func TestGoogleClient_Query(t *testing.T) { } topicObj, err := client.CreateTopic(t.Context(), topic) + require.NoError(t, err) subName := "sub-query-" + topic + _, err = client.CreateSubscription(t.Context(), subName, gcPubSub.SubscriptionConfig{ Topic: topicObj, }) + require.NoError(t, err) result := topicObj.Publish(t.Context(), &gcPubSub.Message{Data: message}) + _, err = result.Get(t.Context()) + require.NoError(t, err) ctx, cancel := context.WithTimeout(t.Context(), 1*time.Second) + defer cancel() queryResult, err := g.Query(ctx, topic) + require.NoError(t, err) + assert.Equal(t, message, queryResult) err = topicObj.Delete(t.Context()) + + require.NoError(t, err) +} + +func TestIsConnected_WhenClientNotNil(t *testing.T) { + g := &googleClient{client: getGoogleClient(t)} + require.True(t, g.isConnected()) +} + +func TestClose_ClientNil(t *testing.T) { + g := &googleClient{ + receiveChan: map[string]chan *pubsub.Message{ + "test-topic": make(chan *pubsub.Message), + }, + } + + err := g.Close() require.NoError(t, err) } + +func TestClose_MultipleReceiveChans_ClientNil(t *testing.T) { + g := &googleClient{ + receiveChan: map[string]chan *pubsub.Message{ + "topic1": make(chan *pubsub.Message), + "topic2": make(chan *pubsub.Message), + }, + // client is nil + } + + err := g.Close() + require.NoError(t, err) +} + +func TestSubscribe_ClientNil(t *testing.T) { + g := &googleClient{} + + msg, err := g.Subscribe(t.Context(), "test-topic") + require.Nil(t, msg) + require.NoError(t, err) +} + +func TestGetTopic_ClientNil(t *testing.T) { + g := &googleClient{} + + _, err := g.getTopic(t.Context(), "any-topic") + require.Equal(t, errClientNotConnected, err) +} + +func TestIsConnected_WhenClientNil(t *testing.T) { + g := &googleClient{} + require.False(t, g.isConnected()) +} + +func TestGoogleClient_getTopic_CreateFailure(t *testing.T) { + client := getGoogleClient(t) + + defer client.Close() + + // Delete the server to simulate failure in CreateTopic + client.Close() + + g := &googleClient{client: client, Config: Config{ + ProjectID: "test", + SubscriptionName: "sub", + }} + + _, err := g.getTopic(t.Context(), "test-topic") + + require.Error(t, err) +} + +func TestGoogleClient_collectMessages_LimitReached(t *testing.T) { + logger := logging.NewMockLogger(logging.DEBUG) + + g := &googleClient{ + logger: logger, + } + + msgChan := make(chan []byte, 3) + + msgChan <- []byte("message1") + msgChan <- []byte("message2") + + close(msgChan) + + ctx := t.Context() + + result := g.collectMessages(ctx, msgChan, 2) + + expected := []byte("message1\nmessage2") + + assert.Equal(t, expected, result) +} + +func TestGoogleClient_getQuerySubscription_CreateFails(t *testing.T) { + client := getGoogleClient(t) + + defer client.Close() + + g := &googleClient{ + client: client, + Config: Config{ProjectID: "test", SubscriptionName: "sub"}, + } + + topic, err := client.CreateTopic(t.Context(), "test-topic-bad") + + require.NoError(t, err) + + // simulate failure by closing client + client.Close() + + sub, err := g.getQuerySubscription(t.Context(), topic) + + require.Error(t, err) + + assert.Nil(t, sub) +} diff --git a/pkg/gofr/datasource/sql/db_test.go b/pkg/gofr/datasource/sql/db_test.go index 42169e238..b7faf9b7a 100644 --- a/pkg/gofr/datasource/sql/db_test.go +++ b/pkg/gofr/datasource/sql/db_test.go @@ -20,6 +20,7 @@ var ( errDB = testutil.CustomError{ErrorMessage: "DB error"} errSyntax = testutil.CustomError{ErrorMessage: "syntax error"} errTx = testutil.CustomError{ErrorMessage: "error starting transaction"} + errbegin = testutil.CustomError{ErrorMessage: "begin failed"} ) func getDB(t *testing.T, logLevel logging.Level) (*DB, sqlmock.Sqlmock) { @@ -1142,3 +1143,87 @@ func TestClean(t *testing.T) { assert.Empty(t, out) } +func TestDB_CloseWhenNil(t *testing.T) { + db := &DB{} + err := db.Close() + assert.NoError(t, err) +} +func TestDB_BeginTx(t *testing.T) { + db, mock := getDB(t, logging.DEBUG) + defer db.DB.Close() + + ctrl := gomock.NewController(t) + db.metrics = NewMockMetrics(ctrl) + + mock.ExpectBegin() + + tx, err := db.BeginTx(t.Context(), &sql.TxOptions{Isolation: sql.LevelReadCommitted}) + require.NoError(t, err) + assert.NotNil(t, tx) +} +func TestDB_PingSuccess(t *testing.T) { + db, mock := getDB(t, logging.DEBUG) + defer db.DB.Close() + + ctrl := gomock.NewController(t) + db.metrics = NewMockMetrics(ctrl) + + mock.ExpectPing() + mock.ExpectPing().WillReturnError(nil) + + err := db.Ping() + assert.NoError(t, err) +} + +func TestDB_PingFailure(t *testing.T) { + db, mock := getDB(t, logging.DEBUG) + defer db.DB.Close() + + mock.ExpectPing().WillReturnError(sql.ErrConnDone) + + err := db.Ping() + assert.Equal(t, sql.ErrConnDone, err) +} +func TestGetOperationType_EdgeCases(t *testing.T) { + require.Empty(t, getOperationType("")) + require.Empty(t, getOperationType(" ")) + require.Equal(t, "SELECT", getOperationType(" SELECT * FROM users")) +} +func TestClean_EmptyString(t *testing.T) { + require.Empty(t, clean("")) + require.Equal(t, "SELECT", clean(" SELECT ")) +} +func TestDB_Dialect(t *testing.T) { + db, _ := getDB(t, logging.INFO) + defer db.Close() + + db.config.Dialect = "postgresql" + require.Equal(t, "postgresql", db.Dialect()) +} +func TestGetOperationType(t *testing.T) { + tests := []struct { + query string + expected string + }{ + {"SELECT * FROM users", "SELECT"}, + {" INSERT INTO users", "INSERT"}, + {"UPDATE users SET name = ?", "UPDATE"}, + {"DELETE FROM users", "DELETE"}, + {"", ""}, + } + + for _, test := range tests { + result := getOperationType(test.query) + require.Equal(t, test.expected, result) + } +} +func TestDB_Begin_Error(t *testing.T) { + db, mock := getDB(t, logging.DEBUG) + defer db.DB.Close() + + mock.ExpectBegin().WillReturnError(errbegin) + + tx, err := db.Begin() + require.Error(t, err) + assert.Nil(t, tx) +} diff --git a/pkg/gofr/file/zip_test.go b/pkg/gofr/file/zip_test.go index 52e1a8b66..4ccec071d 100644 --- a/pkg/gofr/file/zip_test.go +++ b/pkg/gofr/file/zip_test.go @@ -144,3 +144,33 @@ func (m *mockReadCloser) Read(p []byte) (int, error) { func (*mockReadCloser) Close() error { return nil } + +func TestCreateLocalCopies_WithDirectory(t *testing.T) { + mockZip := &Zip{ + Files: map[string]file{ + "dir1/": {name: "dir1/", isDir: true}, + }, + } + + destDir := "test-dir" + defer os.RemoveAll(destDir) + + err := mockZip.CreateLocalCopies(destDir) + require.NoError(t, err) + + // Check if the directory exists + expectedDir := filepath.Join(destDir, "dir1") + info, err := os.Stat(expectedDir) + require.NoError(t, err) + assert.True(t, info.IsDir(), "Expected dir1 to be a directory") +} +func TestCreateLocalCopies_Failure(t *testing.T) { + mockZip := &Zip{ + Files: map[string]file{ + string([]byte{0x00}): {name: string([]byte{0x00}), content: []byte("invalid"), isDir: false, size: 7}, // Invalid path + }, + } + + err := mockZip.CreateLocalCopies("test-bad") + require.Error(t, err, "Expected error when creating file with invalid name") +} diff --git a/pkg/gofr/http/form_data_binder_test.go b/pkg/gofr/http/form_data_binder_test.go new file mode 100644 index 000000000..fad60b05c --- /dev/null +++ b/pkg/gofr/http/form_data_binder_test.go @@ -0,0 +1,55 @@ +package http + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_setFieldValueFromData(t *testing.T) { + t.Run("String Field", func(t *testing.T) { + var str string + field := reflect.ValueOf(&str).Elem() + + err := setFieldValueFromData(field, "hello") + require.NoError(t, err) + assert.Equal(t, "hello", str) + }) + + t.Run("Int Field", func(t *testing.T) { + var num int + field := reflect.ValueOf(&num).Elem() + + err := setFieldValueFromData(field, 42) + require.NoError(t, err) + assert.Equal(t, 42, num) + }) + + t.Run("Float Field", func(t *testing.T) { + var f float64 + field := reflect.ValueOf(&f).Elem() + + err := setFieldValueFromData(field, 3.14) + require.NoError(t, err) + assert.InEpsilon(t, 3.14, f, 0.001) + }) + + t.Run("Bool Field", func(t *testing.T) { + var b bool + field := reflect.ValueOf(&b).Elem() + + err := setFieldValueFromData(field, true) + require.NoError(t, err) + assert.True(t, b) + }) + + t.Run("Unsupported Kind", func(t *testing.T) { + var m map[string]string + field := reflect.ValueOf(&m).Elem() + err := setFieldValueFromData(field, map[string]string{"a": "b"}) + require.Error(t, err) + assert.Contains(t, err.Error(), "unsupported type for field") + }) +} diff --git a/pkg/gofr/logging/logger_test.go b/pkg/gofr/logging/logger_test.go index 74cfc67cc..fdf4b41c8 100644 --- a/pkg/gofr/logging/logger_test.go +++ b/pkg/gofr/logging/logger_test.go @@ -249,3 +249,21 @@ func TestPrettyPrint(t *testing.T) { assert.Contains(t, outputLog, v) } } + +func TestNewFileLogger_UnwritablePath(t *testing.T) { + l := NewFileLogger("/root/invalid.log") + logger, ok := l.(*logger) + require.True(t, ok) + + assert.Equal(t, io.Discard, logger.normalOut) + assert.Equal(t, io.Discard, logger.errorOut) +} + +func TestNewFileLogger_NilPath(t *testing.T) { + l := NewFileLogger("") + logger, ok := l.(*logger) + require.True(t, ok) + + assert.Equal(t, io.Discard, logger.normalOut) + assert.Equal(t, io.Discard, logger.errorOut) +} diff --git a/pkg/gofr/migration/sql_test.go b/pkg/gofr/migration/sql_test.go index 3459d5f01..5360c3d8f 100644 --- a/pkg/gofr/migration/sql_test.go +++ b/pkg/gofr/migration/sql_test.go @@ -5,12 +5,15 @@ import ( "errors" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" "gofr.dev/pkg/gofr/container" ) +var errCreateTable = errors.New("create table error") + func TestQuery(t *testing.T) { t.Run("successful query", func(t *testing.T) { var id int @@ -34,15 +37,20 @@ func TestQuery(t *testing.T) { mocks.SQL.ExpectQuery("SELECT * FROM users").WithoutArgs().WillReturnRows(expectedRows) rows, err := mockContainer.SQL.Query("SELECT * FROM users") + require.NoError(t, err, "TestQuery : error executing mock query") i := 0 for rows.Next() { require.NoError(t, rows.Err(), "TestQuery: row error") + err = rows.Scan(&id, &name) + require.NoError(t, err, "TestQuery: row scan error") + require.Equal(t, expectedResult[i].id, id, "TestQuery: resultant ID & expected ID are not same") + require.Equal(t, expectedResult[i].name, name, "TestQuery: resultant name & expected name are not same") i++ @@ -55,19 +63,26 @@ func TestQuery(t *testing.T) { var name string mockContainer, mocks := container.NewMockContainer(t) + expectedErr := sql.ErrNoRows + expectedRows := mocks.SQL.NewRows([]string{"id", "name"}) mocks.SQL.ExpectQuery("SELECT * FROM unknown_table").WithoutArgs().WillReturnRows(expectedRows) + sqlMockDB := mockContainer.SQL rows, err := sqlMockDB.Query("SELECT * FROM unknown_table") + require.NoError(t, err, "TestQuery : error executing mock query") for rows.Next() { require.NoError(t, rows.Err(), "TestQuery: row error") + err = rows.Scan(&id, &name) + require.Error(t, err, "TestQuery: row scan error") + require.Equal(t, expectedErr, err, "TestQuery: expected error is not equal to resultant error") } }) @@ -84,11 +99,15 @@ func TestQueryRow(t *testing.T) { expectedRows := mocks.SQL.NewRows([]string{"id", "name"}).AddRow(1, "Alex") mocks.SQL.ExpectQuery("SELECT * FROM users WHERE id = ?").WithArgs(1).WillReturnRows(expectedRows) + sqlMockDB := mockContainer.SQL err := sqlMockDB.QueryRow("SELECT * FROM users WHERE id = ?", 1).Scan(&id, &name) + require.NoError(t, err, "TestQueryRow: row scan error") + require.Equal(t, 1, id, "TestQueryRow: expected id to be equal to 1") + require.Equal(t, "Alex", name, "TestQueryRow: expected name to be equal to 'Alex'") }) } @@ -106,11 +125,15 @@ func TestQueryRowContext(t *testing.T) { expectedRows := mocks.SQL.NewRows([]string{"id", "name"}).AddRow(1, "Alex") mocks.SQL.ExpectQuery("SELECT * FROM users WHERE id = ?").WithArgs(1).WillReturnRows(expectedRows) + sqlMockDB := mockContainer.SQL err := sqlMockDB.QueryRowContext(ctx, "SELECT * FROM users WHERE id = ?", 1).Scan(&id, &name) + require.NoError(t, err, "TestQueryRowContext: Error while scanning row") + require.Equal(t, 1, id, "TestQueryRowContext: expected id to be equal to 1") + require.Equal(t, "Alex", name, "TestQueryRowContext: expected name to be equal to 'Alex'") }) } @@ -122,24 +145,31 @@ func TestExec(t *testing.T) { expectedResult := mocks.SQL.NewResult(10, 1) mocks.SQL.ExpectExec("DELETE FROM users WHERE id = ?").WithArgs(1).WillReturnResult(expectedResult) + sqlDB := mockContainer.SQL result, err := sqlDB.Exec("DELETE FROM users WHERE id = ?", 1) + require.NoError(t, err, "TestExec: error while executing mock query") expectedLastInserted, err := expectedResult.LastInsertId() + require.NoError(t, err, "TestExec: error while retrieving last inserted id from expected sqlresult") resultLastInserted, err := result.LastInsertId() + require.NoError(t, err, "TestExec: error while retrieving last inserted id from mock sqlresult") expectedRowsAffected, err := expectedResult.RowsAffected() + require.NoError(t, err, "TestExec: error while retrieving rows affected from expected sqlresult") resultRowsAffected, err := result.RowsAffected() + require.NoError(t, err, "TestExec: error while retrieving rows affected from mock sqlresult") require.Equal(t, expectedLastInserted, resultLastInserted, "TestExec: expected last inserted id to be equal to 10") + require.Equal(t, expectedRowsAffected, resultRowsAffected, "TestExec: expected rows affected to be equal to 1") }) @@ -147,11 +177,15 @@ func TestExec(t *testing.T) { mockContainer, mocks := container.NewMockContainer(t) expectedErr := sql.ErrNoRows + mocks.SQL.ExpectExec("UPDATE unknown_table SET name = ?").WithArgs("John").WillReturnError(expectedErr) + sqlMockDB := mockContainer.SQL _, err := sqlMockDB.Exec("UPDATE unknown_table SET name = ?", "John") + require.Error(t, err, "TestExec: expected error while executing mock query") + require.Equal(t, expectedErr, err, "TestExec: Exec should return the expected error, got: %v", err) }) } @@ -161,36 +195,48 @@ func TestExecContext(t *testing.T) { t.Run("successful exec context", func(t *testing.T) { mockContainer, mocks := container.NewMockContainer(t) + expectedResult := mocks.SQL.NewResult(10, 1) + mocks.SQL.ExpectExec("DELETE FROM users WHERE id = ?").WithArgs(1).WillReturnResult(expectedResult) + sqlMockDB := mockContainer.SQL result, err := sqlMockDB.ExecContext(ctx, "DELETE FROM users WHERE id = ?", 1) + require.NoError(t, err, "TestExecContext: error while executing mock query") expectedLastInserted, err := expectedResult.LastInsertId() + require.NoError(t, err, "TestExecContext: error while retrieving last inserted id from expected sqlresult") resultLastInserted, err := result.LastInsertId() + require.NoError(t, err, "TestExecContext: error while retrieving last inserted id from mock sqlresult") expectedRowsAffected, err := expectedResult.RowsAffected() + require.NoError(t, err, "TestExecContext: error while retrieving rows affected from expected sqlresult") resultRowsAffected, err := result.RowsAffected() + require.NoError(t, err, "TestExecContext: error while retrieving rows affected from mock sqlresult") require.Equal(t, expectedLastInserted, resultLastInserted, "TestExecContext: expected last inserted id to be equal to 10") + require.Equal(t, expectedRowsAffected, resultRowsAffected, "TestExecContext: expected rows affected to be equal to 1") }) } func TestCheckAndCreateMigrationTableSuccess(t *testing.T) { ctrl := gomock.NewController(t) + mockMigrator := NewMockmigrator(ctrl) + mockContainer, mocks := container.NewMockContainer(t) mockMigrator.EXPECT().checkAndCreateMigrationTable(mockContainer) + mocks.SQL.ExpectExec(createSQLGoFrMigrationsTable).WillReturnResult(mocks.SQL.NewResult(1, 1)) migrator := sqlMigrator{ @@ -199,13 +245,17 @@ func TestCheckAndCreateMigrationTableSuccess(t *testing.T) { } err := migrator.checkAndCreateMigrationTable(mockContainer) + require.NoError(t, err, "TestCheckAndCreateMigrationTable: error while executing mock query") } func TestCheckAndCreateMigrationTableExecError(t *testing.T) { ctrl := gomock.NewController(t) + mockMigrator := NewMockmigrator(ctrl) + mockContainer, mocks := container.NewMockContainer(t) + expectedErr := sql.ErrNoRows mocks.SQL.ExpectExec(createSQLGoFrMigrationsTable).WillReturnError(expectedErr) @@ -216,16 +266,21 @@ func TestCheckAndCreateMigrationTableExecError(t *testing.T) { } err := migrator.checkAndCreateMigrationTable(mockContainer) + require.Error(t, err, "TestCheckAndCreateMigrationTable: expected an error while executing mock query") + require.Equal(t, expectedErr, err, "TestCheckAndCreateMigrationTable: resultant error is not eual to expected error") } func TestBeginTransactionSuccess(t *testing.T) { ctrl := gomock.NewController(t) + mockMigrator := NewMockmigrator(ctrl) + mockContainer, mocks := container.NewMockContainer(t) mocks.SQL.ExpectBegin() + mockMigrator.EXPECT().beginTransaction(mockContainer) migrator := sqlMigrator{ @@ -234,6 +289,7 @@ func TestBeginTransactionSuccess(t *testing.T) { } data := migrator.beginTransaction(mockContainer) + require.NotNil(t, data.SQLTx.Tx, "TestBeginTransaction: SQLTX.tx should not be nil") } @@ -243,7 +299,9 @@ var ( func TestBeginTransactionDBError(t *testing.T) { ctrl := gomock.NewController(t) + mockMigrator := NewMockmigrator(ctrl) + mockContainer, mocks := container.NewMockContainer(t) mocks.SQL.ExpectBegin().WillReturnError(errBeginTx) @@ -254,6 +312,7 @@ func TestBeginTransactionDBError(t *testing.T) { } data := migrator.beginTransaction(mockContainer) + require.Nil(t, data.SQLTx, "TestBeginTransaction: beginTransaction should not return a transaction on DB error") } @@ -261,5 +320,84 @@ func TestRollbackNoTransaction(t *testing.T) { mockContainer, _ := container.NewMockContainer(t) migrator := sqlMigrator{} + migrator.rollback(mockContainer, transactionData{}) } + +func TestApply(t *testing.T) { + ctrl := gomock.NewController(t) + mockMigrator := NewMockmigrator(ctrl) + mockContainer, _ := container.NewMockContainer(t) + + ds := &sqlDS{SQL: mockContainer.SQL} + result := ds.apply(mockMigrator) + + sqlMig, ok := result.(sqlMigrator) + require.True(t, ok, "Result should be an sqlMigrator") + require.Equal(t, mockContainer.SQL, sqlMig.SQL, "SQL field should match") + require.Equal(t, mockMigrator, sqlMig.migrator, "Migrator field should match") +} + +func TestGetLastMigration_UseMigratorFallback(t *testing.T) { + ctrl := gomock.NewController(t) + mockMigrator := NewMockmigrator(ctrl) + mockContainer, mocks := container.NewMockContainer(t) + + mocks.SQL.ExpectQuery(getLastSQLGoFrMigration). + WillReturnRows(mocks.SQL.NewRows([]string{"version"}).AddRow(2)) + + mockMigrator.EXPECT().getLastMigration(mockContainer).Return(int64(5)) + + migrator := sqlMigrator{SQL: mockContainer.SQL, migrator: mockMigrator} + + last := migrator.getLastMigration(mockContainer) + require.Equal(t, int64(5), last, "Expected getLastMigration to return higher value from embedded migrator") +} + +func TestGetLastMigration_MigratorReturnsLesser(t *testing.T) { + ctrl := gomock.NewController(t) + mockMigrator := NewMockmigrator(ctrl) + mockContainer, mocks := container.NewMockContainer(t) + + mocks.SQL.ExpectQuery(getLastSQLGoFrMigration). + WillReturnRows(mocks.SQL.NewRows([]string{"version"}).AddRow(7)) + + mockMigrator.EXPECT().getLastMigration(mockContainer).Return(int64(5)) + + migrator := sqlMigrator{SQL: mockContainer.SQL, migrator: mockMigrator} + + last := migrator.getLastMigration(mockContainer) + require.Equal(t, int64(7), last, "Should return SQL migration value as it's higher") +} + +func TestBeginTransaction_ReplaceSQLTx(t *testing.T) { + ctrl := gomock.NewController(t) + mockMigrator := NewMockmigrator(ctrl) + mockContainer, mocks := container.NewMockContainer(t) + + mocks.SQL.ExpectBegin() // this returns a usable SQLTx + + mockMigrator.EXPECT().beginTransaction(mockContainer).Return(transactionData{ + MigrationNumber: 123, + }) + + migrator := sqlMigrator{SQL: mockContainer.SQL, migrator: mockMigrator} + + data := migrator.beginTransaction(mockContainer) + + require.NotNil(t, data.SQLTx, "SQLTx should not be nil") + require.Equal(t, int64(123), data.MigrationNumber, "Expected migration number from embedded migrator") +} + +func TestCheckAndCreateMigrationTable_ErrorCreatingTable(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockContainer, mocks := container.NewMockContainer(t) + mocks.SQL.ExpectExec(createSQLGoFrMigrationsTable).WillReturnError(errCreateTable) + + m := sqlMigrator{} + err := m.checkAndCreateMigrationTable(mockContainer) + require.Error(t, err) + assert.Contains(t, err.Error(), "create table error") +}