From 4438daa4af6ea45b68dddb2c367bf80c5c673269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20G=C3=B3es?= Date: Thu, 15 Aug 2024 16:15:58 -0300 Subject: [PATCH] Add API tests and update CI wf to start containers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tiago Góes Login dockerhub before pulling images Signed-off-by: Tiago Góes Update DB connection Signed-off-by: Tiago Góes Update DB connection Signed-off-by: Tiago Góes Add debug logs Signed-off-by: Tiago Góes --- .github/workflows/build.yaml | 53 +++- .github/workflows/{deploy.yaml => deploy.off} | 0 Dockerfile | 10 + Dockerfile.db | 12 +- docker-compose.yaml => docker-compose.yml | 9 +- go.mod | 19 +- go.sum | 24 ++ src/cmd/main.go | 1 + src/controller/task_controller.go | 12 +- src/db/conn.go | 17 +- src/integration_tests/main_test.go | 291 ++++++++++++++++++ src/repository/task_repository.go | 13 + src/usecase/task_usecase.go | 8 + 13 files changed, 441 insertions(+), 28 deletions(-) rename .github/workflows/{deploy.yaml => deploy.off} (100%) rename docker-compose.yaml => docker-compose.yml (81%) create mode 100644 src/integration_tests/main_test.go diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b39928b..fd37bd8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -3,27 +3,56 @@ name: Build App on: pull_request: branches: [ "main" ] +env: + SHA: ${{ github.sha }} + POSTGRES_USER: ${{ secrets.POSTGRES_USER }} + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} + POSTGRES_DB: ${{ secrets.POSTGRES_DB }} + POSTGRES_HOST: ${{ secrets.POSTGRES_HOST }} jobs: - build-and-test: + run-api-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.22.5' + - name: Start containers + run: | + docker compose build + docker image ls + docker compose up -d --force-recreate + + - name: Wait for containers + run: | + docker ps + docker logs database + max_attempts=3 + attempt=1 + + while [ $attempt -le $max_attempts ]; do + if curl -s http://localhost:8080/health | grep -q '"status":"OK"'; then + echo "Container is up and healthy!" + break + else + echo "Attempt $attempt/$max_attempts: Waiting for container..." + attempt=$((attempt + 1)) + if [ $attempt -le $max_attempts ]; then + sleep 60 # Wait for 60 seconds before the next attempt + fi + fi + done - - name: Build - run: go build -v ./... + if [ $attempt -gt $max_attempts ]; then + echo "Container failed to become healthy after $max_attempts attempts." + exit 1 + fi - name: Test run: go test -v ./... - build-docker-image: + build-push-docker-image: runs-on: ubuntu-latest - needs: build-and-test + needs: run-api-tests steps: - uses: actions/checkout@v4 @@ -37,11 +66,13 @@ jobs: uses: docker/build-push-action@v6 with: push: true - tags: ${{ secrets.DOCKERHUB_USERNAME }}/todo-api:${{ github.sha }} + tags: ${{ secrets.DOCKERHUB_USERNAME }}/todo-api:${{ env.SHA }} - name: Build and push todo-db uses: docker/build-push-action@v6 with: file: Dockerfile.db push: true - tags: ${{ secrets.DOCKERHUB_USERNAME }}/todo-db:${{ github.sha }} + tags: ${{ secrets.DOCKERHUB_USERNAME }}/todo-db:${{ env.SHA }} + + diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.off similarity index 100% rename from .github/workflows/deploy.yaml rename to .github/workflows/deploy.off diff --git a/Dockerfile b/Dockerfile index 415fd4b..5c4e32c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,15 @@ FROM golang:1.22 +ARG POSTGRES_USER +ARG POSTGRES_PASSWORD +ARG POSTGRES_DB +ARG POSTGRES_HOST + +ENV POSTGRES_USER=$POSTGRES_USER +ENV POSTGRES_PASSWORD=$POSTGRES_PASSWORD +ENV POSTGRES_DB=$POSTGRES_DB +ENV POSTGRES_HOST=$POSTGRES_HOST + WORKDIR /app COPY . /app diff --git a/Dockerfile.db b/Dockerfile.db index 2f5763b..60ea5bf 100644 --- a/Dockerfile.db +++ b/Dockerfile.db @@ -1,8 +1,14 @@ FROM postgres -ENV POSTGRES_USER=postgres -ENV POSTGRES_PASSWORD=1234 -ENV POSTGRES_DB=postgres +ARG POSTGRES_USER +ARG POSTGRES_PASSWORD +ARG POSTGRES_DB +ARG POSTGRES_HOST + +ENV POSTGRES_USER=$POSTGRES_USER +ENV POSTGRES_PASSWORD=$POSTGRES_PASSWORD +ENV POSTGRES_DB=$POSTGRES_DB +ENV POSTGRES_HOST=$POSTGRES_HOST # Executing SQL file on startup RUN chmod 777 -R /docker-entrypoint-initdb.d/ diff --git a/docker-compose.yaml b/docker-compose.yml similarity index 81% rename from docker-compose.yaml rename to docker-compose.yml index d4b5d11..05f4155 100644 --- a/docker-compose.yaml +++ b/docker-compose.yml @@ -6,11 +6,15 @@ services: context: . dockerfile: Dockerfile restart: always + container_name: api ports: - "8080:8080" environment: - DATABASE_URL: postgres://postgres:1234@db:5432/postgres?sslmode=disable - GIN_MODE: release + POSTGRES_PASSWORD: 1234 + POSTGRES_USER: postgres + POSTGRES_DB: postgres + POSTGRES_HOST: db + GIN_MODE: debug depends_on: - db networks: @@ -33,6 +37,7 @@ services: POSTGRES_PASSWORD: 1234 POSTGRES_USER: postgres POSTGRES_DB: postgres + POSTGRES_HOST: db networks: - todo-network diff --git a/go.mod b/go.mod index c943602..703aae6 100644 --- a/go.mod +++ b/go.mod @@ -13,26 +13,37 @@ require ( github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/onsi/ginkgo/v2 v2.20.0 // indirect + github.com/onsi/gomega v1.34.1 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/crypto v0.23.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/tools v0.24.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 15302b1..b023e29 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -25,11 +27,17 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -47,6 +55,10 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/onsi/ginkgo/v2 v2.20.0 h1:PE84V2mHqoT1sglvHc8ZdQtPcwmvvt29WLEEO3xmdZw= +github.com/onsi/ginkgo/v2 v2.20.0/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -72,14 +84,26 @@ golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= diff --git a/src/cmd/main.go b/src/cmd/main.go index 6621f54..995e939 100644 --- a/src/cmd/main.go +++ b/src/cmd/main.go @@ -30,6 +30,7 @@ func main() { server.POST("/task", TaskController.CreateTask) server.PUT("/task/:id", TaskController.UpdateTask) server.DELETE("/task/:id", TaskController.DeleteTask) + server.DELETE("/tasks", TaskController.DeleteTasks) server.GET("/health", func(ctx *gin.Context) { ctx.JSON(200, gin.H{ diff --git a/src/controller/task_controller.go b/src/controller/task_controller.go index cc291dc..c67fe21 100644 --- a/src/controller/task_controller.go +++ b/src/controller/task_controller.go @@ -44,7 +44,7 @@ func (p *TaskController) CreateTask(ctx *gin.Context) { var task model.Task err := ctx.BindJSON(&task) if err != nil { - ctx.JSON(http.StatusBadRequest, err) + ctx.JSON(http.StatusBadRequest, err.Error()) return } @@ -75,6 +75,16 @@ func (p *TaskController) DeleteTask(ctx *gin.Context) { ctx.JSON(http.StatusNoContent, model.Task{}) } +func (p *TaskController) DeleteTasks(ctx *gin.Context) { + err := p.TaskUseCase.DeleteTasks() + if err != nil { + ctx.JSON(http.StatusInternalServerError, err) + return + } + + ctx.JSON(http.StatusNoContent, model.Task{}) +} + func (p *TaskController) UpdateTask(ctx *gin.Context) { var newTask = model.Task{} id := ctx.Params.ByName("id") diff --git a/src/db/conn.go b/src/db/conn.go index 00244df..f48ffbc 100644 --- a/src/db/conn.go +++ b/src/db/conn.go @@ -4,19 +4,24 @@ import ( "database/sql" "fmt" "log" - "os" _ "github.com/lib/pq" ) func ConnectDB() (*sql.DB, error) { - databaseURL := os.Getenv("DATABASE_URL") - if databaseURL == "" { + POSTGRES_PASSWORD := "1234" //os.Getenv("POSTGRES_PASSWORD") + POSTGRES_USER := "postgres" // os.Getenv("POSTGRES_USER") + POSTGRES_DB := "postgres" // os.Getenv("POSTGRES_DB") + POSTGRES_HOST := "db" // os.Getenv("POSTGRES_HOST") + + DATABASE_URL := fmt.Sprintf("postgres://%s:%s@%s:5432/%s?sslmode=disable", POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_HOST, POSTGRES_DB) + + if DATABASE_URL == "" { log.Fatal("DATABASE_URL is not set") } - fmt.Print(databaseURL) - db, err := sql.Open("postgres", databaseURL) + fmt.Print(DATABASE_URL) + db, err := sql.Open("postgres", DATABASE_URL) if err != nil { panic(err) @@ -27,7 +32,5 @@ func ConnectDB() (*sql.DB, error) { panic(err) } - // fmt.Printf("Connected to %s", dbname) - return db, nil } diff --git a/src/integration_tests/main_test.go b/src/integration_tests/main_test.go new file mode 100644 index 0000000..b19ca3c --- /dev/null +++ b/src/integration_tests/main_test.go @@ -0,0 +1,291 @@ +package integrationtests + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "testing" + "time" + "todo-api/src/model" + + "github.com/stretchr/testify/assert" +) + +var jsonPayload = map[string]any{ + "title": "Study golang", + "description": "super cool", + "created_at": time.Now(), + "is_completed": false, + "reward_in_sats": 0, +} + +func TestHealth(t *testing.T) { + resp, err := http.Get("http://localhost:8080/health") + if err != nil { + t.Fatalf("Failed to send request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected status code 200, got %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("Failed to read response body: %v", err) + } + + var result struct { + Status string `json:"status"` + } + + err = json.Unmarshal(body, &result) + if err != nil { + t.Fatalf("Failed to unmarshal response body: %v", err) + } + + if result.Status != "OK" { + t.Errorf("Expected status to be 'OK', got '%s'", result.Status) + } +} + +func TestCreateTask(t *testing.T) { + // Arrange + cleanDB(t) + + // Act + task := createTask(t, jsonPayload) + + // Assert + assert.Equal(t, jsonPayload["title"], task.Title) + assert.Equal(t, jsonPayload["description"], task.Description.Val) + assert.Equal(t, jsonPayload["is_completed"], task.IsCompleted) + assert.Equal(t, jsonPayload["reward_in_sats"], task.RewardInSats) +} + +func TestGetEmptyTaskList(t *testing.T) { + // Arrange + cleanDB(t) + + // Act + tasks := getTasks(t) + + // Assert + assert.Empty(t, tasks) +} + +func TestGetTasks(t *testing.T) { + // Arrange + cleanDB(t) + + // Act + task := createTask(t, jsonPayload) + tasks := getTasks(t) + + // Assert + assert.NotEmpty(t, tasks) + assert.Equal(t, 1, len(tasks)) + assert.Equal(t, jsonPayload["title"], task.Title) + assert.Equal(t, jsonPayload["description"], task.Description.Val) + assert.Equal(t, jsonPayload["is_completed"], task.IsCompleted) + assert.Equal(t, jsonPayload["reward_in_sats"], task.RewardInSats) +} + +func TestGetTasksById(t *testing.T) { + // Arrange + cleanDB(t) + + // Act + task := createTask(t, jsonPayload) + returnedTask := getTasksById(t, task.Id) + + // Assert + assert.NotEmpty(t, returnedTask) + assert.Equal(t, task.Id, returnedTask.Id) + assert.Equal(t, jsonPayload["title"], task.Title) + assert.Equal(t, jsonPayload["description"], task.Description.Val) + assert.Equal(t, jsonPayload["is_completed"], task.IsCompleted) + assert.Equal(t, jsonPayload["reward_in_sats"], task.RewardInSats) +} + +func TestUpdateTask(t *testing.T) { + // Arrange + cleanDB(t) + + // Act + task := createTask(t, jsonPayload) + var customJsonPayload = map[string]any{ + "id": task.Id, + "title": "Study kubernets", + "description": "awesome", + "created_at": time.Now(), + "is_completed": false, + "reward_in_sats": 0, + } + updatedTask := updateTask(t, customJsonPayload) + + // Assert + assert.NotEmpty(t, updatedTask) + assert.Equal(t, jsonPayload["title"], task.Title) + assert.Equal(t, jsonPayload["description"], task.Description.Val) + assert.Equal(t, jsonPayload["is_completed"], task.IsCompleted) + assert.Equal(t, jsonPayload["reward_in_sats"], task.RewardInSats) +} + +func TestDeleteTask(t *testing.T) { + // Arrange + cleanDB(t) + + // Act + task := createTask(t, jsonPayload) + deleteTask(t, task.Id) + tasks := getTasks(t) + + // Assert + assert.Empty(t, tasks) +} + +func cleanDB(t *testing.T) { + var client = &http.Client{} + + req, err := http.NewRequest(http.MethodDelete, "http://localhost:8080/tasks", nil) + if err != nil { + t.Fatalf(err.Error()) + } + + resp, err := client.Do(req) + if err != nil { + t.Fatalf(err.Error()) + } + + if resp.StatusCode != http.StatusNoContent { + t.Errorf("Expected status code 204, got %d", resp.StatusCode) + } +} + +func deleteTask(t *testing.T, id int) { + var client = &http.Client{} + + req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://localhost:8080/task/%d", id), nil) + if err != nil { + t.Fatalf(err.Error()) + } + + resp, err := client.Do(req) + if err != nil { + t.Fatalf(err.Error()) + } + + if resp.StatusCode != http.StatusNoContent { + t.Errorf("Expected status code 204, got %d", resp.StatusCode) + } +} + +func createTask(t *testing.T, jsonPayload map[string]any) model.Task { + payload, _ := json.Marshal(jsonPayload) + resp, err := http.Post("http://localhost:8080/task", "application/json", bytes.NewBuffer(payload)) + if err != nil { + t.Fatalf("Failed to send request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusCreated { + t.Errorf("Expected status code 200, got %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("Failed to read response body: %v", err) + } + + var task model.Task + + err = json.Unmarshal(body, &task) + if err != nil { + t.Fatalf("Failed to unmarshal response body: %v", err) + } + return task +} + +func getTasks(t *testing.T) []model.Task { + resp, err := http.Get("http://localhost:8080/tasks") + if err != nil { + t.Fatalf("Failed to send request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected status code 200, got %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("Failed to read response body: %v", err) + } + + var tasks []model.Task + + err = json.Unmarshal(body, &tasks) + if err != nil { + t.Fatalf("Failed to unmarshal response body: %v", err) + } + + return tasks +} + +func getTasksById(t *testing.T, id int) model.Task { + resp, err := http.Get(fmt.Sprintf("http://localhost:8080/task/%d", id)) + if err != nil { + t.Fatalf("Failed to send request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected status code 200, got %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("Failed to read response body: %v", err) + } + + var task model.Task + + err = json.Unmarshal(body, &task) + if err != nil { + t.Fatalf("Failed to unmarshal response body: %v", err) + } + + return task +} + +func updateTask(t *testing.T, jsonPayload map[string]any) model.Task { + client := &http.Client{} + + payload, _ := json.Marshal(jsonPayload) + req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("http://localhost:8080/task/%d", jsonPayload["id"]), bytes.NewBuffer(payload)) + resp, err := client.Do(req) + if err != nil { + t.Fatalf("Failed to send request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected status code 200, got %d", resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + t.Fatalf("Failed to read response body: %v", err) + } + + var task model.Task + + err = json.Unmarshal(body, &task) + if err != nil { + t.Fatalf("Failed to unmarshal response body: %v", err) + } + return task +} diff --git a/src/repository/task_repository.go b/src/repository/task_repository.go index cfbbad7..76e560b 100644 --- a/src/repository/task_repository.go +++ b/src/repository/task_repository.go @@ -122,6 +122,19 @@ func (pr *TaskRepository) DeleteTask(id int) error { return nil } +func (pr *TaskRepository) DeleteTasks() error { + query := fmt.Sprintf("DELETE FROM task") + + rows, err := pr.connection.Query(query) + if err != nil { + fmt.Println(err) + return err + } + + fmt.Print(rows) + return nil +} + func (pr *TaskRepository) UpdateTask(task model.Task) (int, error) { var customUpdatedAt *time.Time diff --git a/src/usecase/task_usecase.go b/src/usecase/task_usecase.go index 8fdc7a4..11b8c7d 100644 --- a/src/usecase/task_usecase.go +++ b/src/usecase/task_usecase.go @@ -44,6 +44,14 @@ func (pu *TaskUseCase) DeleteTask(id int) error { return nil } +func (pu *TaskUseCase) DeleteTasks() error { + err := pu.repository.DeleteTasks() + if err != nil { + return err + } + return nil +} + func (pu *TaskUseCase) UpdateTask(task model.Task) (int, error) { id, err := pu.repository.UpdateTask(task) if err != nil {