diff --git a/.gitignore b/.gitignore index 52e91155..decf6729 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ components/mdz/bin/mdz .idea *.iml components/ledger/_docker-compose.yml +coverage.out diff --git a/Makefile b/Makefile index 3bd5987a..26bf2388 100644 --- a/Makefile +++ b/Makefile @@ -70,14 +70,6 @@ test: fi go test -v ./... ./... -cover: - @echo "$(BLUE)Generating test coverage...$(NC)" - @if ! command -v go >/dev/null 2>&1; then \ - echo "$(RED)Error: go is not installed$(NC)"; \ - exit 1; \ - fi - go test -cover ./... - lint: @echo "$(BLUE)Running linter and performance checks...$(NC)" ./make.sh "lint" @@ -162,6 +154,14 @@ tidy: @echo "$(BLUE)Running go mod tidy...$(NC)" go mod tidy +cover: + @echo -e "$(BLUE)Generating test coverage...$(NC)" + @if ! command -v go >/dev/null 2>&1; then \ + echo "$(RED)Error: go is not installed$(NC)"; \ + exit 1; \ + fi + @sh ./scripts/coverage.sh + generate-docs-all: @echo "$(BLUE)Executing command to generate swagger...$(NC)" $(MAKE) -C $(LEDGER_DIR) generate-docs && \ diff --git a/components/ledger/.air.toml b/components/ledger/.air.toml index 06b5af67..31c01bfe 100644 --- a/components/ledger/.air.toml +++ b/components/ledger/.air.toml @@ -52,4 +52,4 @@ runner = "green" [misc] # Delete tmp directory on exit -clean_on_exit = true \ No newline at end of file +clean_on_exit = true diff --git a/components/ledger/internal/bootstrap/servergRPC.go b/components/ledger/internal/bootstrap/servergRPC.go index 0a98bd32..5e383107 100644 --- a/components/ledger/internal/bootstrap/servergRPC.go +++ b/components/ledger/internal/bootstrap/servergRPC.go @@ -1,13 +1,13 @@ package bootstrap import ( - "google.golang.org/grpc" "net" + "google.golang.org/grpc" + "github.com/LerianStudio/midaz/pkg" "github.com/LerianStudio/midaz/pkg/mlog" "github.com/LerianStudio/midaz/pkg/mopentelemetry" - "github.com/pkg/errors" ) diff --git a/components/transaction/internal/adapters/http/in/transaction.go b/components/transaction/internal/adapters/http/in/transaction.go index db9df987..1f09434f 100644 --- a/components/transaction/internal/adapters/http/in/transaction.go +++ b/components/transaction/internal/adapters/http/in/transaction.go @@ -2,9 +2,10 @@ package in import ( "context" + "reflect" + "go.mongodb.org/mongo-driver/bson" "go.opentelemetry.io/otel/trace" - "reflect" "github.com/LerianStudio/midaz/components/transaction/internal/adapters/postgres/operation" "github.com/LerianStudio/midaz/components/transaction/internal/adapters/postgres/transaction" diff --git a/go.mod b/go.mod index 81ff993e..494ae90d 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,8 @@ require ( require ( github.com/KyleBanks/depth v1.2.1 // indirect + github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect + github.com/alicebob/miniredis/v2 v2.33.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect @@ -65,6 +67,8 @@ require ( github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-redis/redis/v8 v8.11.5 // indirect + github.com/go-redis/redismock/v8 v8.11.5 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect @@ -78,7 +82,9 @@ require ( github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.15.2 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect + github.com/yuin/gopher-lua v1.1.1 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect diff --git a/go.sum b/go.sum index baca38e7..326e7312 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,10 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDe github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= +github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUiji6lZrA= +github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= @@ -33,6 +37,7 @@ github.com/casdoor/casdoor-go-sdk v1.3.0 h1:iUZKsrNUkhtAoyitFIFw3e6TchctAdoxmVgL github.com/casdoor/casdoor-go-sdk v1.3.0/go.mod h1:cMnkCQJgMYpgAlgEx8reSt1AVaDIQLcJ1zk5pzBaz+4= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= @@ -45,6 +50,9 @@ github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSe github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/corpix/uarand v0.0.0-20170723150923-031be390f409 h1:9A+mfQmwzZ6KwUXPc8nHxFtKgn9VIvO3gXAOspIcE3s= github.com/corpix/uarand v0.0.0-20170723150923-031be390f409/go.mod h1:JSm890tOkDN+M1jqN8pUGDKnzJrsVbJwSMHBY4zwz7M= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -74,6 +82,8 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -100,6 +110,11 @@ 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 v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-redis/redismock/v8 v8.11.5 h1:RJFIiua58hrBrSpXhnGX3on79AU3S271H4ZhRI1wyVo= +github.com/go-redis/redismock/v8 v8.11.5/go.mod h1:UaAU9dEe1C+eGr+FHV5prCWIt0hafyPWbGMEWE0UWdA= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gofiber/fiber/v2 v2.32.0/go.mod h1:CMy5ZLiXkn6qwthrl03YMyW1NLfj0rhxz2LKl4t7ZTY= @@ -113,12 +128,26 @@ github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQg github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -129,6 +158,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/icrowley/fake v0.0.0-20240710202011-f797eb4a99c0 h1:ufr2e4uIgz/Ft0RPudkFMyVrp77buvTFxqoDvwNGVSk= github.com/icrowley/fake v0.0.0-20240710202011-f797eb4a99c0/go.mod h1:dQ6TM/OGAe+cMws81eTe4Btv1dKxfPZ2CX+YaAFAPN4= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -210,6 +241,17 @@ github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIf github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= @@ -251,9 +293,11 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -287,8 +331,11 @@ github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6 github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM= go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= go.opentelemetry.io/contrib/bridges/otelzap v0.6.0 h1:j8icMXyyqNf6HGuwlYhniPnVsbJIq7n+WirDu3VAJdQ= @@ -329,21 +376,27 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -352,14 +405,24 @@ golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -384,12 +447,14 @@ golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= @@ -397,6 +462,14 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -404,12 +477,16 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M= gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/app_mock.go b/pkg/app_mock.go new file mode 100644 index 00000000..61e58998 --- /dev/null +++ b/pkg/app_mock.go @@ -0,0 +1,54 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: /Users/maxwelbm/Workspace/midaz/pkg/app.go +// +// Generated by this command: +// +// mockgen -source=/Users/maxwelbm/Workspace/midaz/pkg/app.go -destination=/Users/maxwelbm/Workspace/midaz/pkg/app_mock.go -package pkg +// + +// Package pkg is a generated GoMock package. +package pkg + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockApp is a mock of App interface. +type MockApp struct { + ctrl *gomock.Controller + recorder *MockAppMockRecorder + isgomock struct{} +} + +// MockAppMockRecorder is the mock recorder for MockApp. +type MockAppMockRecorder struct { + mock *MockApp +} + +// NewMockApp creates a new mock instance. +func NewMockApp(ctrl *gomock.Controller) *MockApp { + mock := &MockApp{ctrl: ctrl} + mock.recorder = &MockAppMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockApp) EXPECT() *MockAppMockRecorder { + return m.recorder +} + +// Run mocks base method. +func (m *MockApp) Run(launcher *Launcher) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Run", launcher) + ret0, _ := ret[0].(error) + return ret0 +} + +// Run indicates an expected call of Run. +func (mr *MockAppMockRecorder) Run(launcher any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockApp)(nil).Run), launcher) +} diff --git a/pkg/app_test.go b/pkg/app_test.go new file mode 100644 index 00000000..fcf1ddee --- /dev/null +++ b/pkg/app_test.go @@ -0,0 +1,59 @@ +package pkg + +import ( + "sync" + "testing" + + "github.com/LerianStudio/midaz/pkg/mlog" + gomock "go.uber.org/mock/gomock" +) + +func TestWithLogger(t *testing.T) { + WithLogger(nil) +} + +func TestRunApp(t *testing.T) { + RunApp("test app", nil) +} + +func TestLauncher_Add(t *testing.T) { + l := &Launcher{ + apps: map[string]App{ + "test": nil, + }, + } + l.Add("test app", nil) +} + +func TestLauncherRun(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockApp := NewMockApp(ctrl) + mockApp2 := NewMockApp(ctrl) + mockLogger := mlog.NewMockLogger(ctrl) + + launcherInstance := &Launcher{ + apps: map[string]App{ + "app1": mockApp, + "app2": mockApp2, + }, + Logger: mockLogger, + wg: &sync.WaitGroup{}, + } + + mockLogger.EXPECT().Infof("Starting %d app(s)\n", 2).Times(1) + mockLogger.EXPECT().Info("--").Times(2) + mockLogger.EXPECT().Infof("Launcher: App \u001b[33m(%s)\u001b[0m starting\n", gomock.Any()).Times(2) + mockLogger.EXPECT().Infof("Launcher: App (%s) finished\n", gomock.Any()).Times(2) + mockLogger.EXPECT().Info("Launcher: Terminated").Times(1) + + mockApp.EXPECT().Run(launcherInstance).Return(nil).Times(1) + mockApp2.EXPECT().Run(launcherInstance).Return(nil).Times(1) + + launcherInstance.Run() +} + +func TestNewLauncher(t *testing.T) { + t.Log(NewLauncher(func(l *Launcher) {})) +} diff --git a/pkg/context.go b/pkg/context.go index ef9d72bd..9cd304da 100644 --- a/pkg/context.go +++ b/pkg/context.go @@ -2,6 +2,7 @@ package pkg import ( "context" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" diff --git a/pkg/context_test.go b/pkg/context_test.go new file mode 100644 index 00000000..afdeb1c2 --- /dev/null +++ b/pkg/context_test.go @@ -0,0 +1,30 @@ +package pkg + +import ( + "context" + "testing" +) + +func TestNewLoggerFromContext(t *testing.T) { + t.Log(NewLoggerFromContext(context.Background())) +} + +func TestContextWithLogger(t *testing.T) { + t.Log(ContextWithLogger(context.Background(), nil)) +} + +func TestNewTracerFromContext(t *testing.T) { + t.Log(NewTracerFromContext(context.Background())) +} + +func TestContextWithTracer(t *testing.T) { + t.Log(ContextWithTracer(context.Background(), nil)) +} + +func TestContextWithMidazID(t *testing.T) { + t.Log(ContextWithMidazID(context.Background(), "")) +} + +func TestNewMidazIDFromContext(t *testing.T) { + t.Log(NewMidazIDFromContext(context.Background())) +} diff --git a/pkg/errors.go b/pkg/errors.go index 66e3868d..eefcb35e 100644 --- a/pkg/errors.go +++ b/pkg/errors.go @@ -300,13 +300,13 @@ func ValidateBusinessError(err error, entityType string, args ...any) error { EntityType: entityType, Code: constant.ErrDuplicateLedger.Error(), Title: "Duplicate Ledger Error", - Message: fmt.Sprintf("A ledger with the name %s already exists in the division %s. Please rename the ledger or choose a different division to attach it to.", args...), + Message: fmt.Sprintf("A ledger with the name %v already exists in the division %v. Please rename the ledger or choose a different division to attach it to.", args...), }, constant.ErrLedgerNameConflict: EntityConflictError{ EntityType: entityType, Code: constant.ErrLedgerNameConflict.Error(), Title: "Ledger Name Conflict", - Message: fmt.Sprintf("A ledger named %s already exists in your organization. Please rename the ledger, or if you want to use the same name, consider creating a new ledger for a different division.", args...), + Message: fmt.Sprintf("A ledger named %v already exists in your organization. Please rename the ledger, or if you want to use the same name, consider creating a new ledger for a different division.", args...), }, constant.ErrAssetNameOrCodeDuplicate: EntityConflictError{ EntityType: entityType, @@ -385,7 +385,7 @@ func ValidateBusinessError(err error, entityType string, args ...any) error { EntityType: entityType, Code: constant.ErrDuplicateProductName.Error(), Title: "Duplicate Product Name Error", - Message: fmt.Sprintf("A product with the name %s already exists for this ledger ID %s. Please try again with a different ledger or name.", args...), + Message: fmt.Sprintf("A product with the name %v already exists for this ledger ID %v. Please try again with a different ledger or name.", args...), }, constant.ErrBalanceRemainingDeletion: UnprocessableOperationError{ EntityType: entityType, @@ -415,25 +415,25 @@ func ValidateBusinessError(err error, entityType string, args ...any) error { EntityType: entityType, Code: constant.ErrAliasUnavailability.Error(), Title: "Alias Unavailability Error", - Message: fmt.Sprintf("The alias %s is already in use. Please choose a different alias and try again.", args...), + Message: fmt.Sprintf("The alias %v is already in use. Please choose a different alias and try again.", args...), }, constant.ErrParentTransactionIDNotFound: EntityNotFoundError{ EntityType: entityType, Code: constant.ErrParentTransactionIDNotFound.Error(), Title: "Parent Transaction ID Not Found", - Message: fmt.Sprintf("The parentTransactionId %s does not correspond to any existing transaction. Please review the ID and try again.", args...), + Message: fmt.Sprintf("The parentTransactionId %v does not correspond to any existing transaction. Please review the ID and try again.", args...), }, constant.ErrImmutableField: ValidationError{ EntityType: entityType, Code: constant.ErrImmutableField.Error(), Title: "Immutable Field Error", - Message: fmt.Sprintf("The %s field cannot be modified. Please remove this field from your request and try again.", args...), + Message: fmt.Sprintf("The %v field cannot be modified. Please remove this field from your request and try again.", args...), }, constant.ErrTransactionTimingRestriction: UnprocessableOperationError{ EntityType: entityType, Code: constant.ErrTransactionTimingRestriction.Error(), Title: "Transaction Timing Restriction", - Message: fmt.Sprintf("You can only perform another transaction using %s of %f from %s to %s after %s. Please wait until the specified time to try again.", args...), + Message: fmt.Sprintf("You can only perform another transaction using %v of %f from %v to %v after %v. Please wait until the specified time to try again.", args...), }, constant.ErrAccountStatusTransactionRestriction: ValidationError{ EntityType: entityType, @@ -445,25 +445,25 @@ func ValidateBusinessError(err error, entityType string, args ...any) error { EntityType: entityType, Code: constant.ErrInsufficientAccountBalance.Error(), Title: "Insufficient Account Balance Error", - Message: fmt.Sprintf("The account %s does not have sufficient balance. Please try again with an amount that is less than or equal to the available balance.", args...), + Message: fmt.Sprintf("The account %v does not have sufficient balance. Please try again with an amount that is less than or equal to the available balance.", args...), }, constant.ErrTransactionMethodRestriction: ValidationError{ EntityType: entityType, Code: constant.ErrTransactionMethodRestriction.Error(), Title: "Transaction Method Restriction", - Message: fmt.Sprintf("Transactions involving %s are not permitted for the specified source and/or destination. Please try again using accounts that allow transactions with %s.", args...), + Message: fmt.Sprintf("Transactions involving %v are not permitted for the specified source and/or destination. Please try again using accounts that allow transactions with %v.", args...), }, constant.ErrDuplicateTransactionTemplateCode: EntityConflictError{ EntityType: entityType, Code: constant.ErrDuplicateTransactionTemplateCode.Error(), Title: "Duplicate Transaction Template Code Error", - Message: fmt.Sprintf("A transaction template with the code %s already exists for your ledger. Please use a different code and try again.", args...), + Message: fmt.Sprintf("A transaction template with the code %v already exists for your ledger. Please use a different code and try again.", args...), }, constant.ErrDuplicateAssetPair: EntityConflictError{ EntityType: entityType, Code: constant.ErrDuplicateAssetPair.Error(), Title: "Duplicate Asset Pair Error", - Message: fmt.Sprintf("A pair for the assets %s%s already exists with the ID %s. Please update the existing entry instead of creating a new one.", args...), + Message: fmt.Sprintf("A pair for the assets %v%v already exists with the ID %v. Please update the existing entry instead of creating a new one.", args...), }, constant.ErrInvalidParentAccountID: ValidationError{ EntityType: entityType, @@ -481,7 +481,7 @@ func ValidateBusinessError(err error, entityType string, args ...any) error { EntityType: entityType, Code: constant.ErrChartTypeNotFound.Error(), Title: "Chart Type Not Found", - Message: fmt.Sprintf("The chart type %s does not exist. Please provide a valid chart type and refer to the documentation if you have any questions.", args...), + Message: fmt.Sprintf("The chart type %v does not exist. Please provide a valid chart type and refer to the documentation if you have any questions.", args...), }, constant.ErrInvalidCountryCode: ValidationError{ EntityType: entityType, @@ -571,25 +571,25 @@ func ValidateBusinessError(err error, entityType string, args ...any) error { EntityType: entityType, Code: constant.ErrInvalidDSLFileFormat.Error(), Title: "Invalid DSL File Format", - Message: fmt.Sprintf("The submitted DSL file %s is in an incorrect format. Please ensure that the file follows the expected structure and syntax.", args...), + Message: fmt.Sprintf("The submitted DSL file %v is in an incorrect format. Please ensure that the file follows the expected structure and syntax.", args...), }, constant.ErrEmptyDSLFile: ValidationError{ EntityType: entityType, Code: constant.ErrEmptyDSLFile.Error(), Title: "Empty DSL File", - Message: fmt.Sprintf("The submitted DSL file %s is empty. Please provide a valid file with content.", args...), + Message: fmt.Sprintf("The submitted DSL file %v is empty. Please provide a valid file with content.", args...), }, constant.ErrMetadataKeyLengthExceeded: ValidationError{ EntityType: entityType, Code: constant.ErrMetadataKeyLengthExceeded.Error(), Title: "Metadata Key Length Exceeded", - Message: fmt.Sprintf("The metadata key %s exceeds the maximum allowed length of %s characters. Please use a shorter key.", args...), + Message: fmt.Sprintf("The metadata key %v exceeds the maximum allowed length of %v characters. Please use a shorter key.", args...), }, constant.ErrMetadataValueLengthExceeded: ValidationError{ EntityType: entityType, Code: constant.ErrMetadataValueLengthExceeded.Error(), Title: "Metadata Value Length Exceeded", - Message: fmt.Sprintf("The metadata value %s exceeds the maximum allowed length of %s characters. Please use a shorter value.", args...), + Message: fmt.Sprintf("The metadata value %v exceeds the maximum allowed length of %v characters. Please use a shorter value.", args...), }, constant.ErrAccountIDNotFound: EntityNotFoundError{ EntityType: entityType, @@ -667,7 +667,7 @@ func ValidateBusinessError(err error, entityType string, args ...any) error { EntityType: entityType, Code: constant.ErrInvalidPathParameter.Error(), Title: "Invalid Path Parameter", - Message: fmt.Sprintf("One or more path parameters are in an incorrect format. Please check the following parameters %s and ensure they meet the required format before trying again.", args), + Message: fmt.Sprintf("One or more path parameters are in an incorrect format. Please check the following parameters %v and ensure they meet the required format before trying again.", args), }, constant.ErrInvalidAccountType: ValidationError{ EntityType: entityType, @@ -679,7 +679,7 @@ func ValidateBusinessError(err error, entityType string, args ...any) error { EntityType: entityType, Code: constant.ErrInvalidMetadataNesting.Error(), Title: "Invalid Metadata Nesting", - Message: fmt.Sprintf("The metadata object cannot contain nested values. Please ensure that the value %s is not nested and try again.", args...), + Message: fmt.Sprintf("The metadata object cannot contain nested values. Please ensure that the value %v is not nested and try again.", args...), }, constant.ErrOperationIDNotFound: EntityNotFoundError{ EntityType: entityType, @@ -709,7 +709,7 @@ func ValidateBusinessError(err error, entityType string, args ...any) error { EntityType: entityType, Code: constant.ErrInvalidTransactionType.Error(), Title: "Invalid Transaction Type", - Message: fmt.Sprintf("Only one transaction type ('amount', 'share', or 'remaining') must be specified in the '%s' field for each entry. Please review your input and try again.", args...), + Message: fmt.Sprintf("Only one transaction type ('amount', 'share', or 'remaining') must be specified in the '%v' field for each entry. Please review your input and try again.", args...), }, constant.ErrTransactionValueMismatch: ValidationError{ EntityType: entityType, diff --git a/pkg/errors_test.go b/pkg/errors_test.go new file mode 100644 index 00000000..b191b259 --- /dev/null +++ b/pkg/errors_test.go @@ -0,0 +1,266 @@ +package pkg + +import ( + "errors" + "fmt" + "testing" + + "github.com/LerianStudio/midaz/pkg/constant" + "github.com/stretchr/testify/assert" +) + +func TestEntityNotFoundError_Error(t *testing.T) { + tests := []struct { + name string + errorObj EntityNotFoundError + expected string + }{ + { + name: "EntityType is not empty", + errorObj: EntityNotFoundError{ + EntityType: "User", + }, + expected: "Entity User not found", + }, + { + name: "Message is not empty", + errorObj: EntityNotFoundError{ + Message: "Custom error message", + }, + expected: "Custom error message", + }, + { + name: "Message is empty, but Err is set", + errorObj: EntityNotFoundError{ + Err: errors.New("internal error"), + }, + expected: "internal error", + }, + { + name: "Message and EntityType are empty, and Err is nil", + errorObj: EntityNotFoundError{ + Err: nil, + }, + expected: "entity not found", + }, + { + name: "Message is empty and EntityType is empty", + errorObj: EntityNotFoundError{ + EntityType: "", + }, + expected: "entity not found", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.errorObj.Error() + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestValidationError_Error(t *testing.T) { + tests := []struct { + name string + ve ValidationError + expected string + }{ + { + name: "When Code is non-empty", + ve: ValidationError{Code: "400", Message: "Bad Request"}, + expected: "400 - Bad Request", + }, + { + name: "When Code is empty", + ve: ValidationError{Code: "", Message: "Bad Request"}, + expected: "Bad Request", + }, + { + name: "When Code has only spaces", + ve: ValidationError{Code: " ", Message: "Bad Request"}, + expected: "Bad Request", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.ve.Error() + if result != tt.expected { + t.Errorf("expected %q but got %q", tt.expected, result) + } + }) + } +} + +func TestEntityConflictError_Error(t *testing.T) { + tests := []struct { + name string + errorObj EntityConflictError + expected string + }{ + { + name: "Err is not nil and Message is empty", + errorObj: EntityConflictError{ + Err: errors.New("underlying error"), + Message: "", + }, + expected: "underlying error", + }, + { + name: "Message is not empty, Err is nil", + errorObj: EntityConflictError{ + Message: "Conflict occurred", + }, + expected: "Conflict occurred", + }, + { + name: "Message is empty and Err is nil", + errorObj: EntityConflictError{ + Message: "", + }, + expected: "", + }, + { + name: "Err is nil and Message is whitespace", + errorObj: EntityConflictError{ + Message: " ", + }, + expected: " ", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.errorObj.Error() + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestValidateBadRequestFieldsError(t *testing.T) { + tests := []struct { + name string + requiredFields map[string]string + knownInvalidFields map[string]string + unknownFields map[string]any + entityType string + expectedError error + }{ + { + name: "All fields are empty", + requiredFields: map[string]string{}, + knownInvalidFields: map[string]string{}, + unknownFields: map[string]any{}, + entityType: "Entity1", + expectedError: errors.New("expected knownInvalidFields, unknownFields and requiredFields to be non-empty"), + }, + { + name: "Unknown fields present", + requiredFields: map[string]string{}, + knownInvalidFields: map[string]string{}, + unknownFields: map[string]any{"field1": "value1"}, + entityType: "Entity2", + expectedError: ValidationUnknownFieldsError{ + EntityType: "Entity2", + Code: "0053", + Title: "Unexpected Fields in the Request", + Message: "The request body contains more fields than expected. Please send only the allowed fields as per the documentation. The unexpected fields are listed in the fields object.", + Fields: map[string]any{"field1": "value1"}, + }, + }, + { + name: "Required fields missing", + requiredFields: map[string]string{"field1": "value1"}, + knownInvalidFields: map[string]string{}, + unknownFields: map[string]any{}, + entityType: "Entity3", + expectedError: ValidationKnownFieldsError{ + EntityType: "Entity3", + Code: "0009", + Title: "Missing Fields in Request", + Message: "Your request is missing one or more required fields. Please refer to the documentation to ensure all necessary fields are included in your request.", + Fields: map[string]string{"field1": "value1"}, + }, + }, + { + name: "Known invalid fields", + requiredFields: map[string]string{}, + knownInvalidFields: map[string]string{"field2": "value2"}, + unknownFields: map[string]any{}, + entityType: "Entity4", + expectedError: ValidationKnownFieldsError{ + EntityType: "Entity4", + Code: "0047", + Title: "Bad Request", + Message: "The server could not understand the request due to malformed syntax. Please check the listed fields and try again.", + Fields: map[string]string{"field2": "value2"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ValidateBadRequestFieldsError(tt.requiredFields, tt.knownInvalidFields, tt.entityType, tt.unknownFields) + assert.Equal(t, tt.expectedError, result) + }) + } +} + +func TestValidateBusinessError(t *testing.T) { + type args struct { + err error + entityType string + args []any + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "entity conflict error", + args: args{ + err: EntityConflictError{ + EntityType: "entityType", + Code: constant.ErrDuplicateLedger.Error(), + Title: "Duplicate Ledger Error", + Message: fmt.Sprintf("A ledger with the name %s already exists in the division %s. Please rename the ledger or choose a different division to attach it to.", "arg1", "arg2"), + }, + entityType: "entityType", + args: []any{"arg1", "arg2"}, + }, + wantErr: true, + }, + { + name: "transaction value mismatch error", + args: args{ + err: ValidationError{ + EntityType: "entityType", + Code: constant.ErrTransactionValueMismatch.Error(), + Title: "Transaction Value Mismatch", + Message: "The values for the source, the destination, or both do not match the specified transaction amount. Please verify the values and try again.", + }, + entityType: "entityType", + args: []any{"arg1", "arg2"}, + }, + wantErr: true, + }, + { + name: "error not mapped", + args: args{ + err: errors.New("error not mapped"), + entityType: "entityType", + args: []any{"arg1", "arg2"}, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := ValidateBusinessError(tt.args.err, tt.args.entityType, tt.args.args...); (err != nil) != tt.wantErr { + t.Errorf("ValidateBusinessError() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/gold/transaction/error_test.go b/pkg/gold/transaction/error_test.go new file mode 100644 index 00000000..a9443dd4 --- /dev/null +++ b/pkg/gold/transaction/error_test.go @@ -0,0 +1,44 @@ +package transaction + +import ( + "testing" + + "github.com/antlr4-go/antlr/v4" +) + +func TestError_SyntaxError(t *testing.T) { + type fields struct { + DefaultErrorListener *antlr.DefaultErrorListener + Errors []CompileError + Source string + } + type args struct { + recognizer antlr.Recognizer + offendingSymbol any + line int + column int + msg string + e antlr.RecognitionException + } + tests := []struct { + name string + fields fields + args args + }{ + { + name: "fields emptys", + fields: fields{}, + args: args{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := &Error{ + DefaultErrorListener: tt.fields.DefaultErrorListener, + Errors: tt.fields.Errors, + Source: tt.fields.Source, + } + tr.SyntaxError(tt.args.recognizer, tt.args.offendingSymbol, tt.args.line, tt.args.column, tt.args.msg, tt.args.e) + }) + } +} diff --git a/pkg/gold/transaction/model/validations.go b/pkg/gold/transaction/model/validations.go index 2e3ac5c8..fe04059c 100644 --- a/pkg/gold/transaction/model/validations.go +++ b/pkg/gold/transaction/model/validations.go @@ -52,10 +52,7 @@ func ValidateFromToOperation(ft FromTo, validate Responses, acc *a.Account) (Amo balanceAfter := Balance{} if ft.IsFrom { - ba, err := OperateAmounts(validate.From[ft.Account], acc.Balance, cn.DEBIT) - if err != nil { - return amount, balanceAfter, err - } + ba := OperateAmounts(validate.From[ft.Account], acc.Balance, cn.DEBIT) if ba.Available < 0 && acc.Type != "external" { return amount, balanceAfter, pkg.ValidateBusinessError(cn.ErrInsufficientFunds, "ValidateFromToOperation", acc.Alias) @@ -68,11 +65,7 @@ func ValidateFromToOperation(ft FromTo, validate Responses, acc *a.Account) (Amo balanceAfter = ba } else { - ba, err := OperateAmounts(validate.To[ft.Account], acc.Balance, cn.CREDIT) - if err != nil { - return amount, balanceAfter, err - } - + ba := OperateAmounts(validate.To[ft.Account], acc.Balance, cn.CREDIT) amount = Amount{ Value: validate.To[ft.Account].Value, Scale: validate.To[ft.Account].Scale, @@ -91,10 +84,7 @@ func UpdateAccounts(operation string, fromTo map[string]Amount, accounts []*a.Ac for _, acc := range accounts { for key := range fromTo { if acc.Id == key || acc.Alias == key { - b, err := OperateAmounts(fromTo[key], acc.Balance, operation) - if err != nil { - e <- err - } + b := OperateAmounts(fromTo[key], acc.Balance, operation) balance := a.Balance{ Available: float64(b.Available), @@ -212,10 +202,11 @@ func normalize(total, amount, remaining *Amount) { } // OperateAmounts Function to sum or sub two amounts and normalize the scale -func OperateAmounts(amount Amount, balance *a.Balance, operation string) (Balance, error) { - var scale float64 - - var total float64 +func OperateAmounts(amount Amount, balance *a.Balance, operation string) Balance { + var ( + scale float64 + total float64 + ) switch operation { case cn.DEBIT: @@ -246,7 +237,7 @@ func OperateAmounts(amount Amount, balance *a.Balance, operation string) (Balanc Scale: int(scale), } - return blc, nil + return blc } // calculateTotal Calculate total for sources/destinations based on shares, amounts and remains @@ -324,9 +315,10 @@ func ValidateSendSourceAndDistribute(transaction Transaction) (*Responses, error Aliases: make([]string, 0), } - var sourcesTotal int - - var destinationsTotal int + var ( + sourcesTotal int + destinationsTotal int + ) t := make(chan int) ft := make(chan map[string]Amount) diff --git a/pkg/gold/transaction/model/validations_test.go b/pkg/gold/transaction/model/validations_test.go new file mode 100644 index 00000000..3bed103c --- /dev/null +++ b/pkg/gold/transaction/model/validations_test.go @@ -0,0 +1,1009 @@ +package model + +import ( + "math" + "strings" + "testing" + + "github.com/LerianStudio/midaz/pkg" + cn "github.com/LerianStudio/midaz/pkg/constant" + a "github.com/LerianStudio/midaz/pkg/mgrpc/account" + "github.com/stretchr/testify/require" +) + +func TestValidateAccounts(t *testing.T) { + tests := []struct { + name string + validate Responses + accounts []*a.Account + expectedError error + }{ + { + name: "Mismatch in number of accounts", + validate: Responses{ + From: map[string]Amount{ + "account1": {Value: 100, Scale: 2}, + }, + To: map[string]Amount{ + "account2": {Value: 200, Scale: 2}, + }, + }, + accounts: []*a.Account{ + { + Id: "account1", + Alias: "alias1", + AssetCode: cn.DefaultAssetCode, + }, + }, + expectedError: pkg.ValidateBusinessError(cn.ErrAccountIneligibility, "ValidateAccounts"), + }, + { + name: "Invalid asset code", + validate: Responses{ + From: map[string]Amount{ + "account1": {Value: 100, Scale: 2}, + }, + To: map[string]Amount{ + "account2": {Value: 200, Scale: 2}, + }, + }, + accounts: []*a.Account{ + { + Id: "account1", + Alias: "alias1", + AssetCode: "nonDefaultAsset", + }, + { + Id: "account2", + Alias: "alias2", + AssetCode: cn.DefaultAssetCode, + }, + }, + expectedError: pkg.ValidateBusinessError(cn.ErrAssetCodeNotFound, "ValidateAccounts"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateAccounts(tt.validate, tt.accounts) + + if (err != nil && tt.expectedError == nil) || (err == nil && tt.expectedError != nil) { + t.Fatalf("Expected error: %v, got: %v", tt.expectedError, err) + } + + if err != nil && tt.expectedError != nil && err.Error() != tt.expectedError.Error() { + t.Errorf("Expected error message %v, got %v", tt.expectedError.Error(), err.Error()) + } + }) + } +} + +func TestValidateFromToOperation(t *testing.T) { + tests := []struct { + name string + ft FromTo + validate Responses + acc *a.Account + expectedError error + expectedAmount Amount + expectedBalance Balance + }{ + { + name: "Valid debit operation", + ft: FromTo{ + Account: "account1", + IsFrom: true, + }, + validate: Responses{ + From: map[string]Amount{ + "account1": {Value: 100, Scale: 2}, + }, + }, + acc: &a.Account{ + Alias: "account1", + Balance: &a.Balance{Available: 1000, Scale: 2}, + }, + expectedError: nil, + expectedAmount: Amount{Value: 100, Scale: 2}, + expectedBalance: Balance{Available: 900}, + }, + { + name: "Valid credit operation", + ft: FromTo{ + Account: "account2", + IsFrom: false, + }, + validate: Responses{ + To: map[string]Amount{ + "account2": {Value: 200, Scale: 2}, + }, + }, + acc: &a.Account{ + Alias: "account2", + Balance: &a.Balance{Available: 800, Scale: 2}, + }, + expectedError: nil, + expectedAmount: Amount{Value: 200, Scale: 2}, + expectedBalance: Balance{Available: 1000}, + }, + { + name: "Insufficient funds error", + ft: FromTo{ + Account: "account3", + IsFrom: true, + }, + validate: Responses{ + From: map[string]Amount{ + "account3": {Value: 1200, Scale: 2}, + }, + }, + acc: &a.Account{ + Alias: "account3", + Balance: &a.Balance{Available: 1000, Scale: 2}, + }, + expectedError: pkg.ValidateBusinessError(cn.ErrInsufficientFunds, "ValidateFromToOperation", "account3"), + expectedAmount: Amount{}, + expectedBalance: Balance{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + amount, balanceAfter, err := ValidateFromToOperation(tt.ft, tt.validate, tt.acc) + + if (err != nil && tt.expectedError == nil) || (err == nil && tt.expectedError != nil) { + t.Fatalf("Expected error: %v, got: %v", tt.expectedError, err) + } + + if err != nil && tt.expectedError != nil && !strings.Contains(err.Error(), tt.expectedError.Error()) { + t.Errorf("Expected error message %v, got %v", tt.expectedError.Error(), err.Error()) + } + + if amount != tt.expectedAmount { + t.Errorf("Expected amount %+v, got %+v", tt.expectedAmount, amount) + } + + if balanceAfter.Available != tt.expectedBalance.Available { + t.Errorf("Expected balance available %v, got %v", tt.expectedBalance.Available, balanceAfter.Available) + } + }) + } +} + +func TestUpdateAccountsWithError(t *testing.T) { + fromTo := map[string]Amount{ + "account1": {Value: 100, Scale: 2, Asset: "USD"}, + } + + accounts := []*a.Account{ + { + Id: "account1", + Alias: "alias1", + Balance: &a.Balance{ + Available: 1000, + Scale: 2, + OnHold: 0, + }, + Status: &a.Status{ + Code: "active", + Description: "Account is active", + }, + AllowSending: true, + AllowReceiving: true, + }, + } + + resultChan := make(chan []*a.Account) + errorChan := make(chan error, 1) + + go UpdateAccounts("DEBIT", fromTo, accounts, resultChan, errorChan) + + select { + case err := <-errorChan: + if err == nil { + t.Fatalf("Expected mocked operation error, got %v", err) + } + case updatedAccounts := <-resultChan: + if len(updatedAccounts) > 0 { + t.Fatalf("Expected no accounts to be updated, but got %d", len(updatedAccounts)) + } + default: + } +} + +func TestUpdateAccounts(t *testing.T) { + fromTo := map[string]Amount{ + "account1": {Value: 100, Scale: 2, Asset: "USD"}, + "account2": {Value: 200, Scale: 2, Asset: "USD"}, + } + + accounts := []*a.Account{ + { + Id: "account1", + Alias: "alias1", + Balance: &a.Balance{ + Available: 1000, + Scale: 2, + OnHold: 0, + }, + Status: &a.Status{ + Code: "active", + Description: "Account is active", + }, + AllowSending: true, + AllowReceiving: true, + }, + { + Id: "account2", + Alias: "alias2", + Balance: &a.Balance{ + Available: 2000, + Scale: 2, + OnHold: 0, + }, + Status: &a.Status{ + Code: "active", + Description: "Account is active", + }, + AllowSending: true, + AllowReceiving: true, + }, + } + + resultChan := make(chan []*a.Account) + errorChan := make(chan error, 1) + + go UpdateAccounts("DEBIT", fromTo, accounts, resultChan, errorChan) + + updatedAccounts := <-resultChan + + select { + case err := <-errorChan: + t.Fatalf("Unexpected error: %v", err) + default: + } + + if len(updatedAccounts) != len(accounts) { + t.Fatalf("Expected %d updated accounts, got %d", len(accounts), len(updatedAccounts)) + } + + expectedBalances := []float64{900.0, 1800.0} + for i, acc := range updatedAccounts { + if acc.Balance.Available != expectedBalances[i] { + t.Errorf("Expected balance for account %s to be %f, got %f", + acc.Id, expectedBalances[i], acc.Balance.Available) + } + } +} + +func TestFindScale(t *testing.T) { + tests := []struct { + name string + asset string + v float64 + s int + expected Amount + }{ + { + name: "Integer value with scale 0", + asset: "USD", + v: 100.0, + s: 0, + expected: Amount{ + Asset: "USD", + Value: 100, + Scale: 0, + }, + }, + { + name: "Decimal value with no additional scale", + asset: "USD", + v: 123.45, + s: 0, + expected: Amount{ + Asset: "USD", + Value: 12345, + Scale: 2, + }, + }, + { + name: "Decimal value with existing scale", + asset: "USD", + v: 123.45, + s: 1, + expected: Amount{ + Asset: "USD", + Value: 12345, + Scale: 3, + }, + }, + { + name: "Integer value with existing scale", + asset: "USD", + v: 200.0, + s: 1, + expected: Amount{ + Asset: "USD", + Value: 200, + Scale: 1, + }, + }, + { + name: "Large value with decimal", + asset: "BTC", + v: 0.12345678, + s: 2, + expected: Amount{ + Asset: "BTC", + Value: 12345678, + Scale: 10, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := FindScale(tt.asset, tt.v, tt.s) + + if result.Asset != tt.expected.Asset { + t.Errorf("Expected asset %s, got %s", tt.expected.Asset, result.Asset) + } + + if result.Value != tt.expected.Value { + t.Errorf("Expected value %d, got %d", tt.expected.Value, result.Value) + } + + if result.Scale != tt.expected.Scale { + t.Errorf("Expected scale %d, got %d", tt.expected.Scale, result.Scale) + } + }) + } +} + +func TestScale(t *testing.T) { + tests := []struct { + name string + v int + s0 int + s1 int + expected float64 + }{ + { + name: "Same scale", + v: 1000, + s0: 2, + s1: 2, + expected: 1000.0, + }, + { + name: "Scale up", + v: 1000, + s0: 2, + s1: 3, + expected: 10000.0, + }, + { + name: "Scale down", + v: 1000, + s0: 3, + s1: 2, + expected: 100.0, + }, + { + name: "Negative scale difference", + v: 1000, + s0: -1, + s1: 2, + expected: 1e+06, + }, + { + name: "Zero value", + v: 0, + s0: 3, + s1: 2, + expected: 0.0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Scale(tt.v, tt.s0, tt.s1) + if math.Abs(result-tt.expected) > 1e-6 { + t.Errorf("Scale(%d, %d, %d) = %f; want %f", tt.v, tt.s0, tt.s1, result, tt.expected) + } + }) + } +} + +func TestNormalize(t *testing.T) { + tests := []struct { + name string + total Amount + amount Amount + remaining Amount + expected struct { + total Amount + amount Amount + remaining Amount + } + }{ + { + name: "Same scale for total, amount, and remaining", + total: Amount{ + Value: 500, + Scale: 2, + }, + amount: Amount{ + Value: 200, + Scale: 2, + }, + remaining: Amount{ + Value: 1000, + Scale: 2, + }, + expected: struct { + total Amount + amount Amount + remaining Amount + }{ + total: Amount{ + Value: 700, + Scale: 2, + }, + amount: Amount{ + Value: 200, + Scale: 2, + }, + remaining: Amount{ + Value: 800, + Scale: 2, + }, + }, + }, + { + name: "Different scale between total and amount", + total: Amount{ + Value: 500, + Scale: 2, + }, + amount: Amount{ + Value: 200, + Scale: 3, + }, + remaining: Amount{ + Value: 1000, + Scale: 3, + }, + expected: struct { + total Amount + amount Amount + remaining Amount + }{ + total: Amount{ + Value: 5200, + Scale: 3, + }, + amount: Amount{ + Value: 200, + Scale: 3, + }, + remaining: Amount{ + Value: 800, + Scale: 3, + }, + }, + }, + { + name: "Different scale for remaining and amount", + total: Amount{ + Value: 0, + Scale: 2, + }, + amount: Amount{ + Value: 300, + Scale: 3, + }, + remaining: Amount{ + Value: 1500, + Scale: 2, + }, + expected: struct { + total Amount + amount Amount + remaining Amount + }{ + total: Amount{ + Value: 300, + Scale: 3, + }, + amount: Amount{ + Value: 300, + Scale: 3, + }, + remaining: Amount{ + Value: 14700, + Scale: 3, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + normalize(&tt.total, &tt.amount, &tt.remaining) + + if tt.total != tt.expected.total { + t.Errorf("Expected total %+v, got %+v", tt.expected.total, tt.total) + } + + if tt.amount != tt.expected.amount { + t.Errorf("Expected amount %+v, got %+v", tt.expected.amount, tt.amount) + } + + if tt.remaining != tt.expected.remaining { + t.Errorf("Expected remaining %+v, got %+v", tt.expected.remaining, tt.remaining) + } + }) + } +} + +func TestOperateAmounts(t *testing.T) { + tests := []struct { + name string + amount Amount + balance *a.Balance + operation string + expected Balance + shouldFail bool + }{ + { + name: "Debit with matching scale", + amount: Amount{ + Asset: "USD", + Scale: 2, + Value: 100, + }, + balance: &a.Balance{ + Available: 1000, + OnHold: 0, + Scale: 2, + }, + operation: cn.DEBIT, + expected: Balance{ + Available: 900, + OnHold: 0, + Scale: 2, + }, + shouldFail: false, + }, + { + name: "Debit with different scales", + amount: Amount{ + Asset: "USD", + Scale: 3, + Value: 100, + }, + balance: &a.Balance{ + Available: 10000, + OnHold: 0, + Scale: 2, + }, + operation: cn.DEBIT, + expected: Balance{ + Available: 99900, + OnHold: 0, + Scale: 3, + }, + shouldFail: false, + }, + { + name: "Credit with matching scale", + amount: Amount{ + Asset: "USD", + Scale: 2, + Value: 200, + }, + balance: &a.Balance{ + Available: 1000, + OnHold: 0, + Scale: 2, + }, + operation: "CREDIT", + expected: Balance{ + Available: 1200, + OnHold: 0, + Scale: 2, + }, + shouldFail: false, + }, + { + name: "Credit with different scales", + amount: Amount{ + Asset: "USD", + Scale: 3, + Value: 500, + }, + balance: &a.Balance{ + Available: 10000, + OnHold: 0, + Scale: 2, + }, + operation: "CREDIT", + expected: Balance{ + Available: 100500, + OnHold: 0, + Scale: 3, + }, + shouldFail: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := OperateAmounts(tt.amount, tt.balance, tt.operation) + if result.Available != tt.expected.Available || result.Scale != tt.expected.Scale || result.OnHold != tt.expected.OnHold { + t.Errorf("Expected result %+v, got %+v", tt.expected, result) + } + }) + } +} + +func Test_calculateTotal(t *testing.T) { + fromTos := []FromTo{ + { + Account: "@account1", + Share: &Share{ + Percentage: 50, + PercentageOfPercentage: 100, + }, + }, + { + Account: "@account2", + Amount: &Amount{ + Asset: "USD", + Scale: 2, + Value: 5000, + }, + }, + { + Account: "@account3", + Remaining: "remaining", + }, + } + + send := Send{ + Asset: "USD", + Scale: 2, + Value: 10000, + } + + tChan := make(chan int) + ftChan := make(chan map[string]Amount) + sdChan := make(chan []string) + + go calculateTotal(fromTos, send, tChan, ftChan, sdChan) + + ttl := <-tChan + fmto := <-ftChan + sources := <-sdChan + + expectedTotal := 10000 + if ttl != expectedTotal { + t.Errorf("Expected total %d, got %d", expectedTotal, ttl) + } + + expectedFmto := map[string]Amount{ + "@account1": {Asset: "USD", Scale: 2, Value: 5000}, + "@account2": {Asset: "USD", Scale: 2, Value: 5000}, + "@account3": {Asset: "USD", Scale: 2, Value: 0}, + } + if !compareAmountMaps(fmto, expectedFmto) { + t.Errorf("Expected fmto %v, got %v", expectedFmto, fmto) + } + + expectedSources := []string{"@account1", "@account2", "@account3"} + if !compareSlices(sources, expectedSources) { + t.Errorf("Expected sources %v, got %v", expectedSources, sources) + } +} + +func compareAmountMaps(a, b map[string]Amount) bool { + if len(a) != len(b) { + return false + } + for key, valueA := range a { + if valueB, ok := b[key]; !ok || valueA != valueB { + return false + } + } + return true +} + +func TestValidateSendSourceAndDistribute(t *testing.T) { + t.Run("case 01 success", func(t *testing.T) { + mockTransaction := Transaction{ + Description: "Test Transaction", + Code: "00000000-0000-0000-0000-000000000000", + Send: Send{ + Value: 100, + Source: Source{ + From: []FromTo{ + { + Account: "@account1", + Amount: &Amount{Value: 50}, + }, + { + Account: "@account2", + Amount: &Amount{Value: 50}, + }, + }, + }, + }, + Distribute: Distribute{ + To: []FromTo{ + { + Account: "@account3", + Amount: &Amount{Value: 60}, + }, + { + Account: "@account4", + Amount: &Amount{Value: 40}, + }, + }, + }, + } + + expectedResponse := &Responses{ + Total: 100, + From: map[string]Amount{ + "@account1": {Value: 50}, + "@account2": {Value: 50}, + }, + To: map[string]Amount{ + "@account3": {Value: 60}, + "@account4": {Value: 40}, + }, + Sources: []string{"@account1", "@account2"}, + Destinations: []string{"@account3", "@account4"}, + Aliases: []string{"@account1", "@account2", "@account3", "@account4"}, + } + + response, err := ValidateSendSourceAndDistribute(mockTransaction) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + // Verifica os valores da resposta + if response.Total != expectedResponse.Total { + t.Errorf("Expected total %d, got %d", expectedResponse.Total, response.Total) + } + + if !compareMaps(response.From, expectedResponse.From) { + t.Errorf("Expected From %v, got %v", expectedResponse.From, response.From) + } + + if !compareMaps(response.To, expectedResponse.To) { + t.Errorf("Expected To %v, got %v", expectedResponse.To, response.To) + } + + if !compareSlices(response.Sources, expectedResponse.Sources) { + t.Errorf("Expected Sources %v, got %v", expectedResponse.Sources, response.Sources) + } + + if !compareSlices(response.Destinations, expectedResponse.Destinations) { + t.Errorf("Expected Destinations %v, got %v", expectedResponse.Destinations, response.Destinations) + } + + if !compareSlices(response.Aliases, expectedResponse.Aliases) { + t.Errorf("Expected Aliases %v, got %v", expectedResponse.Aliases, response.Aliases) + } + }) + + t.Run("case 02 error", func(t *testing.T) { + mockTransaction := Transaction{ + Description: "Test Transaction", + Code: "00000000-0000-0000-0000-000000000000", + Send: Send{ + Value: 100, + Source: Source{ + From: []FromTo{ + { + Account: "@account1", + Amount: &Amount{Value: 49}, + }, + { + Account: "@account2", + Amount: &Amount{Value: 50}, + }, + }, + }, + }, + Distribute: Distribute{ + To: []FromTo{ + { + Account: "@account3", + Amount: &Amount{Value: 60}, + }, + { + Account: "@account4", + Amount: &Amount{Value: 40}, + }, + }, + }, + } + + _, err := ValidateSendSourceAndDistribute(mockTransaction) + require.Error(t, err) + }) + + t.Run("case 03 error", func(t *testing.T) { + mockTransaction := Transaction{ + Description: "Test Transaction", + Code: "00000000-0000-0000-0000-000000000000", + Send: Send{ + Value: 100, + Source: Source{ + From: []FromTo{ + { + Account: "@account1", + Amount: &Amount{Value: 50}, + }, + { + Account: "@account2", + Amount: &Amount{Value: 50}, + }, + }, + }, + }, + Distribute: Distribute{ + To: []FromTo{ + { + Account: "@account3", + Amount: &Amount{Value: 41}, + }, + { + Account: "@account4", + Amount: &Amount{Value: 40}, + }, + }, + }, + } + + _, err := ValidateSendSourceAndDistribute(mockTransaction) + require.Error(t, err) + }) +} + +func compareMaps(a, b map[string]Amount) bool { + if len(a) != len(b) { + return false + } + for key, valueA := range a { + if valueB, ok := b[key]; !ok || valueA.Value != valueB.Value { + return false + } + } + return true +} + +func compareSlices(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +func TestValidateAccountsFrom(t *testing.T) { + tests := []struct { + name string + key string + account *a.Account + expectError bool + expectedErr error + }{ + { + name: "case 00", + key: "123", + account: &a.Account{ + Id: "124", + Alias: "@external", + AllowSending: true, + AllowReceiving: false, + Balance: &a.Balance{Available: 100}, + }, + expectError: false, + expectedErr: nil, + }, + { + name: "case 01", + key: "123", + account: &a.Account{ + Id: "123", + Alias: "alias123", + AllowSending: false, + Balance: &a.Balance{Available: 100}, + AllowReceiving: true, + }, + expectError: true, + expectedErr: pkg.ValidateBusinessError(cn.ErrAccountStatusTransactionRestriction, "ValidateAccounts"), + }, + { + name: "case 02", + key: "123", + account: &a.Account{ + Id: "124", + Alias: "123", + AllowSending: true, + Balance: &a.Balance{Available: 0}, + AllowReceiving: true, + }, + expectError: true, + expectedErr: pkg.ValidateBusinessError(cn.ErrInsufficientFunds, "ValidateAccounts", "123"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateAccountsFrom(tt.key, tt.account) + if tt.expectError { + if err == nil { + t.Errorf("Expected error, got nil") + } else if !strings.Contains(err.Error(), tt.expectedErr.Error()) { + t.Errorf("Expected error %v, got %v", tt.expectedErr, err) + } + } else { + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + } + }) + } +} + +func Test_validateAccountsTo(t *testing.T) { + tests := []struct { + key string + account *a.Account + expectError bool + }{ + { + key: "123", + account: &a.Account{ + Id: "123", + Alias: "alias123", + AllowReceiving: false, + }, + expectError: true, + }, + { + key: "alias123", + account: &a.Account{ + Id: "456", + Alias: "alias123", + AllowReceiving: true, + }, + expectError: false, + }, + { + key: "789", + account: &a.Account{ + Id: "789", + Alias: "alias789", + AllowReceiving: true, + }, + expectError: true, + }, + } + for _, tt := range tests { + err := validateAccountsTo(tt.key, tt.account) + if (err != nil) != tt.expectError { + t.Errorf("validateAccountsTo(%q, %v) = %v; want error: %v", + tt.key, tt.account, err, tt.expectError) + } + } +} diff --git a/pkg/gold/transaction/parse_test.go b/pkg/gold/transaction/parse_test.go new file mode 100644 index 00000000..fa2c3ddc --- /dev/null +++ b/pkg/gold/transaction/parse_test.go @@ -0,0 +1,51 @@ +package transaction + +import ( + "testing" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string + dsl string + expected any + expectError bool + }{ + { + name: "Empty DSL input", + dsl: "", + expected: nil, + expectError: true, + }, + { + name: "Invalid DSL input", + dsl: "INVALID SYNTAX", + expected: nil, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + if !tt.expectError { + t.Fatalf("Unexpected panic: %v", r) + } + } + }() + + transaction := Parse(tt.dsl) + + if tt.expectError { + if transaction != nil { + t.Errorf("Expected error, got %v", transaction) + } + } else { + if transaction != tt.expected { + t.Errorf("Expected %v, got %v", tt.expected, transaction) + } + } + }) + } +} diff --git a/pkg/mcasdoor/casdoor.go b/pkg/mcasdoor/casdoor.go index 8cf0c70e..633cc937 100644 --- a/pkg/mcasdoor/casdoor.go +++ b/pkg/mcasdoor/casdoor.go @@ -4,10 +4,11 @@ import ( _ "embed" "encoding/json" "errors" - "go.uber.org/zap" "io" "net/http" + "go.uber.org/zap" + "github.com/LerianStudio/midaz/pkg/mlog" "github.com/casdoor/casdoor-go-sdk/casdoorsdk" diff --git a/pkg/mcasdoor/casdoor_test.go b/pkg/mcasdoor/casdoor_test.go new file mode 100644 index 00000000..bcad284b --- /dev/null +++ b/pkg/mcasdoor/casdoor_test.go @@ -0,0 +1,267 @@ +package mcasdoor + +import ( + _ "embed" + "net/http" + "net/http/httptest" + "testing" + + "github.com/LerianStudio/midaz/pkg/mlog" + "github.com/casdoor/casdoor-go-sdk/casdoorsdk" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" +) + +func TestCasdoorConnection_Connect(t *testing.T) { + t.Run("should return error when jwtPKCertificate is empty", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockLogger := mlog.NewMockLogger(ctrl) + + mockLogger.EXPECT(). + Info("Connecting to casdoor..."). + Times(1) + + mockLogger.EXPECT(). + Fatalf( + "public key certificate isn't load. error: %v", + gomock.Any(), + ). + Times(1) + + cc := &CasdoorConnection{ + Logger: mockLogger, + } + + jwtPKCertificate = []byte("") + + err := cc.Connect() + + assert.NotNil(t, err) + assert.EqualError(t, err, "public key certificate isn't load") + }) + + t.Run("should return error when healthCheck fails", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockLogger := mlog.NewMockLogger(ctrl) + + mockLogger.EXPECT(). + Info("Connecting to casdoor..."). + Times(1) + + mockLogger.EXPECT(). + Error("casdoor unhealthy..."). + Times(1) + + mockLogger.EXPECT(). + Fatalf( + "Casdoor.HealthCheck %v", + gomock.Any(), + ). + Times(1) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(`{"status": "error"}`)) + })) + defer server.Close() + + cc := &CasdoorConnection{ + Logger: mockLogger, + Endpoint: server.URL, + ClientID: "test-id", + ClientSecret: "test-secret", + OrganizationName: "org", + ApplicationName: "app", + } + + jwtPKCertificate = []byte("valid-cert") + + err := cc.Connect() + + assert.NotNil(t, err) + assert.EqualError(t, err, "can't connect casdoor") + }) + + t.Run("should connect successfully", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockLogger := mlog.NewMockLogger(ctrl) + + mockLogger.EXPECT(). + Info("Connecting to casdoor..."). + Times(1) + + mockLogger.EXPECT(). + Info("Connected to casdoor ✅ "). + Times(1) + + // Mock do servidor de healthCheck retornando sucesso + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status": "ok"}`)) + })) + defer server.Close() + + cc := &CasdoorConnection{ + Logger: mockLogger, + Endpoint: server.URL, // Usar o servidor mockado + ClientID: "test-id", + ClientSecret: "test-secret", + OrganizationName: "org", + ApplicationName: "app", + } + + // Configurar certificado válido + jwtPKCertificate = []byte("valid-cert") + + // Criar cliente + cc.Client = &casdoorsdk.Client{} + + err := cc.Connect() + + assert.Nil(t, err) + assert.True(t, cc.Connected) + assert.NotNil(t, cc.Client) + }) +} + +func TestCasdoorConnection_GetClient(t *testing.T) { + t.Run("should return error when Connect fails", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockLogger := mlog.NewMockLogger(ctrl) + + originalJwtPKCertificate := jwtPKCertificate + defer func() { jwtPKCertificate = originalJwtPKCertificate }() + jwtPKCertificate = []byte("") + + cc := &CasdoorConnection{ + Logger: mockLogger, + Client: nil, + } + + gomock.InOrder( + mockLogger.EXPECT().Info("Connecting to casdoor...").Times(1), + mockLogger.EXPECT().Fatalf("public key certificate isn't load. error: %v", gomock.Any()).Times(1), + mockLogger.EXPECT().Infof("ERRCONECT %s", gomock.Any()).Times(1), + ) + + client, err := cc.GetClient() + + assert.Nil(t, client) + assert.EqualError(t, err, "public key certificate isn't load") + }) + + t.Run("should return existing client when already connected", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockLogger := mlog.NewMockLogger(ctrl) + + existingClient := &casdoorsdk.Client{} + cc := &CasdoorConnection{ + Logger: mockLogger, + Client: existingClient, + } + + client, err := cc.GetClient() + + assert.Nil(t, err) + assert.Equal(t, existingClient, client) + }) +} + +func TestCasdoorConnection_healthCheck(t *testing.T) { + t.Run("should return false when GET request fails", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockLogger := mlog.NewMockLogger(ctrl) + mockLogger.EXPECT(). + Errorf("failed to make GET request: %v", gomock.Any()). + Times(1) + + cc := &CasdoorConnection{ + Logger: mockLogger, + Endpoint: "http://invalid-url", + } + + result := cc.healthCheck() + assert.False(t, result) + }) + + t.Run("should return false when response JSON is invalid", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockLogger := mlog.NewMockLogger(ctrl) + mockLogger.EXPECT(). + Errorf("failed to unmarshal response: %v", gomock.Any()). + Times(1) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("invalid-json")) + })) + defer server.Close() + + cc := &CasdoorConnection{ + Logger: mockLogger, + Endpoint: server.URL, + } + + result := cc.healthCheck() + assert.False(t, result) + }) + + t.Run("should return false when status is not ok", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockLogger := mlog.NewMockLogger(ctrl) + mockLogger.EXPECT(). + Error("casdoor unhealthy..."). + Times(1) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status": "error"}`)) + })) + defer server.Close() + + cc := &CasdoorConnection{ + Logger: mockLogger, + Endpoint: server.URL, + } + + result := cc.healthCheck() + assert.False(t, result) + }) + + t.Run("should return true when status is ok", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockLogger := mlog.NewMockLogger(ctrl) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status": "ok"}`)) + })) + defer server.Close() + + cc := &CasdoorConnection{ + Logger: mockLogger, + Endpoint: server.URL, + } + + result := cc.healthCheck() + assert.True(t, result) + }) +} diff --git a/pkg/mgrpc/grpc.go b/pkg/mgrpc/grpc.go index 16befcdc..e0062021 100644 --- a/pkg/mgrpc/grpc.go +++ b/pkg/mgrpc/grpc.go @@ -2,11 +2,12 @@ package mgrpc import ( "context" + "log" + "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" gmtdt "google.golang.org/grpc/metadata" - "log" "github.com/LerianStudio/midaz/pkg" "github.com/LerianStudio/midaz/pkg/constant" diff --git a/pkg/mgrpc/grpc_test.go b/pkg/mgrpc/grpc_test.go new file mode 100644 index 00000000..aedc44f2 --- /dev/null +++ b/pkg/mgrpc/grpc_test.go @@ -0,0 +1,113 @@ +package mgrpc + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/LerianStudio/midaz/pkg/constant" + "github.com/LerianStudio/midaz/pkg/mlog" + "github.com/LerianStudio/midaz/pkg/mopentelemetry" + "github.com/stretchr/testify/assert" + + "go.uber.org/mock/gomock" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +func TestGRPCConnection_Connect(t *testing.T) { + t.Run("should connect successfully", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockLogger := mlog.NewMockLogger(ctrl) + + mockLogger.EXPECT(). + Info("Connected to gRPC ✅ "). + Times(1) + + // Mock do servidor gRPC + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + c := &GRPCConnection{ + Addr: server.URL, + Logger: mockLogger, + } + + err := c.Connect() + + assert.Nil(t, err) + assert.NotNil(t, c.Conn) + }) +} + +func TestGRPCConnection_GetNewClient(t *testing.T) { + t.Run("should return existing connection when already connected", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockLogger := mlog.NewMockLogger(ctrl) + + // Conexão simulada + existingConn := &grpc.ClientConn{} + + c := &GRPCConnection{ + Conn: existingConn, + Logger: mockLogger, + } + + conn, err := c.GetNewClient() + + assert.Nil(t, err) + assert.Equal(t, existingConn, conn) + }) + + t.Run("should connect successfully and return connection", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockLogger := mlog.NewMockLogger(ctrl) + + mockLogger.EXPECT(). + Info("Connected to gRPC ✅ "). + Times(1) + + // Mock do servidor gRPC + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + c := &GRPCConnection{ + Addr: server.URL, + Logger: mockLogger, + } + + conn, err := c.GetNewClient() + + assert.Nil(t, err) + assert.NotNil(t, conn) + }) +} + +func TestGRPCConnection_ContextMetadataInjection(t *testing.T) { + t.Run("should inject metadata and return a new context", func(t *testing.T) { + ctx := context.Background() + token := "test-token" + + c := &GRPCConnection{} + newCtx := c.ContextMetadataInjection(ctx, token) + + md, ok := metadata.FromOutgoingContext(newCtx) + assert.True(t, ok, "metadata should exist in context") + + assert.Equal(t, "Bearer "+token, md[constant.MDAuthorization][0]) + + otelCtx := mopentelemetry.ExtractContext(newCtx) + assert.NotNil(t, otelCtx, "context should have OpenTelemetry data") + }) +} diff --git a/pkg/mlog/log_mock.go b/pkg/mlog/log_mock.go new file mode 100644 index 00000000..3fe2d498 --- /dev/null +++ b/pkg/mlog/log_mock.go @@ -0,0 +1,331 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: /Users/maxwelbm/Workspace/midaz/pkg/mlog/log.go +// +// Generated by this command: +// +// mockgen -source=/Users/maxwelbm/Workspace/midaz/pkg/mlog/log.go -destination=/Users/maxwelbm/Workspace/midaz/pkg/mlog/log_mock.go -package mlog +// + +// Package mlog is a generated GoMock package. +package mlog + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockLogger is a mock of Logger interface. +type MockLogger struct { + ctrl *gomock.Controller + recorder *MockLoggerMockRecorder + isgomock struct{} +} + +// MockLoggerMockRecorder is the mock recorder for MockLogger. +type MockLoggerMockRecorder struct { + mock *MockLogger +} + +// NewMockLogger creates a new mock instance. +func NewMockLogger(ctrl *gomock.Controller) *MockLogger { + mock := &MockLogger{ctrl: ctrl} + mock.recorder = &MockLoggerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLogger) EXPECT() *MockLoggerMockRecorder { + return m.recorder +} + +// Debug mocks base method. +func (m *MockLogger) Debug(args ...any) { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Debug", varargs...) +} + +// Debug indicates an expected call of Debug. +func (mr *MockLoggerMockRecorder) Debug(args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockLogger)(nil).Debug), args...) +} + +// Debugf mocks base method. +func (m *MockLogger) Debugf(format string, args ...any) { + m.ctrl.T.Helper() + varargs := []any{format} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Debugf", varargs...) +} + +// Debugf indicates an expected call of Debugf. +func (mr *MockLoggerMockRecorder) Debugf(format any, args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{format}, args...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debugf", reflect.TypeOf((*MockLogger)(nil).Debugf), varargs...) +} + +// Debugln mocks base method. +func (m *MockLogger) Debugln(args ...any) { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Debugln", varargs...) +} + +// Debugln indicates an expected call of Debugln. +func (mr *MockLoggerMockRecorder) Debugln(args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debugln", reflect.TypeOf((*MockLogger)(nil).Debugln), args...) +} + +// Error mocks base method. +func (m *MockLogger) Error(args ...any) { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Error", varargs...) +} + +// Error indicates an expected call of Error. +func (mr *MockLoggerMockRecorder) Error(args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLogger)(nil).Error), args...) +} + +// Errorf mocks base method. +func (m *MockLogger) Errorf(format string, args ...any) { + m.ctrl.T.Helper() + varargs := []any{format} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Errorf", varargs...) +} + +// Errorf indicates an expected call of Errorf. +func (mr *MockLoggerMockRecorder) Errorf(format any, args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{format}, args...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Errorf", reflect.TypeOf((*MockLogger)(nil).Errorf), varargs...) +} + +// Errorln mocks base method. +func (m *MockLogger) Errorln(args ...any) { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Errorln", varargs...) +} + +// Errorln indicates an expected call of Errorln. +func (mr *MockLoggerMockRecorder) Errorln(args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Errorln", reflect.TypeOf((*MockLogger)(nil).Errorln), args...) +} + +// Fatal mocks base method. +func (m *MockLogger) Fatal(args ...any) { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Fatal", varargs...) +} + +// Fatal indicates an expected call of Fatal. +func (mr *MockLoggerMockRecorder) Fatal(args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fatal", reflect.TypeOf((*MockLogger)(nil).Fatal), args...) +} + +// Fatalf mocks base method. +func (m *MockLogger) Fatalf(format string, args ...any) { + m.ctrl.T.Helper() + varargs := []any{format} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Fatalf", varargs...) +} + +// Fatalf indicates an expected call of Fatalf. +func (mr *MockLoggerMockRecorder) Fatalf(format any, args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{format}, args...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fatalf", reflect.TypeOf((*MockLogger)(nil).Fatalf), varargs...) +} + +// Fatalln mocks base method. +func (m *MockLogger) Fatalln(args ...any) { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Fatalln", varargs...) +} + +// Fatalln indicates an expected call of Fatalln. +func (mr *MockLoggerMockRecorder) Fatalln(args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fatalln", reflect.TypeOf((*MockLogger)(nil).Fatalln), args...) +} + +// Info mocks base method. +func (m *MockLogger) Info(args ...any) { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Info", varargs...) +} + +// Info indicates an expected call of Info. +func (mr *MockLoggerMockRecorder) Info(args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLogger)(nil).Info), args...) +} + +// Infof mocks base method. +func (m *MockLogger) Infof(format string, args ...any) { + m.ctrl.T.Helper() + varargs := []any{format} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Infof", varargs...) +} + +// Infof indicates an expected call of Infof. +func (mr *MockLoggerMockRecorder) Infof(format any, args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{format}, args...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Infof", reflect.TypeOf((*MockLogger)(nil).Infof), varargs...) +} + +// Infoln mocks base method. +func (m *MockLogger) Infoln(args ...any) { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Infoln", varargs...) +} + +// Infoln indicates an expected call of Infoln. +func (mr *MockLoggerMockRecorder) Infoln(args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Infoln", reflect.TypeOf((*MockLogger)(nil).Infoln), args...) +} + +// Sync mocks base method. +func (m *MockLogger) Sync() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Sync") + ret0, _ := ret[0].(error) + return ret0 +} + +// Sync indicates an expected call of Sync. +func (mr *MockLoggerMockRecorder) Sync() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sync", reflect.TypeOf((*MockLogger)(nil).Sync)) +} + +// Warn mocks base method. +func (m *MockLogger) Warn(args ...any) { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Warn", varargs...) +} + +// Warn indicates an expected call of Warn. +func (mr *MockLoggerMockRecorder) Warn(args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLogger)(nil).Warn), args...) +} + +// Warnf mocks base method. +func (m *MockLogger) Warnf(format string, args ...any) { + m.ctrl.T.Helper() + varargs := []any{format} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Warnf", varargs...) +} + +// Warnf indicates an expected call of Warnf. +func (mr *MockLoggerMockRecorder) Warnf(format any, args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{format}, args...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warnf", reflect.TypeOf((*MockLogger)(nil).Warnf), varargs...) +} + +// Warnln mocks base method. +func (m *MockLogger) Warnln(args ...any) { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Warnln", varargs...) +} + +// Warnln indicates an expected call of Warnln. +func (mr *MockLoggerMockRecorder) Warnln(args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warnln", reflect.TypeOf((*MockLogger)(nil).Warnln), args...) +} + +// WithDefaultMessageTemplate mocks base method. +func (m *MockLogger) WithDefaultMessageTemplate(message string) Logger { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WithDefaultMessageTemplate", message) + ret0, _ := ret[0].(Logger) + return ret0 +} + +// WithDefaultMessageTemplate indicates an expected call of WithDefaultMessageTemplate. +func (mr *MockLoggerMockRecorder) WithDefaultMessageTemplate(message any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithDefaultMessageTemplate", reflect.TypeOf((*MockLogger)(nil).WithDefaultMessageTemplate), message) +} + +// WithFields mocks base method. +func (m *MockLogger) WithFields(fields ...any) Logger { + m.ctrl.T.Helper() + varargs := []any{} + for _, a := range fields { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "WithFields", varargs...) + ret0, _ := ret[0].(Logger) + return ret0 +} + +// WithFields indicates an expected call of WithFields. +func (mr *MockLoggerMockRecorder) WithFields(fields ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithFields", reflect.TypeOf((*MockLogger)(nil).WithFields), fields...) +} diff --git a/pkg/mmodel/account_test.go b/pkg/mmodel/account_test.go new file mode 100644 index 00000000..a90e25a6 --- /dev/null +++ b/pkg/mmodel/account_test.go @@ -0,0 +1,174 @@ +package mmodel + +import ( + "testing" + "time" + + "github.com/LerianStudio/midaz/components/mdz/pkg/ptr" + proto "github.com/LerianStudio/midaz/pkg/mgrpc/account" +) + +func TestAccount_ToProto(t *testing.T) { + tm := time.Now() + + var timeDel *time.Time = &tm + + tests := []struct { + name string + account *Account + expected *proto.Account + }{ + { + name: "normal case", + account: &Account{ + ID: "1", + Name: "Account 1", + AssetCode: "USD", + OrganizationID: "org-123", + LedgerID: "ledger-456", + Balance: Balance{ + Available: ptr.Float64Ptr(12), + OnHold: ptr.Float64Ptr(12), + Scale: ptr.Float64Ptr(12), + }, + Status: Status{ + Code: "1", + Description: new(string), + }, + AllowSending: new(bool), + AllowReceiving: new(bool), + Type: "type-1", + UpdatedAt: time.Now(), + CreatedAt: time.Now(), + }, + expected: &proto.Account{ + Id: "1", + Name: "Account 1", + AssetCode: "USD", + OrganizationId: "org-123", + LedgerId: "ledger-456", + Balance: &proto.Balance{ + Available: 100, + OnHold: 200, + Scale: 2, + }, + Status: &proto.Status{ + Code: "1", + Description: "Some description", + }, + AllowSending: true, + AllowReceiving: true, + Type: "type-1", + }, + }, + { + name: "normal case is not nil", + account: &Account{ + ID: "1", + Name: "Account 1", + AssetCode: "USD", + OrganizationID: "org-123", + ParentAccountID: ptr.StringPtr("parent"), + LedgerID: "ledger-456", + Balance: Balance{ + Available: ptr.Float64Ptr(12), + OnHold: ptr.Float64Ptr(12), + Scale: ptr.Float64Ptr(12), + }, + Status: Status{ + Code: "1", + Description: new(string), + }, + AllowSending: new(bool), + AllowReceiving: new(bool), + Type: "type-1", + UpdatedAt: time.Now(), + CreatedAt: time.Now(), + DeletedAt: timeDel, + EntityID: ptr.StringPtr("EntityID"), + PortfolioID: ptr.StringPtr("PortfolioID"), + ProductID: ptr.StringPtr("ProductID"), + Alias: ptr.StringPtr("Alias"), + }, + expected: &proto.Account{ + Id: "1", + Name: "Account 1", + AssetCode: "USD", + OrganizationId: "org-123", + LedgerId: "ledger-456", + Balance: &proto.Balance{ + Available: 100, + OnHold: 200, + Scale: 2, + }, + Status: &proto.Status{ + Code: "1", + Description: "Some description", + }, + AllowSending: true, + AllowReceiving: true, + Type: "type-1", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.account.ToProto() + t.Log(result) + }) + } +} + +func TestBalance_IsEmpty(t *testing.T) { + type fields struct { + Available *float64 + OnHold *float64 + Scale *float64 + } + tests := []struct { + name string + fields fields + want bool + }{ + { + name: "case 01", + fields: fields{ + Available: nil, + OnHold: nil, + Scale: nil, + }, + want: true, + }, + { + name: "case 02", + fields: fields{ + Available: ptr.Float64Ptr(1), + OnHold: nil, + Scale: nil, + }, + want: false, + }, + { + name: "case 03", + fields: fields{ + Available: ptr.Float64Ptr(1), + OnHold: ptr.Float64Ptr(2), + Scale: nil, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := Balance{ + Available: tt.fields.Available, + OnHold: tt.fields.OnHold, + Scale: tt.fields.Scale, + } + if got := b.IsEmpty(); got != tt.want { + t.Errorf("Balance.IsEmpty() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/mmodel/status_test.go b/pkg/mmodel/status_test.go new file mode 100644 index 00000000..080ff14c --- /dev/null +++ b/pkg/mmodel/status_test.go @@ -0,0 +1,55 @@ +package mmodel + +import ( + "testing" + + "github.com/LerianStudio/midaz/components/mdz/pkg/ptr" +) + +func TestStatus_IsEmpty(t *testing.T) { + type fields struct { + Code string + Description *string + } + tests := []struct { + name string + fields fields + want bool + }{ + { + name: "case 01", + fields: fields{ + Code: "", + Description: nil, + }, + want: true, + }, + { + name: "case 02", + fields: fields{ + Code: "1", + Description: nil, + }, + want: false, + }, + { + name: "case 03", + fields: fields{ + Code: "1", + Description: ptr.StringPtr("a"), + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := Status{ + Code: tt.fields.Code, + Description: tt.fields.Description, + } + if got := s.IsEmpty(); got != tt.want { + t.Errorf("Status.IsEmpty() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/mpointers/pointers_test.go b/pkg/mpointers/pointers_test.go new file mode 100644 index 00000000..62cf1164 --- /dev/null +++ b/pkg/mpointers/pointers_test.go @@ -0,0 +1,46 @@ +package mpointers + +import ( + "testing" + "time" +) + +func TestString(t *testing.T) { + s := "test" + result := String(s) + if *result != s { + t.Errorf("String() = %v, want %v", *result, s) + } +} + +func TestBool(t *testing.T) { + b := true + result := Bool(b) + if *result != b { + t.Errorf("Bool() = %v, want %v", *result, b) + } +} + +func TestTime(t *testing.T) { + t1 := time.Now() + result := Time(t1) + if !result.Equal(t1) { + t.Errorf("Time() = %v, want %v", result, t1) + } +} + +func TestInt64(t *testing.T) { + num := int64(42) + result := Int64(num) + if *result != num { + t.Errorf("Int64() = %v, want %v", *result, num) + } +} + +func TestInt(t *testing.T) { + num := 42 + result := Int(num) + if *result != num { + t.Errorf("Int() = %v, want %v", *result, num) + } +} diff --git a/pkg/mredis/redis.go b/pkg/mredis/redis.go index 13dd788b..6bad646d 100644 --- a/pkg/mredis/redis.go +++ b/pkg/mredis/redis.go @@ -2,11 +2,11 @@ package mredis import ( "context" + + "github.com/redis/go-redis/v9" "go.uber.org/zap" "github.com/LerianStudio/midaz/pkg/mlog" - - "github.com/redis/go-redis/v9" ) const RedisTTL = 300 diff --git a/pkg/mzap/injector_test.go b/pkg/mzap/injector_test.go new file mode 100644 index 00000000..4fb53492 --- /dev/null +++ b/pkg/mzap/injector_test.go @@ -0,0 +1,15 @@ +package mzap + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInitializeLogger(t *testing.T) { + os.Setenv("ENV_NAME", "production") + defer os.Unsetenv("ENV_NAME") + logger := InitializeLogger() + assert.NotNil(t, logger) +} diff --git a/pkg/mzap/zap_test.go b/pkg/mzap/zap_test.go new file mode 100644 index 00000000..d81e24f4 --- /dev/null +++ b/pkg/mzap/zap_test.go @@ -0,0 +1,188 @@ +package mzap + +import ( + "testing" + + "go.uber.org/zap" +) + +func TestZap(t *testing.T) { + t.Run("log with hydration", func(t *testing.T) { + l := &ZapWithTraceLogger{} + l.logWithHydration(func(a ...any) {}, "") + }) + + t.Run("logf with hydration", func(t *testing.T) { + l := &ZapWithTraceLogger{} + l.logfWithHydration(func(s string, a ...any) {}, "", "") + }) + + t.Run("ZapWithTraceLogger info", func(t *testing.T) { + logger, _ := zap.NewDevelopment() + sugar := logger.Sugar() + + zapLogger := &ZapWithTraceLogger{ + Logger: sugar, + defaultMessageTemplate: "default template: ", + } + zapLogger.Info(func(s string, a ...any) {}, "", "") + }) + + t.Run("ZapWithTraceLogger infof", func(t *testing.T) { + logger, _ := zap.NewDevelopment() + sugar := logger.Sugar() + + zapLogger := &ZapWithTraceLogger{ + Logger: sugar, + defaultMessageTemplate: "default template: ", + } + zapLogger.Infof("", "") + }) + + t.Run("ZapWithTraceLogger infoln", func(t *testing.T) { + logger, _ := zap.NewDevelopment() + sugar := logger.Sugar() + + zapLogger := &ZapWithTraceLogger{ + Logger: sugar, + defaultMessageTemplate: "default template: ", + } + zapLogger.Infoln("", "") + }) + + t.Run("ZapWithTraceLogger Error", func(t *testing.T) { + logger, _ := zap.NewDevelopment() + sugar := logger.Sugar() + + zapLogger := &ZapWithTraceLogger{ + Logger: sugar, + defaultMessageTemplate: "default template: ", + } + zapLogger.Error("", "") + }) + + t.Run("ZapWithTraceLogger Errorf", func(t *testing.T) { + logger, _ := zap.NewDevelopment() + sugar := logger.Sugar() + + zapLogger := &ZapWithTraceLogger{ + Logger: sugar, + defaultMessageTemplate: "default template: ", + } + zapLogger.Errorf("", "") + }) + + t.Run("ZapWithTraceLogger Errorln", func(t *testing.T) { + logger, _ := zap.NewDevelopment() + sugar := logger.Sugar() + + zapLogger := &ZapWithTraceLogger{ + Logger: sugar, + defaultMessageTemplate: "default template: ", + } + zapLogger.Errorln("", "") + }) + + t.Run("ZapWithTraceLogger Warn", func(t *testing.T) { + logger, _ := zap.NewDevelopment() + sugar := logger.Sugar() + + zapLogger := &ZapWithTraceLogger{ + Logger: sugar, + defaultMessageTemplate: "default template: ", + } + zapLogger.Warn("", "") + }) + + t.Run("ZapWithTraceLogger Warnf", func(t *testing.T) { + logger, _ := zap.NewDevelopment() + sugar := logger.Sugar() + + zapLogger := &ZapWithTraceLogger{ + Logger: sugar, + defaultMessageTemplate: "default template: ", + } + zapLogger.Warnf("", "") + }) + + t.Run("ZapWithTraceLogger Warnln", func(t *testing.T) { + logger, _ := zap.NewDevelopment() + sugar := logger.Sugar() + + zapLogger := &ZapWithTraceLogger{ + Logger: sugar, + defaultMessageTemplate: "default template: ", + } + zapLogger.Warnln("", "") + }) + + t.Run("ZapWithTraceLogger Debug", func(t *testing.T) { + logger, _ := zap.NewDevelopment() + sugar := logger.Sugar() + + zapLogger := &ZapWithTraceLogger{ + Logger: sugar, + defaultMessageTemplate: "default template: ", + } + zapLogger.Debug("", "") + }) + + t.Run("ZapWithTraceLogger Debugf", func(t *testing.T) { + logger, _ := zap.NewDevelopment() + sugar := logger.Sugar() + + zapLogger := &ZapWithTraceLogger{ + Logger: sugar, + defaultMessageTemplate: "default template: ", + } + zapLogger.Debugf("", "") + }) + + t.Run("ZapWithTraceLogger Debugln", func(t *testing.T) { + logger, _ := zap.NewDevelopment() + sugar := logger.Sugar() + + zapLogger := &ZapWithTraceLogger{ + Logger: sugar, + defaultMessageTemplate: "default template: ", + } + zapLogger.Debugln("", "") + }) + + t.Run("ZapWithTraceLogger WithFields", func(t *testing.T) { + logger, _ := zap.NewDevelopment() + sugar := logger.Sugar() + + zapLogger := &ZapWithTraceLogger{ + Logger: sugar, + defaultMessageTemplate: "default template: ", + } + zapLogger.WithFields("", "") + }) + + t.Run("ZapWithTraceLogger Sync)", func(t *testing.T) { + logger, _ := zap.NewDevelopment() + sugar := logger.Sugar() + + zapLogger := &ZapWithTraceLogger{ + Logger: sugar, + defaultMessageTemplate: "default template: ", + } + zapLogger.Sync() + }) + + t.Run("ZapWithTraceLogger WithDefaultMessageTemplate)", func(t *testing.T) { + logger, _ := zap.NewDevelopment() + sugar := logger.Sugar() + + zapLogger := &ZapWithTraceLogger{ + Logger: sugar, + defaultMessageTemplate: "default template: ", + } + zapLogger.WithDefaultMessageTemplate("") + }) + + t.Run("ZapWithTraceLogger WithDefaultMessageTemplate)", func(t *testing.T) { + hydrateArgs("", []any{}) + }) +} diff --git a/pkg/net/http/withTelemetry.go b/pkg/net/http/withTelemetry.go index 35fd55dd..f121c46c 100644 --- a/pkg/net/http/withTelemetry.go +++ b/pkg/net/http/withTelemetry.go @@ -2,13 +2,14 @@ package http import ( "context" + "strings" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "strings" "github.com/LerianStudio/midaz/pkg" cn "github.com/LerianStudio/midaz/pkg/constant" @@ -130,8 +131,9 @@ func (tm *TelemetryMiddleware) collectMetrics(ctx context.Context) error { return err } - cpuUsage := pkg.GetCPUUsage(ctx) - memUsage := pkg.GetMemUsage(ctx) + cmdExec := pkg.Syscmd{} + cpuUsage := pkg.GetCPUUsage(ctx, &cmdExec) + memUsage := pkg.GetMemUsage(ctx, &cmdExec) cpuGauge.Record(ctx, cpuUsage) memGauge.Record(ctx, memUsage) diff --git a/pkg/os_test.go b/pkg/os_test.go new file mode 100644 index 00000000..161fa347 --- /dev/null +++ b/pkg/os_test.go @@ -0,0 +1,258 @@ +package pkg + +import ( + "os" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestGetenvOrDefault(t *testing.T) { + tests := []struct { + name string + key string + envValue string + defaultValue string + expectedValue string + }{ + { + name: "Key exists with value", + key: "EXISTING_KEY", + envValue: "some_value", + defaultValue: "default_value", + expectedValue: "some_value", + }, + { + name: "Key does not exist", + key: "NON_EXISTING_KEY", + envValue: "", + defaultValue: "default_value", + expectedValue: "default_value", + }, + { + name: "Key exists with empty value", + key: "EMPTY_KEY", + envValue: "", + defaultValue: "default_value", + expectedValue: "default_value", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv(tt.key, tt.envValue) + result := GetenvOrDefault(tt.key, tt.defaultValue) + assert.Equal(t, tt.expectedValue, result) + }) + } +} + +func TestGetenvBoolOrDefault(t *testing.T) { + tests := []struct { + name string + key string + envValue string + defaultValue bool + expectedValue bool + }{ + { + name: "Key exists with 'true' value", + key: "BOOL_KEY", + envValue: "true", + defaultValue: false, + expectedValue: true, + }, + { + name: "Key exists with 'false' value", + key: "BOOL_KEY", + envValue: "false", + defaultValue: true, + expectedValue: false, + }, + { + name: "Key exists with invalid value", + key: "BOOL_KEY", + envValue: "not_a_bool", + defaultValue: true, + expectedValue: true, + }, + { + name: "Key does not exist", + key: "NON_EXISTING_KEY", + envValue: "", + defaultValue: true, + expectedValue: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv(tt.key, tt.envValue) + result := GetenvBoolOrDefault(tt.key, tt.defaultValue) + assert.Equal(t, tt.expectedValue, result) + }) + } +} + +func TestGetenvIntOrDefault(t *testing.T) { + tests := []struct { + name string + key string + envValue string + defaultValue int64 + expectedValue int64 + }{ + { + name: "Key exists with valid int value", + key: "INT_KEY", + envValue: "42", + defaultValue: 0, + expectedValue: 42, + }, + { + name: "Key exists with invalid int value", + key: "INT_KEY", + envValue: "invalid", + defaultValue: 10, + expectedValue: 10, + }, + { + name: "Key does not exist", + key: "NON_EXISTING_KEY", + envValue: "", + defaultValue: 100, + expectedValue: 100, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv(tt.key, tt.envValue) + result := GetenvIntOrDefault(tt.key, tt.defaultValue) + assert.Equal(t, tt.expectedValue, result) + }) + } +} + +func TestInitLocalEnvConfig_WithoutEnvFile(t *testing.T) { + t.Run("without envfile", func(t *testing.T) { + localEnvConfig = nil + localEnvConfigOnce = sync.Once{} + + os.Remove(".env") + + os.Setenv("VERSION", "1.0.0") + os.Setenv("ENV_NAME", "local") + defer os.Unsetenv("VERSION") + defer os.Unsetenv("ENV_NAME") + + cfg := InitLocalEnvConfig() + + if cfg == nil { + t.Fatal("expected non-nil LocalEnvConfig, got nil") + } + + if cfg.Initialized { + t.Errorf("expected Initialized to be false, got true") + } + }) + + t.Run("non local environment", func(t *testing.T) { + localEnvConfig = nil + localEnvConfigOnce = sync.Once{} + + os.Setenv("VERSION", "1.0.0") + os.Setenv("ENV_NAME", "production") + defer os.Unsetenv("VERSION") + defer os.Unsetenv("ENV_NAME") + + cfg := InitLocalEnvConfig() + + if cfg != nil { + t.Fatalf("expected nil LocalEnvConfig for non-local env, got %v", cfg) + } + }) +} + +func TestSetConfigFromEnvVars(t *testing.T) { + type Config struct { + Host string `env:"APP_HOST"` + Port int `env:"APP_PORT"` + UseHTTPS bool `env:"APP_USE_HTTPS"` + } + + os.Setenv("APP_HOST", "localhost") + os.Setenv("APP_PORT", "8080") + os.Setenv("APP_USE_HTTPS", "true") + + defer func() { + os.Unsetenv("APP_HOST") + os.Unsetenv("APP_PORT") + os.Unsetenv("APP_USE_HTTPS") + }() + + cfg := &Config{} + err := SetConfigFromEnvVars(cfg) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if cfg.Host != "localhost" { + t.Errorf("expected Host to be 'localhost', got '%s'", cfg.Host) + } + + if cfg.Port != 8080 { + t.Errorf("expected Port to be 8080, got '%d'", cfg.Port) + } + + if !cfg.UseHTTPS { + t.Errorf("expected UseHTTPS to be true, got false") + } +} + +func TestEnsureConfigFromEnvVars(t *testing.T) { + t.Run("Valid environment variables", func(t *testing.T) { + + type Config struct { + Host string `env:"HOST"` + Port string `env:"PORT"` + } + + os.Setenv("HOST", "localhost") + os.Setenv("PORT", "8080") + defer os.Unsetenv("HOST") + defer os.Unsetenv("PORT") + + config := &Config{} + result := EnsureConfigFromEnvVars(config).(*Config) + + if result.Host != "localhost" { + t.Errorf("Expected Host to be 'localhost', got '%s'", result.Host) + } + if result.Port != "8080" { + t.Errorf("Expected Port to be '8080', got '%s'", result.Port) + } + }) + + t.Run("Missing environment variables", func(t *testing.T) { + os.Unsetenv("HOST") + os.Unsetenv("PORT") + + type Config struct { + Host string `env:"HOST"` + Port string `env:"PORT"` + Time time.Time `env:"TIME"` + } + + defer func() { + if r := recover(); r == nil { + t.Errorf("Expected panic but did not occur") + } + }() + + config := &Config{} + EnsureConfigFromEnvVars(config) + }) +} diff --git a/pkg/stringUtils_test.go b/pkg/stringUtils_test.go index b56ce260..0b290099 100644 --- a/pkg/stringUtils_test.go +++ b/pkg/stringUtils_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/LerianStudio/midaz/pkg/mpointers" + "github.com/stretchr/testify/assert" ) func Test_RemoveAccents(t *testing.T) { @@ -89,3 +90,188 @@ func TestCamelToSnakeCase(t *testing.T) { }) } } + +func TestRegexIgnoreAccents(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "Test single lowercase character", + input: "a", + expected: "[aáàãâ]", + }, + { + name: "Test single uppercase character", + input: "A", + expected: "[AÁÀÃÂ]", + }, + { + name: "Test multiple characters", + input: "aAç", + expected: "[aáàãâ][AÁÀÃÂ][cç]", + }, + { + name: "Test no matching character", + input: "z", + expected: "z", + }, + { + name: "Test string with accented letters", + input: "áéíóú", + expected: "[aáàãâ][eéèê][iíìî][oóòõô][uùúû]", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := RegexIgnoreAccents(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestRemoveChars(t *testing.T) { + tests := []struct { + name string + str string + chars map[string]bool + expected string + }{ + { + name: "Remove single character", + str: "hello", + chars: map[string]bool{"e": true}, + expected: "hllo", + }, + { + name: "Remove characters not in string", + str: "hello", + chars: map[string]bool{"x": true}, + expected: "hello", + }, + { + name: "Remove all characters", + str: "hello", + chars: map[string]bool{"h": true, "e": true, "l": true, "o": true}, + expected: "", + }, + { + name: "Empty string", + str: "", + chars: map[string]bool{"a": true}, + expected: "", + }, + { + name: "Remove no characters", + str: "hello", + chars: map[string]bool{}, + expected: "hello", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := RemoveChars(tt.str, tt.chars) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestReplaceUUIDWithPlaceholder(t *testing.T) { + tests := []struct { + name string + path string + expected string + }{ + { + name: "Replace single UUID", + path: "/users/123e4567-e89b-12d3-a456-426614174000/posts", + expected: "/users/:id/posts", + }, + { + name: "Replace multiple UUIDs", + path: "/users/123e4567-e89b-12d3-a456-426614174000/posts/987e6543-a21b-34d5-b678-123456789012", + expected: "/users/:id/posts/:id", + }, + { + name: "No UUID in path", + path: "/users/hello/posts", + expected: "/users/hello/posts", + }, + { + name: "UUID in middle of path", + path: "/items/123e4567-e89b-12d3-a456-426614174000/details", + expected: "/items/:id/details", + }, + { + name: "Empty path", + path: "", + expected: "", + }, + { + name: "Invalid UUID format", + path: "/users/invalid-uuid/posts", + expected: "/users/invalid-uuid/posts", // Não há UUID válido para substituir + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ReplaceUUIDWithPlaceholder(tt.path) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestValidateServerAddress(t *testing.T) { + tests := []struct { + name string + value string + expected string + }{ + { + name: "Valid server address", + value: "localhost:8080", + expected: "localhost:8080", + }, + { + name: "Valid server address with domain", + value: "example.com:443", + expected: "example.com:443", + }, + { + name: "Invalid server address (no port)", + value: "localhost", + expected: "", + }, + { + name: "Invalid server address (no host)", + value: ":8080", + expected: "", + }, + { + name: "Invalid server address (non-numeric port)", + value: "localhost:abc", + expected: "", + }, + { + name: "Empty server address", + value: "", + expected: "", + }, + { + name: "Valid address with IP", + value: "192.168.1.1:8080", + expected: "192.168.1.1:8080", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ValidateServerAddress(tt.value) + assert.Equal(t, tt.expected, result) + }) + } +} diff --git a/pkg/utils.go b/pkg/utils.go index 3135f9c9..660ea029 100644 --- a/pkg/utils.go +++ b/pkg/utils.go @@ -12,9 +12,9 @@ import ( "strings" "unicode" - cn "github.com/LerianStudio/midaz/pkg/constant" - "github.com/google/uuid" + + cn "github.com/LerianStudio/midaz/pkg/constant" ) // Contains checks if an item is in a slice. This function uses type parameters to work with any slice type. @@ -181,11 +181,21 @@ func MergeMaps(source, target map[string]any) map[string]any { return target } +type SyscmdI interface { + ExecCmd(name string, arg ...string) ([]byte, error) +} + +type Syscmd struct{} + +func (r *Syscmd) ExecCmd(name string, arg ...string) ([]byte, error) { + return exec.Command(name, arg...).Output() +} + // GetCPUUsage get the current CPU usage -func GetCPUUsage(ctx context.Context) int64 { +func GetCPUUsage(ctx context.Context, exc SyscmdI) int64 { logger := NewLoggerFromContext(ctx) - out, err := exec.Command("sh", "-c", "top -bn1 | grep 'Cpu(s)' | sed 's/.*, *\\([0-9.]*\\)%* id.*/\\1/' | awk '{print 100 - $1}'").Output() + out, err := exc.ExecCmd("sh", "-c", "top -bn1 | grep 'Cpu(s)' | sed 's/.*, *\\([0-9.]*\\)%* id.*/\\1/' | awk '{print 100 - $1}'") if err != nil { fmt.Println("Error executing command:", err) return 0 @@ -204,10 +214,10 @@ func GetCPUUsage(ctx context.Context) int64 { } // GetMemUsage get the current memory usage -func GetMemUsage(ctx context.Context) int64 { +func GetMemUsage(ctx context.Context, exc SyscmdI) int64 { logger := NewLoggerFromContext(ctx) - out, err := exec.Command("sh", "-c", "free | grep Mem | awk '{print $3/$2 * 100.0}'").Output() + out, err := exc.ExecCmd("sh", "-c", "free | grep Mem | awk '{print $3/$2 * 100.0}'") if err != nil { return 0 } diff --git a/pkg/utils_mock.go b/pkg/utils_mock.go new file mode 100644 index 00000000..af9b3cb0 --- /dev/null +++ b/pkg/utils_mock.go @@ -0,0 +1,60 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: /home/max/Workspace/LerianStudio/midaz/pkg/utils.go +// +// Generated by this command: +// +// mockgen -source=/home/max/Workspace/LerianStudio/midaz/pkg/utils.go -destination=/home/max/Workspace/LerianStudio/midaz/pkg/utils_mock.go -package pkg +// + +// Package pkg is a generated GoMock package. +package pkg + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockSyscmdI is a mock of SyscmdI interface. +type MockSyscmdI struct { + ctrl *gomock.Controller + recorder *MockSyscmdIMockRecorder + isgomock struct{} +} + +// MockSyscmdIMockRecorder is the mock recorder for MockSyscmdI. +type MockSyscmdIMockRecorder struct { + mock *MockSyscmdI +} + +// NewMockSyscmdI creates a new mock instance. +func NewMockSyscmdI(ctrl *gomock.Controller) *MockSyscmdI { + mock := &MockSyscmdI{ctrl: ctrl} + mock.recorder = &MockSyscmdIMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSyscmdI) EXPECT() *MockSyscmdIMockRecorder { + return m.recorder +} + +// ExecCmd mocks base method. +func (m *MockSyscmdI) ExecCmd(name string, arg ...string) ([]byte, error) { + m.ctrl.T.Helper() + varargs := []any{name} + for _, a := range arg { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ExecCmd", varargs...) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExecCmd indicates an expected call of ExecCmd. +func (mr *MockSyscmdIMockRecorder) ExecCmd(name any, arg ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{name}, arg...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecCmd", reflect.TypeOf((*MockSyscmdI)(nil).ExecCmd), varargs...) +} diff --git a/pkg/utils_test.go b/pkg/utils_test.go index 4c8354d3..d6412069 100644 --- a/pkg/utils_test.go +++ b/pkg/utils_test.go @@ -1,17 +1,598 @@ package pkg import ( + "context" + "errors" + "reflect" "testing" + cn "github.com/LerianStudio/midaz/pkg/constant" "github.com/google/uuid" + "github.com/stretchr/testify/assert" + gomock "go.uber.org/mock/gomock" ) +func TestContains(t *testing.T) { + type args struct { + slice []any + item any + } + + type examploToTestCheck struct { + Msg string + } + + tests := []struct { + name string + args args + want bool + }{ + { + name: "checks for an integer value returning positive", + args: args{ + slice: []any{1, 2, 3, 4, 5}, + item: 4, + }, + want: true, + }, + { + name: "checks for integer value returns false because the value entered is not in the list", args: args{ + slice: []any{1, 2, 3, 4, 5}, + item: 11, + }, + want: false, + }, + { + name: "checks for an string value returning positive", + args: args{ + slice: []any{"luffy", "sanji", "zoro"}, + item: "sanji", + }, + want: true, + }, + { + name: "checks for string value returns false because the value entered is not in the list", + args: args{ + slice: []any{"luffy", "sanji"}, + item: "zoro", + }, + want: false, + }, + { + name: "checks for an struct value returning positive", + args: args{ + slice: []any{ + examploToTestCheck{ + Msg: "01", + }, + }, + item: examploToTestCheck{Msg: "01"}, + }, + want: true, + }, + { + name: "checks for struct value returns false because the value entered is not in the list", + args: args{ + slice: []any{ + examploToTestCheck{ + Msg: "02", + }, + }, + item: examploToTestCheck{Msg: "01"}, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Contains(tt.args.slice, tt.args.item); got != tt.want { + t.Errorf("Contains() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCheckMetadataKeyAndValueLength(t *testing.T) { + type args struct { + limit int + metadata map[string]any + } + tests := []struct { + name string + args args + err error + wantErr bool + }{ + { + name: "exceeds the key limit", + args: args{ + limit: 2, + metadata: map[string]any{ + "01": 12, + "02": 13, + "033": 142, + }, + }, + err: cn.ErrMetadataKeyLengthExceeded, + wantErr: true, + }, + { + name: "case parse int", + args: args{ + limit: 2, + metadata: map[string]any{ + "01": 12, + "02": 13, + }, + }, + }, + { + name: "case parse float64", + args: args{ + limit: 5, + metadata: map[string]any{ + "01": 12.1, + "02": 13.4, + }, + }, + }, + { + name: "case parse string", + args: args{ + limit: 5, + metadata: map[string]any{ + "01": "br", + "02": "us", + }, + }, + }, + { + name: "case parse bool", + args: args{ + limit: 5, + metadata: map[string]any{ + "01": true, + "02": false, + }, + }, + }, + { + name: "case parse string exeeds the limit value", + args: args{ + limit: 5, + metadata: map[string]any{ + "01": "br", + "02": "us", + "03": "Guarapa", + }, + }, + err: cn.ErrMetadataValueLengthExceeded, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := CheckMetadataKeyAndValueLength(tt.args.limit, tt.args.metadata) + if (err != nil) != tt.wantErr { + if err != tt.err { + t.Errorf("CheckMetadataKeyAndValueLength() error = %v, wantErr %v", err, tt.wantErr) + } + } + }) + } +} + +func TestValidateCountryAddress(t *testing.T) { + type args struct { + country string + } + tests := []struct { + name string + args args + err error + wantErr bool + }{ + { + name: "get item exist", + args: args{ + country: "LY", + }, + }, + { + name: "failed get item no exist", + args: args{ + country: "SOU", + }, + err: cn.ErrInvalidCountryCode, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := ValidateCountryAddress(tt.args.country); (err != nil) != tt.wantErr { + t.Errorf("ValidateCountryAddress() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestValidateAccountType(t *testing.T) { + tests := []struct { + name string + accountType string + wantErr bool + }{ + {"Valid Deposit", "deposit", false}, + {"Valid Savings", "savings", false}, + {"Valid Loans", "loans", false}, + {"Valid Marketplace", "marketplace", false}, + {"Valid CreditCard", "creditCard", false}, + {"Invalid Account Type", "invalidType", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateAccountType(tt.accountType) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestValidateType(t *testing.T) { + tests := []struct { + input string + expected error + }{ + {"crypto", nil}, + {"currency", nil}, + {"commodity", nil}, + {"others", nil}, + {"invalid", cn.ErrInvalidType}, + {"", cn.ErrInvalidType}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + err := ValidateType(tt.input) + if err != tt.expected { + t.Errorf("ValidateType(%q) = %v; want %v", tt.input, err, tt.expected) + } + }) + } +} + +func TestValidateCode(t *testing.T) { + tests := []struct { + name string + input string + expected error + }{ + {name: "Valid Code", input: "USD", expected: nil}, + {name: "Lowercase Letters", input: "usd", expected: cn.ErrCodeUppercaseRequirement}, + {name: "Contains Number", input: "US1", expected: cn.ErrInvalidCodeFormat}, + {name: "Contains Symbol", input: "US$", expected: cn.ErrInvalidCodeFormat}, + {name: "Empty Code", input: "", expected: nil}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateCode(tt.input) + if err != tt.expected { + t.Errorf("ValidateCode(%q) = %v; want %v", tt.input, err, tt.expected) + } + }) + } +} + +func TestValidateCurrency(t *testing.T) { + tests := []struct { + name string + input string + expected error + }{ + {name: "Valid Currency Code - USD", input: "USD", expected: nil}, + {name: "Valid Currency Code - EUR", input: "EUR", expected: nil}, + {name: "Valid Currency Code - JPY", input: "JPY", expected: nil}, + {name: "Invalid Currency Code", input: "ABC", expected: cn.ErrCurrencyCodeStandardCompliance}, + {name: "Empty Currency Code", input: "", expected: cn.ErrCurrencyCodeStandardCompliance}, + {name: "Lowercase Currency Code", input: "usd", expected: cn.ErrCurrencyCodeStandardCompliance}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateCurrency(tt.input) + if err != tt.expected { + t.Errorf("ValidateCurrency(%q) = %v; want %v", tt.input, err, tt.expected) + } + }) + } +} + +func TestSafeIntToUint64(t *testing.T) { + tests := []struct { + name string + input int + expected uint64 + }{ + {name: "Positive Value", input: 42, expected: 42}, + {name: "Zero Value", input: 0, expected: 0}, + {name: "Negative Value", input: -1, expected: 1}, + {name: "Large Positive Value", input: 1000000, expected: 1000000}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := SafeIntToUint64(tt.input) + if result != tt.expected { + t.Errorf("SafeIntToUint64(%d) = %d; want %d", tt.input, result, tt.expected) + } + }) + } +} + +func TestIsUUID(t *testing.T) { + tests := []struct { + name string + input string + expected bool + }{ + {name: "Valid UUID", input: "123e4567-e89b-12d3-a456-426614174000", expected: true}, + {name: "Invalid UUID - Missing Segments", input: "123e4567-e89b-12d3-a456", expected: false}, + {name: "Invalid UUID - Extra Characters", input: "123e4567-e89b-12d3-a456-426614174000xyz", expected: false}, + {name: "Invalid UUID - Wrong Version", input: "123e4567-e89b-62d3-a456-426614174000", expected: false}, + {name: "Invalid UUID - Wrong Variant", input: "123e4567-e89b-12d3-c456-426614174000", expected: false}, + {name: "Empty String", input: "", expected: false}, + {name: "Random String", input: "not-a-uuid", expected: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := IsUUID(tt.input) + assert.Equal(t, tt.expected, result, "IsUUID(%q) should return %v", tt.input, tt.expected) + }) + } +} + func Test_GenerateUUIDv7(t *testing.T) { u := GenerateUUIDv7() - if u.Version() != 7 { - t.Errorf("Expected UUID version 7, but got version %d", u.Version()) + assert.NotEqual(t, uuid.Nil, u, "Generated UUIDv7 should not be nil") + assert.Equal(t, 7, int(u.Version()), "Generated UUID version should be 7") + assert.Equal(t, 36, len(u.String()), "Generated UUID length should be 36") +} + +func TestStructToJSONString(t *testing.T) { + tests := []struct { + name string + input any + expected string + expectError bool + }{ + { + name: "Valid Struct", + input: struct{ Name string }{Name: "John"}, + expected: `{"Name":"John"}`, + expectError: false, + }, + { + name: "Nil Input", + input: nil, + expected: "null", + expectError: false, + }, + { + name: "Empty Struct", + input: struct{}{}, + expected: `{}`, + expectError: false, + }, + { + name: "Struct with Multiple Fields", + input: struct { + Name string + Age int + }{Name: "Alice", Age: 30}, + expected: `{"Name":"Alice","Age":30}`, + expectError: false, + }, + { + name: "Invalid Struct (unexported field)", + input: struct{ name string }{name: "Hidden"}, + expected: `{}`, // Unexported fields are ignored in JSON serialization + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := StructToJSONString(tt.input) + + if tt.expectError { + assert.Error(t, err, "Expected an error but got none") + } else { + assert.NoError(t, err, "Did not expect an error but got one") + assert.JSONEq(t, tt.expected, result, "Expected JSON: %s, got: %s", tt.expected, result) + } + }) + } +} + +func TestMergeMaps(t *testing.T) { + tests := []struct { + name string + source map[string]any + target map[string]any + expected map[string]any + }{ + { + name: "Add new keys from source", + source: map[string]any{"key1": "value1", "key2": "value2"}, + target: map[string]any{"key3": "value3"}, + expected: map[string]any{"key1": "value1", "key2": "value2", "key3": "value3"}, + }, + { + name: "Override existing keys in target", + source: map[string]any{"key1": "newValue1"}, + target: map[string]any{"key1": "oldValue1", "key2": "value2"}, + expected: map[string]any{"key1": "newValue1", "key2": "value2"}, + }, + { + name: "Remove keys from target when source value is nil", + source: map[string]any{"key1": nil}, + target: map[string]any{"key1": "value1", "key2": "value2"}, + expected: map[string]any{"key2": "value2"}, + }, + { + name: "Empty source map", + source: map[string]any{}, + target: map[string]any{"key1": "value1"}, + expected: map[string]any{"key1": "value1"}, + }, + { + name: "Empty target map", + source: map[string]any{"key1": "value1"}, + target: map[string]any{}, + expected: map[string]any{"key1": "value1"}, + }, + { + name: "Nil source map", + source: nil, + target: map[string]any{"key1": "value1"}, + expected: map[string]any{"key1": "value1"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := MergeMaps(tt.source, tt.target) + assert.Equal(t, tt.expected, result, "Result mismatch for test case: %s", tt.name) + }) } - if u.Variant() != uuid.RFC4122 { - t.Errorf("Expected UUID variant RFC4122, but got variant %d", u.Variant()) +} + +func TestGetCPUUsage(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + tests := []struct { + name string + mockResponse []byte + mockError error + expected int64 + expectError bool + }{ + { + name: "Valid CPU usage", + mockResponse: []byte("12.34"), + mockError: nil, + expected: 12, + expectError: false, + }, + { + name: "Error in executing command", + mockResponse: nil, + mockError: errors.New("command failed"), + expected: 0, + expectError: true, + }, + { + name: "Invalid output format", + mockResponse: []byte("invalid data"), + mockError: nil, + expected: 0, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockExecutor := NewMockSyscmdI(ctrl) + mockExecutor.EXPECT().ExecCmd(gomock.Any(), gomock.Any()).Return(tt.mockResponse, tt.mockError) + + ctx := context.Background() + result := GetCPUUsage(ctx, mockExecutor) + + if tt.expectError { + assert.Equal(t, tt.expected, result) + } else { + assert.Equal(t, tt.expected, result) + } + }) } } + +func TestGetMemUsage(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + tests := []struct { + name string + mockResponse []byte + mockError error + expected int64 + expectError bool + }{ + { + name: "Valid memory usage", + mockResponse: []byte("42.5"), + mockError: nil, + expected: 42, // Esperado arredondamento para int64 + expectError: false, + }, + { + name: "Error in executing command", + mockResponse: nil, + mockError: errors.New("command failed"), + expected: 0, + expectError: true, + }, + { + name: "Invalid output format", + mockResponse: []byte("invalid data"), + mockError: nil, + expected: 0, + expectError: true, + }, + { + name: "Empty output", + mockResponse: []byte(""), + mockError: nil, + expected: 0, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockExecutor := NewMockSyscmdI(ctrl) + mockExecutor.EXPECT().ExecCmd(gomock.Any(), gomock.Any()).Return(tt.mockResponse, tt.mockError) + + ctx := context.Background() + result := GetMemUsage(ctx, mockExecutor) + + if tt.expectError { + assert.Equal(t, tt.expected, result) + } else { + assert.Equal(t, tt.expected, result) + } + }) + } +} + +func TestGetMapNumKinds(t *testing.T) { + expected := map[reflect.Kind]bool{ + reflect.Int: true, + reflect.Int8: true, + reflect.Int16: true, + reflect.Int32: true, + reflect.Int64: true, + reflect.Float32: true, + reflect.Float64: true, + } + + result := GetMapNumKinds() + assert.Equal(t, expected, result) +} diff --git a/scripts/coverage.sh b/scripts/coverage.sh new file mode 100644 index 00000000..31ed7a85 --- /dev/null +++ b/scripts/coverage.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +IGNORED=$(cat ./scripts/coverage_ignore.txt | xargs -I{} echo '-not -path ./{}/*' | xargs) +PACKAGES=$(go list ./pkg/... | grep -v -f ./scripts/coverage_ignore.txt) + +echo $PACKAGES +go test -cover $PACKAGES -coverprofile=coverage.out diff --git a/scripts/coverage_ignore.txt b/scripts/coverage_ignore.txt new file mode 100644 index 00000000..bf5a6b3a --- /dev/null +++ b/scripts/coverage_ignore.txt @@ -0,0 +1 @@ +github.com/LerianStudio/midaz/pkg/mredis