From 0809dbe9fc318f6e3a9439eb2937163c7637c5dc Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Sun, 8 Jun 2025 00:07:50 +0330 Subject: [PATCH 01/14] install github.com/DATA-DOG/go-sqlmock --- go.mod | 1 + go.sum | 3 +++ 2 files changed, 4 insertions(+) diff --git a/go.mod b/go.mod index fd2671c..06bbaf0 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect + github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect diff --git a/go.sum b/go.sum index 1f67318..0889bdc 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -36,6 +38,7 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= From ed63e9d0d74c1b4a678d0adc9434ce414e7e36c2 Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Sun, 8 Jun 2025 00:07:57 +0330 Subject: [PATCH 02/14] Create user_handler_test.go --- internal/handlers/user_handler_test.go | 44 ++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 internal/handlers/user_handler_test.go diff --git a/internal/handlers/user_handler_test.go b/internal/handlers/user_handler_test.go new file mode 100644 index 0000000..bdb7fc2 --- /dev/null +++ b/internal/handlers/user_handler_test.go @@ -0,0 +1,44 @@ +package handlers + +import ( + "encoding/json" + "github.com/DATA-DOG/go-sqlmock" + "net/http" + "net/http/httptest" + "testing" +) + +func TestGetLatestUsers_Success(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("mocking error: %v", err) + } + defer db.Close() + + rows := sqlmock.NewRows([]string{"id", "name", "email", "phone", "created_at"}). + AddRow(1, "Alice", "alice@example.com", "123456", "2024-01-01T00:00:00Z"). + AddRow(2, "Bob", "bob@example.com", "654321", "2024-01-02T00:00:00Z") + + mock.ExpectQuery("SELECT id, name, COALESCE").WillReturnRows(rows) + + handler := &DBHandler{DB: db} + + req := httptest.NewRequest(http.MethodGet, "/users", nil) + w := httptest.NewRecorder() + + handler.GetLatestUsers(w, req) + + resp := w.Result() + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Errorf("expected status 200, got %d", resp.StatusCode) + } + + var response map[string]interface{} + json.NewDecoder(resp.Body).Decode(&response) + + if _, ok := response["data"]; !ok { + t.Error("expected data in response") + } +} From 79120ffd3bce1faa4e5cdc63c0205b189bf9f3e0 Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Fri, 13 Jun 2025 19:49:22 +0330 Subject: [PATCH 03/14] add `TestDeleteLabel_OK` --- go.mod | 9 ++- go.sum | 6 ++ internal/handlers/label_handler_test.go | 84 ++++++++++++------------- 3 files changed, 54 insertions(+), 45 deletions(-) diff --git a/go.mod b/go.mod index 06bbaf0..de9a601 100644 --- a/go.mod +++ b/go.mod @@ -3,21 +3,25 @@ module github.com/milwad-dev/do-it go 1.22.0 require ( + github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/go-chi/chi/v5 v5.2.0 github.com/go-playground/validator/v10 v10.25.0 github.com/go-sql-driver/mysql v1.8.1 github.com/joho/godotenv v1.5.1 + github.com/redis/go-redis/v9 v9.8.0 + github.com/stretchr/testify v1.9.0 github.com/swaggo/http-swagger v1.3.4 github.com/swaggo/swag v1.16.4 + go.uber.org/zap v1.27.0 golang.org/x/crypto v0.32.0 ) require ( filippo.io/edwards25519 v1.1.0 // indirect - github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect @@ -29,10 +33,9 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.9.0 // indirect - github.com/redis/go-redis/v9 v9.8.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/swaggo/files v1.0.1 // indirect go.uber.org/multierr v1.10.0 // indirect - go.uber.org/zap v1.27.0 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect diff --git a/go.sum b/go.sum index 0889bdc..e81c62f 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,10 @@ github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7Oputl github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -62,6 +66,8 @@ github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4 github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= diff --git a/internal/handlers/label_handler_test.go b/internal/handlers/label_handler_test.go index 539569d..5f5e428 100644 --- a/internal/handlers/label_handler_test.go +++ b/internal/handlers/label_handler_test.go @@ -1,58 +1,58 @@ package handlers import ( - "bytes" + "context" + "github.com/dgrijalva/jwt-go" + "github.com/go-chi/chi/v5" "net/http" "net/http/httptest" "testing" -) -// Mock dbHandler struct to satisfy the dbHandler interface -type dbHandler struct{} + "github.com/DATA-DOG/go-sqlmock" + "github.com/stretchr/testify/require" +) -func TestStoreLabel(t *testing.T) { - // Create a mock dbHandler with any necessary dependencies - mockDB := &dbHandler{} // You may need to create a mock dbHandler +func TestDeleteLabel_OK(t *testing.T) { + // Mock DB + db, mock, err := sqlmock.New() + require.NoError(t, err) + defer db.Close() - // Create a sample label JSON body - labelJSON := []byte(`{"title": "Test Label", "color": "#FF0000"}`) + // Expectations + mock.ExpectQuery(`SELECT count\(\*\) FROM labels WHERE id = \? AND user_id = \?`). + WithArgs("42", float64(1)). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1)) - // Create a mock HTTP request with the sample label data - req, err := http.NewRequest("POST", "/labels", bytes.NewBuffer(labelJSON)) - if err != nil { - t.Fatal(err) - } + mock.ExpectExec(`DELETE FROM labels WHERE id = \? AND user_id = \?`). + WithArgs("42", float64(1)). + WillReturnResult(sqlmock.NewResult(0, 1)) - // Create a ResponseRecorder to record the response - rr := httptest.NewRecorder() + // Handler + h := &DBHandler{DB: db} - // Call the storeLabel handler function with the mock dbHandler and mock HTTP request - handler := http.HandlerFunc(mockDB.storeLabel) - handler.ServeHTTP(rr, req) + // Request setup + req := httptest.NewRequest(http.MethodDelete, "/labels/42", nil) - // Check the status code returned by the handler - if status := rr.Code; status != http.StatusOK { - t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) - } + // Add Chi route context with id param + rctx := chi.NewRouteContext() + rctx.URLParams.Add("id", "42") + req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx)) - // Check if the response body contains the expected message - expectedResponse := `"message":"The label stored successfully."` - if rr.Body.String() != expectedResponse { - t.Errorf("handler returned unexpected body: got %v want %v", rr.Body.String(), expectedResponse) - } -} + // Add JWT claims to context + req = req.WithContext(context.WithValue(req.Context(), "userID", jwt.MapClaims{ + "user_id": float64(1), + })) -// Mock implementation of storeLabel function to satisfy the dbHandler interface -func (db *dbHandler) storeLabel(w http.ResponseWriter, r *http.Request) { - data := make(map[string]string) - data["message"] = "The label stored successfully." - jsonResponse(w, data, http.StatusOK) -} - -// Mock implementation of jsonResponse function for testing purposes -func jsonResponse(w http.ResponseWriter, data map[string]string, statusCode int) { - w.WriteHeader(statusCode) - for key, value := range data { - w.Write([]byte(`"` + key + `":"` + value + `"`)) - } + // Recorder & handler call + rr := httptest.NewRecorder() + h.DeleteLabel(rr, req) + + // Assert + require.Equal(t, http.StatusOK, rr.Code) + require.JSONEq(t, `{"data": { +"message":"The label deleted successfully." +}, +"status": "Success" +}`, rr.Body.String()) + require.NoError(t, mock.ExpectationsWereMet()) } From 2a7326db5c4bbfbaf407e517c92567f114b01ba0 Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Fri, 13 Jun 2025 20:27:04 +0330 Subject: [PATCH 04/14] Update .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f5546f2..5561aac 100644 --- a/.gitignore +++ b/.gitignore @@ -29,5 +29,6 @@ go.work # Miscellaneous todo.txt -# Logger +# Logs logs/*.log +internal/handlers/logs/*.log From a8e00d219cdcea70645b535bc991abcc1c325c14 Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Fri, 13 Jun 2025 20:27:07 +0330 Subject: [PATCH 05/14] Update label_handler.go --- internal/handlers/label_handler.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/handlers/label_handler.go b/internal/handlers/label_handler.go index 2ea2045..02d679f 100644 --- a/internal/handlers/label_handler.go +++ b/internal/handlers/label_handler.go @@ -30,8 +30,8 @@ func (db *DBHandler) GetLatestLabels(w http.ResponseWriter, r *http.Request) { u.id, u.name, COALESCE(u.email, ''), COALESCE(u.phone, ''), u.created_at FROM labels l JOIN users u ON l.user_id = u.id - ORDER BY l.created_at DESC - WHERE user_id = ?` + WHERE l.user_id = ? + ORDER BY l.created_at DESC` rows, err := db.Query(query, userId) if err != nil { data["message"] = err.Error() From 0c1c5ad0508fb70295a695850923dff6d7b4efad Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Fri, 13 Jun 2025 23:01:56 +0330 Subject: [PATCH 06/14] Update label_handler.go --- internal/handlers/label_handler.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/handlers/label_handler.go b/internal/handlers/label_handler.go index 02d679f..c9fea94 100644 --- a/internal/handlers/label_handler.go +++ b/internal/handlers/label_handler.go @@ -27,9 +27,9 @@ func (db *DBHandler) GetLatestLabels(w http.ResponseWriter, r *http.Request) { query := ` SELECT l.id, l.title, l.color, l.created_at, l.updated_at, l.user_id, - u.id, u.name, COALESCE(u.email, ''), COALESCE(u.phone, ''), u.created_at - FROM labels l - JOIN users u ON l.user_id = u.id + u.id, u.name, COALESCE(u.email, ''), COALESCE(u.phone, ''), u.created_at + FROM labels AS l + JOIN users AS u ON l.user_id = u.id WHERE l.user_id = ? ORDER BY l.created_at DESC` rows, err := db.Query(query, userId) From 087c2371fa9f0afc6e58a4ed768c8abca5724308 Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Fri, 13 Jun 2025 23:02:01 +0330 Subject: [PATCH 07/14] Update label_handler_test.go --- internal/handlers/label_handler_test.go | 84 ++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 8 deletions(-) diff --git a/internal/handlers/label_handler_test.go b/internal/handlers/label_handler_test.go index 5f5e428..cba6634 100644 --- a/internal/handlers/label_handler_test.go +++ b/internal/handlers/label_handler_test.go @@ -4,6 +4,7 @@ import ( "context" "github.com/dgrijalva/jwt-go" "github.com/go-chi/chi/v5" + "github.com/milwad-dev/do-it/internal/logger" "net/http" "net/http/httptest" "testing" @@ -12,6 +13,68 @@ import ( "github.com/stretchr/testify/require" ) +func TestGetLatestLabels_OK(t *testing.T) { + // Mock DB + db, mock, err := sqlmock.New() + require.NoError(t, err) + defer db.Close() + + // Initialize logger in non-production mode for testing (prints JSON to stdout + file) + logger.InitLogger(false) + + // Expectations + mock.ExpectQuery(`SELECT l\.id, l\.title, l\.color, l\.created_at, l\.updated_at, l\.user_id, +\s*u\.id, u\.name, COALESCE\(u\.email, ''\), COALESCE\(u\.phone, ''\), u\.created_at +\s*FROM labels l +\s*JOIN users u ON l\.user_id = u\.id +\s*WHERE l\.user_id = \? +\s*ORDER BY l\.created_at DESC`). + WithArgs(float64(1)). + WillReturnRows(sqlmock.NewRows([]string{ + "id", "title", "color", "created_at", "updated_at", "user_id", + "user_id", "name", "email", "phone", "user_created_at", + }).AddRow( + 1, "Label Title", "#FF0000", "2025-01-01 10:00:00", "2025-01-02 10:00:00", 42, + 42, "User Name", "user@example.com", "1234567890", "2024-12-31 09:00:00", + )) + + // Handler + h := &DBHandler{DB: db} + + // Request setup + req := httptest.NewRequest(http.MethodGet, "/labels", nil) + + // Add Chi route context with id param + req = callContext(req) + + // Recorder & handler call + rr := httptest.NewRecorder() + h.GetLatestLabels(rr, req) + + // Assert + require.Equal(t, http.StatusOK, rr.Code) + require.JSONEq(t, `{"data":{ +{ +"color":"#FF0000", +"created_at":"2025-01-01 10:00:00", +"id":1, +"title":"Label Title", +"updated_at":"2025-01-02 10:00:00", +"user":{ +"created_at":"2024-12-31 09:00:00", +"email":"user@example.com", +"emailVerified_at":"0001-01-01T00:00:00Z", +"id":42, +"name":"User Name", +"phone":"1234567890", +"phoneVerified_at":"0001-01-01T00:00:00Z", +"updated_at":""} +}}, +"status": "Success" +}`, rr.Body.String()) + require.NoError(t, mock.ExpectationsWereMet()) +} + func TestDeleteLabel_OK(t *testing.T) { // Mock DB db, mock, err := sqlmock.New() @@ -34,14 +97,7 @@ func TestDeleteLabel_OK(t *testing.T) { req := httptest.NewRequest(http.MethodDelete, "/labels/42", nil) // Add Chi route context with id param - rctx := chi.NewRouteContext() - rctx.URLParams.Add("id", "42") - req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx)) - - // Add JWT claims to context - req = req.WithContext(context.WithValue(req.Context(), "userID", jwt.MapClaims{ - "user_id": float64(1), - })) + req = callContext(req) // Recorder & handler call rr := httptest.NewRecorder() @@ -56,3 +112,15 @@ func TestDeleteLabel_OK(t *testing.T) { }`, rr.Body.String()) require.NoError(t, mock.ExpectationsWereMet()) } + +func callContext(req *http.Request) *http.Request { + rctx := chi.NewRouteContext() + rctx.URLParams.Add("id", "42") + req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx)) + + // Add JWT claims to context + req = req.WithContext(context.WithValue(req.Context(), "userID", jwt.MapClaims{ + "user_id": float64(1), + })) + return req +} From 833654da6980e06145d160999dafd7162af891b9 Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Tue, 1 Jul 2025 21:53:47 +0330 Subject: [PATCH 08/14] Update label_handler_test.go --- internal/handlers/label_handler_test.go | 54 ++++++++++++++----------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/internal/handlers/label_handler_test.go b/internal/handlers/label_handler_test.go index cba6634..59bf9ba 100644 --- a/internal/handlers/label_handler_test.go +++ b/internal/handlers/label_handler_test.go @@ -7,6 +7,7 @@ import ( "github.com/milwad-dev/do-it/internal/logger" "net/http" "net/http/httptest" + "regexp" "testing" "github.com/DATA-DOG/go-sqlmock" @@ -23,12 +24,14 @@ func TestGetLatestLabels_OK(t *testing.T) { logger.InitLogger(false) // Expectations - mock.ExpectQuery(`SELECT l\.id, l\.title, l\.color, l\.created_at, l\.updated_at, l\.user_id, -\s*u\.id, u\.name, COALESCE\(u\.email, ''\), COALESCE\(u\.phone, ''\), u\.created_at -\s*FROM labels l -\s*JOIN users u ON l\.user_id = u\.id -\s*WHERE l\.user_id = \? -\s*ORDER BY l\.created_at DESC`). + mock.ExpectQuery(regexp.QuoteMeta(` + SELECT l.id, l.title, l.color, l.created_at, l.updated_at, l.user_id, + u.id, u.name, COALESCE(u.email, ''), COALESCE(u.phone, ''), u.created_at + FROM labels AS l + JOIN users AS u ON l.user_id = u.id + WHERE l.user_id = ? + ORDER BY l.created_at DESC +`)). WithArgs(float64(1)). WillReturnRows(sqlmock.NewRows([]string{ "id", "title", "color", "created_at", "updated_at", "user_id", @@ -53,24 +56,27 @@ func TestGetLatestLabels_OK(t *testing.T) { // Assert require.Equal(t, http.StatusOK, rr.Code) - require.JSONEq(t, `{"data":{ -{ -"color":"#FF0000", -"created_at":"2025-01-01 10:00:00", -"id":1, -"title":"Label Title", -"updated_at":"2025-01-02 10:00:00", -"user":{ -"created_at":"2024-12-31 09:00:00", -"email":"user@example.com", -"emailVerified_at":"0001-01-01T00:00:00Z", -"id":42, -"name":"User Name", -"phone":"1234567890", -"phoneVerified_at":"0001-01-01T00:00:00Z", -"updated_at":""} -}}, -"status": "Success" + require.JSONEq(t, `{ + "data": [ + { + "id": 1, + "title": "Label Title", + "color": "#FF0000", + "created_at": "2025-01-01 10:00:00", + "updated_at": "2025-01-02 10:00:00", + "user": { + "id": 42, + "name": "User Name", + "email": "user@example.com", + "phone": "1234567890", + "emailVerified_at": "0001-01-01T00:00:00Z", + "phoneVerified_at": "0001-01-01T00:00:00Z", + "created_at": "2024-12-31 09:00:00", + "updated_at": "" + } + } + ], + "status": "Success" }`, rr.Body.String()) require.NoError(t, mock.ExpectationsWereMet()) } From bc8831452932ea12d0e9f8d41d65acba63724039 Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Tue, 1 Jul 2025 22:06:10 +0330 Subject: [PATCH 09/14] Update label_handler_test.go --- internal/handlers/label_handler_test.go | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/internal/handlers/label_handler_test.go b/internal/handlers/label_handler_test.go index 59bf9ba..67d6c86 100644 --- a/internal/handlers/label_handler_test.go +++ b/internal/handlers/label_handler_test.go @@ -8,6 +8,7 @@ import ( "net/http" "net/http/httptest" "regexp" + "strings" "testing" "github.com/DATA-DOG/go-sqlmock" @@ -81,6 +82,49 @@ func TestGetLatestLabels_OK(t *testing.T) { require.NoError(t, mock.ExpectationsWereMet()) } +func TestStoreLabel_OK(t *testing.T) { + // Setup mock DB + db, mock, err := sqlmock.New() + require.NoError(t, err) + defer db.Close() + + // Initialize logger if needed + logger.InitLogger(false) + + // Prepare expected query and arguments + mock.ExpectExec(regexp.QuoteMeta("INSERT INTO labels (title, color, user_id) VALUES (?, ?, ?)")). + WithArgs("Test Label", "#FF00FF", float64(1)). + WillReturnResult(sqlmock.NewResult(1, 1)) + + // Setup DBHandler with mock DB + h := &DBHandler{DB: db} + + // Create JSON request body + body := `{"title":"Test Label","color":"#FF00FF"}` + + req := httptest.NewRequest(http.MethodPost, "/labels", strings.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + + // Add userID context (matching your GetUserIdFromContext) + req = callContext(req) + + // Record response + rr := httptest.NewRecorder() + + // Call handler + h.StoreLabel(rr, req) + + // Check response code + require.Equal(t, http.StatusOK, rr.Code) + + // Check response body JSON + expected := `{"data":{"message":"The label store successfully."}, "status":"Success"}` + require.JSONEq(t, expected, rr.Body.String()) + + // Ensure all expectations were met + require.NoError(t, mock.ExpectationsWereMet()) +} + func TestDeleteLabel_OK(t *testing.T) { // Mock DB db, mock, err := sqlmock.New() From 07e220743495b8821c6aabd6b5159cc90f00a960 Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Tue, 1 Jul 2025 22:09:21 +0330 Subject: [PATCH 10/14] Create handler_test.go --- internal/handlers/handler_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 internal/handlers/handler_test.go diff --git a/internal/handlers/handler_test.go b/internal/handlers/handler_test.go new file mode 100644 index 0000000..53c0168 --- /dev/null +++ b/internal/handlers/handler_test.go @@ -0,0 +1,27 @@ +package handlers + +import ( + "github.com/DATA-DOG/go-sqlmock" + "github.com/redis/go-redis/v9" + "github.com/stretchr/testify/require" + "testing" +) + +func TestNewDBHandler(t *testing.T) { + // Create a mock sql.DB + db, _, err := sqlmock.New() + require.NoError(t, err) + defer db.Close() + + // Create a mock redis client (no need to mock connection for this simple test) + redisClient := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + }) + + // Call constructor + handler := NewDBHandler(db, redisClient) + + // Assertions + require.NotNil(t, handler) + require.Equal(t, db, handler.DB) +} From 608248b52e6baf6f771e4b68f723faef55ff6fad Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Tue, 1 Jul 2025 22:49:56 +0330 Subject: [PATCH 11/14] Create task_handler_test.go --- internal/handlers/task_handler_test.go | 1 + 1 file changed, 1 insertion(+) create mode 100644 internal/handlers/task_handler_test.go diff --git a/internal/handlers/task_handler_test.go b/internal/handlers/task_handler_test.go new file mode 100644 index 0000000..5ac8282 --- /dev/null +++ b/internal/handlers/task_handler_test.go @@ -0,0 +1 @@ +package handlers From 02d3ffdcaf9a9526faa79531066419cf4bc7e2a1 Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Tue, 1 Jul 2025 22:50:03 +0330 Subject: [PATCH 12/14] add `TestGetLatestTasks_Success` --- internal/handlers/task_handler_test.go | 77 ++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/internal/handlers/task_handler_test.go b/internal/handlers/task_handler_test.go index 5ac8282..dad2885 100644 --- a/internal/handlers/task_handler_test.go +++ b/internal/handlers/task_handler_test.go @@ -1 +1,78 @@ package handlers + +import ( + "net/http" + "net/http/httptest" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" +) + +func TestGetLatestTasks_Success(t *testing.T) { + // Setup DB mock + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("failed to create mock db: %v", err) + } + defer db.Close() + + handler := &DBHandler{DB: db} + + // Mock rows + rows := sqlmock.NewRows([]string{ + "task_id", "task_title", "task_description", "task_status", "user_id", "label_id", "task_completed_at", "task_created_at", + "user_id", "user_name", "user_email", "user_phone", "user_created_at", + "label_id", "label_title", "label_color", "label_created_at", + }).AddRow( + 1, "Test Task", "A task", "open", 10, 100, "", "2023-01-01", + 10, "Milwad", "milwad@example.com", "123456", "2022-12-01", + 100, "Work", "#ffcc00", "2022-11-01", + ) + + mock.ExpectQuery(regexp.QuoteMeta(` + SELECT + tasks.id AS task_id, + tasks.title AS task_title, + tasks.description AS task_description, + tasks.status AS task_status, + tasks.user_id, + tasks.label_id, + COALESCE(tasks.completed_at, '') AS task_completed_at, + tasks.created_at AS task_created_at, + + users.id AS user_id, + users.name AS user_name, + COALESCE(users.email, '') AS user_email, + COALESCE(users.phone, '') AS user_phone, + users.created_at AS user_created_at, + + labels.id AS label_id, + labels.title AS label_title, + labels.color AS label_color, + labels.created_at AS label_created_at + FROM tasks + JOIN users ON tasks.user_id = users.id + JOIN labels ON tasks.label_id = labels.id + WHERE tasks.user_id = ? +`)).WithArgs(float64(1)).WillReturnRows(rows) + + // Create request + req := httptest.NewRequest(http.MethodGet, "/tasks", nil) + req.Header.Set("Content-Type", "application/json") + + // Mock user id from context + req = callContext(req) + + // Record response + rr := httptest.NewRecorder() + + // Call handler + handler.GetLatestTasks(rr, req) + + // Check status + if rr.Code != http.StatusOK { + t.Errorf("Error: %s", rr.Body) + t.Errorf("expected 200, got %d", rr.Code) + } +} From bcd73cb43c2e81bdeec9dd8fe6524c505f668466 Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Tue, 1 Jul 2025 22:54:42 +0330 Subject: [PATCH 13/14] add `TestStoreTask_Success` --- internal/handlers/task_handler_test.go | 53 ++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/internal/handlers/task_handler_test.go b/internal/handlers/task_handler_test.go index dad2885..bdb6ef8 100644 --- a/internal/handlers/task_handler_test.go +++ b/internal/handlers/task_handler_test.go @@ -1,6 +1,9 @@ package handlers import ( + "bytes" + "encoding/json" + "github.com/milwad-dev/do-it/internal/models" "net/http" "net/http/httptest" "regexp" @@ -76,3 +79,53 @@ func TestGetLatestTasks_Success(t *testing.T) { t.Errorf("expected 200, got %d", rr.Code) } } + +func TestStoreTask_Success(t *testing.T) { + // Setup sqlmock + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("failed to open sqlmock database: %v", err) + } + defer db.Close() + + handler := &DBHandler{DB: db} + + // Prepare input task as JSON + task := models.Task{ + Title: "Test Task", + Description: "Test Description", + Status: "pending", + LabelId: 1, + } + taskJSON, _ := json.Marshal(task) + + // Create request + req := httptest.NewRequest(http.MethodPost, "/tasks", bytes.NewBuffer(taskJSON)) + req.Header.Set("Content-Type", "application/json") + + // Mock user id from context + req = callContext(req) + + // Record response + w := httptest.NewRecorder() + + // Mock the label exists query returning count = 1 + mock.ExpectQuery("SELECT count\\(\\*\\) FROM labels WHERE id = \\? AND user_id = \\?"). + WithArgs(task.LabelId, float64(1)). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1)) + + // Mock the INSERT query + mock.ExpectExec("INSERT INTO tasks"). + WithArgs(task.Title, task.Description, task.Status, task.LabelId, float64(1)). + WillReturnResult(sqlmock.NewResult(1, 1)) + + // Call the function + handler.StoreTask(w, req) + + resp := w.Result() + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Errorf("expected status 200 OK but got %d", resp.StatusCode) + } +} From 84a6bd48f587491d94d55bdb142214fac5edfbbd Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Tue, 1 Jul 2025 22:58:25 +0330 Subject: [PATCH 14/14] add coverage to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 5561aac..a98a44d 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ todo.txt # Logs logs/*.log internal/handlers/logs/*.log + +# Coverage +internal/handlers/coverage