diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b920e678..50e08205 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,19 @@ jobs: ENVIRONMENT: ci services: + mongo: + image: mongo:4.4.4 + env: + MONGO_INITDB_ROOT_USERNAME: mongoadmin + MONGO_INITDB_ROOT_PASSWORD: mongoadmin + options: >- + --health-cmd "echo 'db.stats().ok' | mongo localhost:27017/mongoadmin --quiet" + --health-interval 10s + --health-timeout 10s + --health-retries 10 + ports: + - 27017:27017 + redis: image: redis options: >- diff --git a/docker-compose.yml b/docker-compose.yml index 038800a8..1bdc497f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,11 @@ version: "3.4" services: + mongo: + image: mongo:4.4.4 + environment: + MONGO_INITDB_ROOT_USERNAME: mongoadmin + MONGO_INITDB_ROOT_PASSWORD: mongoadmin redis: image: redis:alpine rabbit: @@ -30,6 +35,7 @@ services: AWS_SECRET_ACCESS_KEY: minioadmin AWS_REGION: eu-west-1 depends_on: + - mongo - redis - rabbit - elasticsearch diff --git a/go.mod b/go.mod index 8890fe30..4243ecf7 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,8 @@ require ( github.com/tidwall/gjson v1.13.0 github.com/tidwall/sjson v1.2.4 github.com/xeipuuv/gojsonschema v1.2.0 + go.mongodb.org/mongo-driver v1.12.1 + golang.org/x/exp v0.0.0-20230306221820-f0f767cdffd6 gopkg.in/yaml.v3 v3.0.1 ) @@ -57,17 +59,20 @@ require ( github.com/goccy/go-json v0.4.7 // indirect github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/golang/mock v1.6.0 // indirect + github.com/golang/snappy v0.0.1 // indirect github.com/google/pprof v0.0.0-20230111200839-76d1ae5aea2b // indirect github.com/hashicorp/go-immutable-radix v1.3.0 // indirect github.com/hashicorp/go-memdb v1.3.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/klauspost/compress v1.13.6 // indirect github.com/lestrrat-go/backoff/v2 v2.0.7 // indirect github.com/lestrrat-go/httpcc v1.0.0 // indirect github.com/lestrrat-go/iter v1.0.0 // indirect github.com/lestrrat-go/option v1.0.0 // indirect github.com/lestrrat-go/pdebug/v3 v3.0.1 // indirect github.com/magefile/mage v1.10.0 // indirect + github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/onsi/ginkgo/v2 v2.7.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect @@ -76,12 +81,16 @@ require ( github.com/quic-go/quic-go v0.33.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect golang.org/x/crypto v0.5.0 // indirect - golang.org/x/exp v0.0.0-20230306221820-f0f767cdffd6 // indirect golang.org/x/mod v0.8.0 // indirect golang.org/x/net v0.8.0 // indirect + golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect golang.org/x/tools v0.6.0 // indirect diff --git a/go.sum b/go.sum index 0396e2e6..207e3320 100644 --- a/go.sum +++ b/go.sum @@ -133,10 +133,13 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -198,6 +201,8 @@ github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dv github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -239,6 +244,8 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo/v2 v2.7.0 h1:/XxtEV3I3Eif/HobnVx9YmJgk8ENdRsuUmM+fLCFNow= @@ -321,6 +328,12 @@ github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhso github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc= github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -328,9 +341,14 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1: github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.mongodb.org/mongo-driver v1.12.1 h1:nLkghSU8fQNaK7oUmDhQFsnrtcoNy7Z6LVFKsEecqgE= +go.mongodb.org/mongo-driver v1.12.1/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -344,6 +362,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U 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-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -369,6 +389,7 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/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.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -387,8 +408,11 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -402,7 +426,9 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -423,16 +449,23 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -459,10 +492,12 @@ golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4X golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 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/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= diff --git a/sonar-project.properties b/sonar-project.properties index a77a6a43..aea79972 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,7 +5,7 @@ sonar.organization=telefonicatc2tech sonar.go.tests.reportPaths=test-report.out sonar.go.coverage.reportPaths=coverage.txt sonar.go.golangci-lint.reportPaths=golangci-report.xml -sonar.coverage.exclusions=**/*context.go,**/*logger.go,**/*steps.go,**/*client.go,**/mock/**/*.go +sonar.coverage.exclusions=**/*context.go,**/*logger.go,**/*steps.go,**/*client.go,**/*collection.go,**/mock/**/*.go sonar.sources=. sonar.exclusions=**/*_test.go diff --git a/steps/mongo/client.go b/steps/mongo/client.go new file mode 100644 index 00000000..bfc7fe3e --- /dev/null +++ b/steps/mongo/client.go @@ -0,0 +1,47 @@ +// Copyright (c) Telefónica Cybersecurity & Cloud Tech S.L. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mongo + +import ( + "context" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/readpref" +) + +type ClientFunctions interface { + Ping(ctx context.Context, rp *readpref.ReadPref, client *mongo.Client) error + Disconnect(ctx context.Context, client *mongo.Client) error + Database(name string, client *mongo.Client) *mongo.Database +} + +type ClientService struct{} + +func NewMongoClientService() *ClientService { + return &ClientService{} +} + +func (c ClientService) Ping(ctx context.Context, rp *readpref.ReadPref, + client *mongo.Client) error { + return client.Ping(ctx, rp) +} + +func (c ClientService) Disconnect(ctx context.Context, client *mongo.Client) error { + return client.Disconnect(ctx) +} + +func (c ClientService) Database(name string, client *mongo.Client) *mongo.Database { + return client.Database(name) +} diff --git a/steps/mongo/client_mock.go b/steps/mongo/client_mock.go new file mode 100644 index 00000000..8b05d79d --- /dev/null +++ b/steps/mongo/client_mock.go @@ -0,0 +1,54 @@ +// Copyright (c) Telefónica Cybersecurity & Cloud Tech S.L. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mongo + +import ( + "context" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/readpref" +) + +var ( + PingError error + DisconnectError error + DatabaseDatabase *mongo.Database + ContextCliFake context.Context + ClientCliFake *mongo.Client + NameCliFake string + ReadPrefCliFake *readpref.ReadPref +) + +type ClientServiceFuncMock struct{} + +func (c ClientServiceFuncMock) Ping(ctx context.Context, rp *readpref.ReadPref, + client *mongo.Client) error { + ContextCliFake = ctx + ReadPrefCliFake = rp + ClientCliFake = client + return PingError +} + +func (c ClientServiceFuncMock) Disconnect(ctx context.Context, client *mongo.Client) error { + ContextCliFake = ctx + ClientCliFake = client + return DisconnectError +} + +func (c ClientServiceFuncMock) Database(name string, client *mongo.Client) *mongo.Database { + NameCliFake = name + ClientCliFake = client + return DatabaseDatabase +} diff --git a/steps/mongo/collection.go b/steps/mongo/collection.go new file mode 100644 index 00000000..c8d6f35f --- /dev/null +++ b/steps/mongo/collection.go @@ -0,0 +1,69 @@ +// Copyright (c) Telefónica Cybersecurity & Cloud Tech S.L. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mongo + +import ( + "context" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type CollectionFunctions interface { + Collection(name string, database *mongo.Database) *mongo.Collection + Name(collection *mongo.Collection) string + Find(ctx context.Context, filter interface{}, collection *mongo.Collection, + opts ...*options.FindOptions) (*mongo.Cursor, error) + FindOne(ctx context.Context, filter interface{}, collection *mongo.Collection, + opt ...*options.FindOneOptions) *mongo.SingleResult + InsertMany(ctx context.Context, documents []interface{}, + collection *mongo.Collection) (*mongo.InsertManyResult, error) + DeleteMany(ctx context.Context, filter interface{}, + collection *mongo.Collection, opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) +} + +type CollectionService struct{} + +func NewMongoCollectionService() *CollectionService { + return &CollectionService{} +} + +func (c CollectionService) Collection(name string, database *mongo.Database) *mongo.Collection { + return database.Collection(name) +} + +func (c CollectionService) Name(collection *mongo.Collection) string { + return collection.Name() +} + +func (c CollectionService) FindOne(ctx context.Context, filter interface{}, + collection *mongo.Collection, opts ...*options.FindOneOptions) *mongo.SingleResult { + return collection.FindOne(ctx, filter, opts...) +} + +func (c CollectionService) Find(ctx context.Context, filter interface{}, + collection *mongo.Collection, opts ...*options.FindOptions) (*mongo.Cursor, error) { + return collection.Find(ctx, filter, opts...) +} + +func (c CollectionService) InsertMany(ctx context.Context, documents []interface{}, + collection *mongo.Collection) (*mongo.InsertManyResult, error) { + return collection.InsertMany(ctx, documents) +} + +func (c CollectionService) DeleteMany(ctx context.Context, filter interface{}, + collection *mongo.Collection, opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) { + return collection.DeleteMany(ctx, filter, opts...) +} diff --git a/steps/mongo/collection_mock.go b/steps/mongo/collection_mock.go new file mode 100644 index 00000000..97d3970b --- /dev/null +++ b/steps/mongo/collection_mock.go @@ -0,0 +1,92 @@ +// Copyright (c) Telefónica Cybersecurity & Cloud Tech S.L. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mongo + +import ( + "context" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +var ( + CollectionCollection *mongo.Collection + NameString string + FindOneSingleResult *mongo.SingleResult + FindCursor *mongo.Cursor + FindError error + InsertManyInsertManyResult *mongo.InsertManyResult + InsertManyError error + DeleteResultDeleteResult *mongo.DeleteResult + DeleteResultError error + ContextColFake context.Context + CollectionColFake *mongo.Collection + FindOptsColFake []*options.FindOptions + FindOneOptsColFake []*options.FindOneOptions + DeleteOptsColFake []*options.DeleteOptions + DatabaseColFake *mongo.Database + NameColFake string + FilterColFake interface{} + DocumentsColFake interface{} +) + +type CollectionServiceFuncMock struct{} + +func (c CollectionServiceFuncMock) Collection(name string, + database *mongo.Database) *mongo.Collection { + NameColFake = name + DatabaseColFake = database + return CollectionCollection +} + +func (c CollectionServiceFuncMock) Name(collection *mongo.Collection) string { + CollectionColFake = collection + return NameString +} + +func (c CollectionServiceFuncMock) FindOne(ctx context.Context, filter interface{}, + collection *mongo.Collection, opts ...*options.FindOneOptions) *mongo.SingleResult { + ContextColFake = ctx + FilterColFake = filter + CollectionColFake = collection + FindOneOptsColFake = opts + return FindOneSingleResult +} + +func (c CollectionServiceFuncMock) Find(ctx context.Context, filter interface{}, + collection *mongo.Collection, opts ...*options.FindOptions) (*mongo.Cursor, error) { + ContextColFake = ctx + FilterColFake = filter + CollectionColFake = collection + FindOptsColFake = opts + return FindCursor, FindError +} + +func (c CollectionServiceFuncMock) InsertMany(ctx context.Context, documents []interface{}, + collection *mongo.Collection) (*mongo.InsertManyResult, error) { + ContextColFake = ctx + DocumentsColFake = documents + CollectionColFake = collection + return InsertManyInsertManyResult, InsertManyError +} + +func (c CollectionServiceFuncMock) DeleteMany(ctx context.Context, filter interface{}, + collection *mongo.Collection, opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) { + ContextColFake = ctx + FilterColFake = filter + CollectionColFake = collection + DeleteOptsColFake = opts + return DeleteResultDeleteResult, DeleteResultError +} diff --git a/steps/mongo/context.go b/steps/mongo/context.go new file mode 100755 index 00000000..59b5153c --- /dev/null +++ b/steps/mongo/context.go @@ -0,0 +1,38 @@ +// Copyright 2021 Telefonica Cybersecurity & Cloud Tech SL +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mongo + +import ( + "context" +) + +// ContextKey defines a type to store the mongo session in context.Context. +type ContextKey string + +const contextKey ContextKey = "mongoSession" + +// InitializeContext adds the mongo session to the context. +// The new context is returned because context is immutable. +func InitializeContext(ctx context.Context) context.Context { + return context.WithValue(ctx, contextKey, &Session{ + MongoClientService: *NewMongoClientService(), + MongoCollectionService: *NewMongoCollectionService()}) +} + +// GetSession returns the mongo session stored in context. +// Note that the context should be previously initialized with InitializeContext function. +func GetSession(ctx context.Context) *Session { + return ctx.Value(contextKey).(*Session) +} diff --git a/steps/mongo/session.go b/steps/mongo/session.go new file mode 100644 index 00000000..66ceea89 --- /dev/null +++ b/steps/mongo/session.go @@ -0,0 +1,548 @@ +// Copyright (c) Telefónica Cybersecurity & Cloud Tech S.L. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mongo + +import ( + "context" + "fmt" + "reflect" + "strconv" + "strings" + + "github.com/TelefonicaTC2Tech/golium" + "github.com/cucumber/godog" + "github.com/google/uuid" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +var ( + EMPTY = "[EMPTY]" + NULL = "[NULL]" +) + +// Session contains the information of a MongoDB session. +type Session struct { + + // Set the MongoDB database + database string + + // Set the host and credentials for the connection + clientOptions *options.ClientOptions + + // Create the mongo client with the MongoDB connection + client *mongo.Client + + // Points to a record in a collection + singleResult *mongo.SingleResult + + // Saves the _id collection to be used + idCollection string + + // Saves the collection to be used + collection *mongo.Collection + + // Save the names of the collection fields + fieldsCollectionName []string + + // Save collection items as JSON + dataCollectionJSONBytes []byte + + // Access MongoDB Client features + MongoClientService ClientFunctions + + // Access MongoDB Collecti on features + MongoCollectionService CollectionFunctions +} + +// FUNCTIONS CALLED BY STEPS + +// CheckMongoFieldDoesNotExistOrEmptyStep check that a field +// does not exist or it does exist and is empty +func (s *Session) CheckMongoFieldDoesNotExistOrEmptyStep( + ctx context.Context, collectionName, fieldSearched, idCollection string, +) error { + // 1-Setting the Collection Name in the Session + s.SetCollection(collectionName) + + // 2-Set fields collection name in Session: s.fieldsCollectionName + err := s.SetFieldsCollectionName(ctx, idCollection) + if err != nil { + return err + } + + // 3-If fieldSearched exists, check that the value of the fieldSearched is null + if s.ExistFieldCollection(fieldSearched) { + err = s.SetSingleResult(ctx, fieldSearched, nil) + } + + return err +} + +// CheckMongoFieldNameStep check if the name of the field searched of the user collection is correct +func (s *Session) CheckMongoFieldNameStep( + ctx context.Context, collectionName, fieldSearched, exist, idCollection string, +) error { + // 1-Set collection name and fields collection name in Session + s.SetCollection(collectionName) + s.SetFieldsCollectionName(ctx, idCollection) + + // 2-Get boolean if field exist and must exist in collection + existField := s.ExistFieldCollection(fieldSearched) + mustExistField := VerifyMustExist(exist) + + // 3-Verify exist and must exist + return s.VerifyExistAndMustExistValue(existField, mustExistField, nil) +} + +// CheckMongoValueIDStep checks if the past idCollection exists in the collection +func (s *Session) CheckMongoValueIDStep( + ctx context.Context, collectionName, idCollection, exist string, +) error { + // 1-Set collection name and fields collection name in Session + s.SetCollection(collectionName) + + // 2-Get boolean if _id exist and must exist in collection + existID, err := s.VerifyExistID(ctx, idCollection) + mustExistID := VerifyMustExist(exist) + + // 3-Verify exist and must exist + return s.VerifyExistAndMustExistValue(existID, mustExistID, err) +} + +// CheckMongoValuesStep checks the value of the MongoDB fields in the specified collection +func (s *Session) CheckMongoValuesStep( + ctx context.Context, collectionName, idCollection, exist string, t *godog.Table, +) error { + // 1-Set collection name and fields collection name in Session + s.SetCollection(collectionName) + + // 2-Get value of specified table + props, err := golium.ConvertTableToMap(ctx, t) + if err != nil { + return fmt.Errorf("ERROR: failed processing the table for validating the body: '%w'", err) + } + + // 3-Get boolean if data exist and must exist in collection + existValue, err := s.ValidateDataMongo(ctx, idCollection, props) + mustExistValue := VerifyMustExist(exist) + + // 4-Verify exist and must exist + return s.VerifyExistAndMustExistValue(existValue, mustExistValue, err) +} + +// CheckNumberDocumentscollectionNameStep verify the number of documents in collection +func (s *Session) CheckNumberDocumentscollectionNameStep(collectionName string, num int) error { + // 1-The collection from which the documents are to be counted is established + s.SetCollection(collectionName) + + // 2-Make a query to get all the documents in the collection + cursor, err := s.MongoCollectionService.Find(context.Background(), + bson.D{}, s.collection, &options.FindOptions{}) + if err != nil { + return fmt.Errorf("error: query error: '%s'", err) + } + + // 3-Iterate through the documents and count the ones that are there + count := 0 + for cursor.Next(context.Background()) { + count++ + } + + // 4-Check the result + if count != num { + return fmt.Errorf("error: the number of documents is '%d' and should be '%d'", count, num) + } + return nil +} + +// CreateDocumentcollectionNameStep creates a number of documents in the specified collection +func (s *Session) CreateDocumentscollectionNameStep( + ctx context.Context, num int, collectionName string) error { + // 1-collection in which the insertion will be made, if it does not exist it is created + s.SetCollection(collectionName) + + // 2-The documents to be inserted are created + allDocuments := s.CreateDocumentsCollection(ctx, num) + + // 3-Insert the documents into the "collectionName" collection of the database + _, err := s.MongoCollectionService.InsertMany(context.TODO(), allDocuments, s.collection) + if err != nil { + return err + } + + return nil +} + +// DeleteDocumentscollectionNameStep delete a document from the MongoDB collection +func (s *Session) DeleteAllDocumentscollectionNameStep(ctx context.Context, collectionName string, +) error { + // 1-The collection in which the deletion is to be made is established. + s.SetCollection(collectionName) + + // 2-Delete all documents + _, err := s.collection.DeleteMany(ctx, bson.D{}) + if err != nil { + return err + } + + return nil +} + +// DeleteDocumentscollectionNameStep delete a document from the MongoDB collection +func (s *Session) DeleteDocumentscollectionNameStep( + ctx context.Context, collectionName, field, value string) error { + // 1-The collection in which the deletion is to be made is established. + s.SetCollection(collectionName) + + // 2-Performs the deletion of documents that match the filter after the data type is converted. + // Only it is possible filter by string, int, float, or boolean values, also in slices and maps. + _, err := s.collection.DeleteMany(ctx, GetFilterConverted(field, value)) + if err != nil { + return err + } + + return nil +} + +// GenerateUUIDStoreIt create uuid in string format like _id and save in struct and context like _ID +func (s *Session) GenerateUUIDStoreItStep(ctx context.Context) error { + guid, err := uuid.NewRandom() + if err != nil { + return fmt.Errorf("failed generating UUID: %w", err) + } + s.idCollection = guid.String() + golium.GetContext(ctx).Put("_ID", s.idCollection) + + return nil +} + +// MongoConnectionStep establishes a connection in MongoDB. +// The connection, client and database data are saved in s.clientOptions, s.client, and s.database +func (s *Session) MongoConnectionStep(ctx context.Context, t *godog.Table) error { + // 1-Set credentials and host in session + var err error + props, err := golium.ConvertTableToMap(ctx, t) + if err != nil { + return fmt.Errorf("ERROR: failed processing the table for validating the body: '%w'", err) + } + uri := fmt.Sprintf("mongodb://%s:%s@%s/%s", + props["User"], props["Password"], props["Host"], props["AuthSource"]) + + // 2-Set clientOptions in session + s.clientOptions = options.Client().ApplyURI(uri) + + // 3-Connect to the MongoDB server and set client in session + s.client, err = mongo.Connect(ctx, s.clientOptions) + if err != nil { + return fmt.Errorf("error: problems with the client options or with the context. '%s'", err) + } + + // 4-Check the connection to the MongoDB server + err = s.MongoClientService.Ping(ctx, nil, s.client) + if err != nil { + return fmt.Errorf("error: problems with connection to MongoDB. '%s'", err) + } + + // 5-Set the database in session + s.database = props["Database"].(string) + + return nil +} + +// MongoDisconnection closes the connection to MongoDB if it exists +func (s *Session) MongoDisconnectionStep() error { + if s.client != nil { + err := s.MongoClientService.Disconnect(context.Background(), s.client) + if err != nil { + return fmt.Errorf("error: problem in MongoDB disconnection: '%s'", err) + } + } + return nil +} + +// GENERIC FUNCTIONS + +// ContainsElements check if an item exists in a slice +func ContainsElements(expectedElement, sliceElements interface{}) bool { + // 1-Create a reflection object from the slice + sliceValue := reflect.ValueOf(sliceElements) + + // 2-It is verified that the object of reflection is of the "slice" type + if sliceValue.Kind() != reflect.Slice { + return false + } + + for i := 0; i < sliceValue.Len(); i++ { + // 3-The slice element converted into an interface is compared with the searched element. + // The comparison is made in value and type + if reflect.DeepEqual(sliceValue.Index(i).Interface(), expectedElement) { + return true + } + } + + return false +} + +// GetFilter returns a filter to search for a record from a field +func GetFilter(key string, value interface{}) primitive.M { + if value == nil { + return bson.M{key: nil} + } + return bson.M{key: value} +} + +// GetFilterConverted returns a filter with field and data type required for delete a record +func GetFilterConverted(field, value string) primitive.M { + /* In Golang: + - the values "1", "true", "t", "T", "TRUE", "True" are interpreted as true + - the values "0", "false", "f", "F", "FALSE", "False" are interpreted as false. + To force only a few values in a cell to be considered Boolean, this slice is created. + */ + boolSlice := []string{"true", "TRUE", "True", "false", "FALSE", "False"} + + // Try to convert to bool + if ContainsElements(value, boolSlice) { + convertedValue, err := strconv.ParseBool(value) + if err == nil { + return bson.M{field: convertedValue} + } + } + + // Try to convert to int + if convertedValue, err := strconv.Atoi(value); err == nil { + return bson.M{field: convertedValue} + } + + // Try converting to float64 + if convertedValue, err := strconv.ParseFloat(value, 64); err == nil { + return bson.M{field: convertedValue} + } + + // If the passed value is "[EMPTY]" or "[NULL]", it evaluates to nil + if value == EMPTY || value == NULL { + return bson.M{field: nil} + } + + // If none of the above is true, the value remains as the original type (string) + return bson.M{field: value} +} + +// GetOptionsSearchAllFields creates an "options" to search all fields in the collection +func GetOptionsSearchAllFields() *options.FindOneOptions { + return options.FindOne().SetProjection(bson.M{}) +} + +// VerifyMustExist returns a boolean indicating whether the element should exist +func VerifyMustExist(exist string) bool { + return !strings.Contains(strings.ToLower(exist), "not") +} + +// SESSION FUNCTIONS + +// CreateDocumentsCollection creates num documents in a slice and inserts them into a collection +func (s *Session) CreateDocumentsCollection(ctx context.Context, num int) []interface{} { + ContextCliFake = ctx + + // 1-Initialize the document slice + allDocuments := []interface{}{} + + // 2-Obtaining the _id of the struct, if it does not exist it is created + id := s.idCollection + if id == "" { + newUUID, err := uuid.NewRandom() + if err != nil { + return nil + } + id = newUUID.String() + } + // 3-Creating Documents and Inserting Into the Slice + for i := 1; i <= num; i++ { + // Defines the document to be inserted. + // The _id will be the same across all + _ + iteration number + document := map[string]interface{}{ + "_id": id + "_" + strconv.Itoa(i), + "fieldString": "Example field string " + strconv.Itoa(i), + "fieldInt": i, + "fieldFloat": 3.14, + "fieldBool": true, + "fieldSlice": []string{"itemSlice_" + strconv.Itoa(i), "itemSlice20", "itemSlice30"}, + "fieldEmpty": nil, + "fieldMap": map[string]interface{}{ + "fieldString": "Example field in map string " + strconv.Itoa(i), + "fieldInt": i * 10, + "fieldFloat": 1974.1976, + "fieldBool": false, + "fieldSliceEmpty": []string{}, + "fieldMap2": map[string]interface{}{ + "fieldString": "Example field in map map string " + strconv.Itoa(i), + "fieldInt": i * 100, + "fieldFloat": 1974.1976, + "fieldBool": false, + "fieldEmpty": nil, + "fieldEmptyText": "", + }, + }, + } + allDocuments = append(allDocuments, document) + } + return allDocuments +} + +// ExistFieldCollection evaluate whether or not the searched field exists +func (s *Session) ExistFieldCollection(fieldSearched string) bool { + existField := false + for _, element := range s.fieldsCollectionName { + if element == fieldSearched { + existField = true + } + } + return existField +} + +// GetDecodeDocument decodes the BSON document in the bsonDoc variable +func (s *Session) GetDecodeDocument(singleResult mongo.SingleResult) (bson.D, error) { + var bsonDoc bson.D + if err := singleResult.Decode(&bsonDoc); err != nil { + err = fmt.Errorf("error: the decoding of the BSON has been erroneous: '%s'", err) + return nil, err + } + return bsonDoc, nil +} + +// SetCollection sets the collection. If the collection does not exist, no error is returned. +// Collections are created dynamically when you insert a document +func (s *Session) SetCollection(collectionName string) { + database := s.MongoClientService.Database(s.database, s.client) + s.collection = s.MongoCollectionService.Collection(collectionName, database) +} + +// SetDataCollectionJSONBytes convert BSON object to JSON +func (s *Session) SetDataCollectionJSONBytes(bsonDoc bson.D) error { + var err error + s.dataCollectionJSONBytes, err = bson.MarshalExtJSON(bsonDoc, false, false) + if err != nil { + return fmt.Errorf("error: the conversion from BSON to JSON has been erroneous: '%s'", err) + } + return nil +} + +// SetFieldsCollectionName save a slice with the names of the fields in s.fieldsCollectionName +func (s *Session) SetFieldsCollectionName(ctx context.Context, idCollection string) error { + // Make a query to find past _id's document + var document bson.M + err := s.MongoCollectionService.FindOne(ctx, + GetFilter("_id", idCollection), s.collection, &options.FindOneOptions{}).Decode(&document) + if err == mongo.ErrNoDocuments { + return fmt.Errorf("error: no documents matching the filter were found") + } else if err != nil { + return fmt.Errorf("error: '%s'", err) + } else { + // s.fieldsCollectionName is flushed, and the names of the fields in the document are added + s.fieldsCollectionName = s.fieldsCollectionName[:0] + for fieldName := range document { + s.fieldsCollectionName = append(s.fieldsCollectionName, fieldName) + } + } + + return nil +} + +// SetSingleResult set a Single Result from a Filter Search (GetFilter(...) function) +func (s *Session) SetSingleResult( + ctx context.Context, fieldSearched string, value interface{}) error { + s.singleResult = s.MongoCollectionService.FindOne( + ctx, GetFilter(fieldSearched, value), s.collection, GetOptionsSearchAllFields()) + if s.singleResult.Err() != nil { + return fmt.Errorf("error: the searched '%s' field does not have the '%s' value "+ + "in the '%s' collection", fieldSearched, value, s.collection.Name()) + } + return nil +} + +// ValidateDataMongo verifies that the feature table data exists in the MongoDB collection +func (s *Session) ValidateDataMongo( + ctx context.Context, idCollection string, props map[string]interface{}) (bool, error) { + // 1-Sets a document to s.singleResult from a filter search + s.SetSingleResult(ctx, "_id", idCollection) + + // 2-Decodes the singleResult document into a BSON (bsonDoc) + bsonDoc, err := s.GetDecodeDocument(*s.singleResult) + if err != nil { + return false, err + } + + // 3-Convert the bsonDoc object to JSON + err = s.SetDataCollectionJSONBytes(bsonDoc) + if err != nil { + return false, err + } + + // 4-Set the list of fields in the document to fieldsCollectionName + s.SetFieldsCollectionName(ctx, idCollection) + if err != nil { + return false, err + } + + // 5-Navigate through the feature table and check the data + m := golium.NewMapFromJSONBytes(s.dataCollectionJSONBytes) + for key, expectedValue := range props { + value := m.Get(key) + // Verify that the name of the clean field (fieldTableFeature) exists + // in the list of fields in the collection (s.fieldsCollectionName) + fieldTableFeature := strings.Split(key, ".")[0] + if ContainsElements(fieldTableFeature, s.fieldsCollectionName) { + if value == nil && expectedValue == "" { + // The value of the field in MongoDB is null and expectedValue is [EMPTY] + continue + } else if value != expectedValue { + return false, fmt.Errorf("error: mismatch of mongo field '%s': expected '%s',"+ + "actual '%s'", key, expectedValue, value) + } + } else { + return false, fmt.Errorf("error: the field '%s': does not exist in '%s' collection", + key, s.collection.Name()) + } + } + return true, nil +} + +// VerifyExistAndMustExistValue check if the values exist and should exist +func (s *Session) VerifyExistAndMustExistValue(exist, mustExist bool, err error) error { + // If Exist and should NOT exist or NOT exist and should exist return error + // If Exist and shoud exist OR not exist and should not exist return nil + if exist == mustExist { + return nil + } + if err != nil { + return err + } + return fmt.Errorf("error: the value DOES NOT EXIST and SHOULD, or EXIST and SHOULD NOT, " + + "in the collection") +} + +// VerifyExistID returns a boolean indicating whether the _id searched exists in the collection +func (s *Session) VerifyExistID(ctx context.Context, idCollection string) (bool, error) { + // Perform the search and get the result as singleResult + err := s.SetSingleResult(ctx, "_id", idCollection) + if err != nil { + return false, fmt.Errorf("error: searched _id '%s' does not exist in the '%s' collection", + idCollection, s.collection.Name()) + } + return true, nil +} diff --git a/steps/mongo/session_test.go b/steps/mongo/session_test.go new file mode 100644 index 00000000..245ee6a4 --- /dev/null +++ b/steps/mongo/session_test.go @@ -0,0 +1,453 @@ +// Copyright 2021 Telefonica Cybersecurity & Cloud Tech SL +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mongo + +import ( + "context" + "fmt" + "reflect" + "testing" + + "github.com/TelefonicaTC2Tech/golium" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" +) + +var ISBOOLEAN = "is_boolean" + +func TestPing(t *testing.T) { + tests := []struct { + name string + pingErr error + wantErr bool + }{ + { + name: "Ping error", + pingErr: fmt.Errorf("ping error"), + wantErr: true, + }, + { + name: "Ping done", + pingErr: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Session{} + s.MongoClientService = ClientServiceFuncMock{} + PingError = tt.pingErr + if err := s.MongoClientService.Ping(context.Background(), + nil, s.client); (err != nil) != tt.wantErr { + t.Errorf("Session.Ping() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestDatabase(t *testing.T) { + tests := []struct { + name string + database *mongo.Database + wantErr bool + }{ + { + name: "Database error", + database: nil, + wantErr: true, + }, + { + name: "Database done", + database: &mongo.Database{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Session{} + s.MongoClientService = ClientServiceFuncMock{} + DatabaseDatabase = tt.database + if d := s.MongoClientService.Database("test", s.client); (d != nil) == tt.wantErr { + t.Errorf("Session.Database() error, wantErr %v", tt.wantErr) + } + }) + } +} + +func TestDisconnect(t *testing.T) { + tests := []struct { + name string + disconnectErr error + wantErr bool + }{ + { + name: "Disconnect error", + disconnectErr: fmt.Errorf("Disconnect error"), + wantErr: true, + }, + { + name: "Disconnect done", + disconnectErr: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Session{} + s.MongoClientService = ClientServiceFuncMock{} + DisconnectError = tt.disconnectErr + if err := s.MongoClientService.Disconnect(context.Background(), + s.client); (err != nil) != tt.wantErr { + t.Errorf("Session.Disconnect() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCollectionCollection(t *testing.T) { + tests := []struct { + name string + collection *mongo.Collection + wantErr bool + }{ + { + name: "Collection error", + collection: nil, + wantErr: true, + }, + { + name: "Collection done", + collection: &mongo.Collection{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Session{} + s.MongoCollectionService = CollectionServiceFuncMock{} + CollectionCollection = tt.collection + if d := s.MongoCollectionService.Collection("test", + &mongo.Database{}); (d != nil) == tt.wantErr { + t.Errorf("Session.Collection() error, wantErr %v", tt.wantErr) + } + }) + } +} + +func TestCollectionName(t *testing.T) { + tests := []struct { + name string + nameString string + wantErr bool + }{ + { + name: "Collection name error", + nameString: "", + wantErr: true, + }, + { + name: "Collection name done", + nameString: "test-collection", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Session{} + s.collection = &mongo.Collection{} + s.MongoCollectionService = CollectionServiceFuncMock{} + NameString = tt.nameString + if n := s.MongoCollectionService.Name(s.collection); (n == "") != tt.wantErr { + t.Errorf("Session.Name() error, wantErr %v", tt.wantErr) + } + }) + } +} + +func TestCollectionFind(t *testing.T) { + tests := []struct { + name string + findCursor *mongo.Cursor + findError error + wantErr bool + }{ + { + name: "Collection find error", + findCursor: nil, + findError: fmt.Errorf("find error"), + wantErr: true, + }, + { + name: "Collection find done", + findCursor: &mongo.Cursor{}, + findError: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Session{} + s.MongoCollectionService = CollectionServiceFuncMock{} + FindCursor = tt.findCursor + FindError = tt.findError + c, _ := s.MongoCollectionService.Find(context.Background(), nil, &mongo.Collection{}) + if (c != nil) == tt.wantErr { + t.Errorf("Session.Find() error, wantErr %v", tt.wantErr) + } + }) + } +} + +func TestCollectionFindOne(t *testing.T) { + tests := []struct { + name string + findOneSingleResult *mongo.SingleResult + wantErr bool + }{ + { + name: "Collection find one error", + findOneSingleResult: nil, + wantErr: true, + }, + { + name: "Collection find one done", + findOneSingleResult: &mongo.SingleResult{}, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Session{} + s.collection = &mongo.Collection{} + s.MongoCollectionService = CollectionServiceFuncMock{} + FindOneSingleResult = tt.findOneSingleResult + if f := s.MongoCollectionService.FindOne(context.Background(), + nil, s.collection); (f != nil) == tt.wantErr { + t.Errorf("Session.FindOne() error, wantErr %v", tt.wantErr) + } + }) + } +} + +func TestCollectionInsertMany(t *testing.T) { + tests := []struct { + name string + insertManyResult *mongo.InsertManyResult + insertManyError error + wantErr bool + }{ + { + name: "Collection insert many error", + insertManyResult: nil, + insertManyError: fmt.Errorf("insert many error"), + wantErr: true, + }, + { + name: "Collection insert many done", + insertManyResult: &mongo.InsertManyResult{}, + insertManyError: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Session{} + s.collection = &mongo.Collection{} + s.MongoCollectionService = CollectionServiceFuncMock{} + InsertManyInsertManyResult = tt.insertManyResult + InsertManyError = tt.insertManyError + _, err := s.MongoCollectionService.InsertMany(context.Background(), nil, s.collection) + if (err != nil) != tt.wantErr { + t.Errorf("Session.InsertMany() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestCollectionDeletetMany(t *testing.T) { + tests := []struct { + name string + deleteResult *mongo.DeleteResult + deleteResultError error + wantErr bool + }{ + { + name: "Collection delete many error", + deleteResult: nil, + deleteResultError: fmt.Errorf("delete many error"), + wantErr: true, + }, + { + name: "Collection delete many done", + deleteResult: &mongo.DeleteResult{}, + deleteResultError: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Session{} + s.collection = &mongo.Collection{} + s.MongoCollectionService = CollectionServiceFuncMock{} + DeleteResultDeleteResult = tt.deleteResult + DeleteResultError = tt.deleteResultError + d, _ := s.MongoCollectionService.DeleteMany(context.Background(), nil, s.collection) + if (d == nil) != tt.wantErr { + t.Errorf("Session.DeleteMany() error, wantErr %v", tt.wantErr) + } + }) + } +} + +// FUNCTIONS CALLED BY STEPS + +func TestGenerateUUIDStoreItStep(t *testing.T) { + // 1-Establish a Session instance and context + s := &Session{} + ctx := golium.InitializeContext(context.Background()) + + // 2-Generating a Random UUID + t.Run("Generating a Random UUID", func(t *testing.T) { + if s.GenerateUUIDStoreItStep(ctx) != nil { + t.Errorf("Error generating a random UUID") + } + }) + + // 3-Verifying that a UUID has been created + if len(s.idCollection) != 36 { + t.Errorf("s.idCollection was not created") + } +} + +// GENERIC FUNCTIONS + +func TestContainsElements(t *testing.T) { + t.Run("Element exists in slice", func(t *testing.T) { + slice := []int{1, 2, 3, 4, 5} + element := 3 + result := ContainsElements(element, slice) + if !result { + t.Errorf("Expected %v to be in the slice, but it wasn't.", element) + } + }) + t.Run("Element does not exist in slice", func(t *testing.T) { + slice := []string{"Alcorcon", "Madrid", "Barcelona"} + element := "Valladolid" + result := ContainsElements(element, slice) + if result { + t.Errorf("Expected %v not to be in the slice, but it was.", element) + } + }) +} + +func TestGetFilter(t *testing.T) { + t.Run("Create filter with non-nil value", func(t *testing.T) { + key := "age" + value := 30 + filter := GetFilter(key, value) + expectedFilter := bson.M{"age": 30} + if !reflect.DeepEqual(filter, expectedFilter) { + t.Errorf("Expected filter: %v, but got: %v", expectedFilter, filter) + } + }) + t.Run("Create filter with nil value", func(t *testing.T) { + key := "name" + filter := GetFilter(key, nil) + expectedFilter := bson.M{"name": nil} + if !reflect.DeepEqual(filter, expectedFilter) { + t.Errorf("Expected filter: %v, but got: %v", expectedFilter, filter) + } + }) +} + +func TestGetFilterConverted(t *testing.T) { + t.Run("Convert to boolean (true)", func(t *testing.T) { + field := ISBOOLEAN + value := "true" + filter := GetFilterConverted(field, value) + expectedFilter := primitive.M{field: true} + if filter[field] != expectedFilter[field] { + t.Errorf("Expected filter: %v, but got: %v", expectedFilter, filter) + } + }) + t.Run("Convert to boolean (false)", func(t *testing.T) { + field := ISBOOLEAN + value := "false" + filter := GetFilterConverted(field, value) + expectedFilter := primitive.M{field: false} + if filter[field] != expectedFilter[field] { + t.Errorf("Expected filter: %v, but got: %v", expectedFilter, filter) + } + }) + t.Run("Convert to integer", func(t *testing.T) { + field := "quantity number" + value := "42" + filter := GetFilterConverted(field, value) + expectedFilter := primitive.M{field: 42} + if filter[field] != expectedFilter[field] { + t.Errorf("Expected filter: %v, but got: %v", expectedFilter, filter) + } + }) + t.Run("Convert to float64", func(t *testing.T) { + field := "quantity decimal number" + value := "99.99" + filter := GetFilterConverted(field, value) + expectedFilter := primitive.M{field: 99.99} + if filter[field] != expectedFilter[field] { + t.Errorf("Expected filter: %v, but got: %v", expectedFilter, filter) + } + }) + t.Run("Convert to nil (empty)", func(t *testing.T) { + field := "nil or empty" + value := EMPTY + filter := GetFilterConverted(field, value) + expectedFilter := primitive.M{field: nil} + if filter[field] != expectedFilter[field] { + t.Errorf("Expected filter: %v, but got: %v", expectedFilter, filter) + } + }) + t.Run("No conversion (string)", func(t *testing.T) { + field := "Nadie" + value := "Juan" + filter := GetFilterConverted(field, value) + expectedFilter := primitive.M{field: value} + if filter[field] != expectedFilter[field] { + t.Errorf("Expected filter: %v, but got: %v", expectedFilter, filter) + } + }) +} + +func TestGetOptionsSearchAllFields(t *testing.T) { + t.Run("Check creates an 'Options' to search all fields in the collection", func(t *testing.T) { + options := GetOptionsSearchAllFields() + if options.Projection == nil { + t.Errorf("Expected Projection to be nil, but got: %v", options.Projection) + } + }) +} + +func TestVerifyMustExist(t *testing.T) { + t.Run("exist is true", func(t *testing.T) { + if VerifyMustExist("does exist") != true { + t.Errorf("Expected true, but got false") + } + }) + t.Run("exist is false", func(t *testing.T) { + if VerifyMustExist("does not exist") != false { + t.Errorf("Expected false, but got true") + } + }) +} diff --git a/steps/mongo/steps.go b/steps/mongo/steps.go new file mode 100755 index 00000000..6ceee043 --- /dev/null +++ b/steps/mongo/steps.go @@ -0,0 +1,70 @@ +// Copyright (c) Telefónica Cybersecurity & Cloud Tech S.L. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mongo + +import ( + "context" + + "github.com/TelefonicaTC2Tech/golium" + "github.com/cucumber/godog" +) + +type Steps struct { +} + +// InitializeSteps initializes all the steps +func (us Steps) InitializeSteps(ctx context.Context, scenCtx *godog.ScenarioContext) context.Context { + // Initialize the Mongo session in the context + ctx = InitializeContext(ctx) + session := GetSession(ctx) + + scenCtx.Step(`^I connect to MongoDB$`, func(t *godog.Table) error { + return session.MongoConnectionStep(ctx, t) + }) + scenCtx.Step(`^I check that these values of the MongoDB "([^"]*)" collection with "([^"]*)" _id "([^"]*)" exist$`, func(collectionName, idCollection, exist string, t *godog.Table) error { + return session.CheckMongoValuesStep(ctx, golium.ValueAsString(ctx, collectionName), golium.ValueAsString(ctx, idCollection), golium.ValueAsString(ctx, exist), t) + }) + scenCtx.Step(`^I check that the MongoDB "([^"]*)" collection with "([^"]*)" _id "([^"]*)" exist$`, func(collectionName, idCollection, exist string) error { + return session.CheckMongoValueIDStep(ctx, golium.ValueAsString(ctx, collectionName), golium.ValueAsString(ctx, idCollection), golium.ValueAsString(ctx, exist)) + }) + scenCtx.Step(`^I check that in the MongoDB "([^"]*)" collection, "([^"]*)" field "([^"]*)" exist for the "([^"]*)" _id$`, func(collectionName, fieldSearched, exist, idCollection string) error { + return session.CheckMongoFieldNameStep(ctx, golium.ValueAsString(ctx, collectionName), golium.ValueAsString(ctx, fieldSearched), golium.ValueAsString(ctx, exist), golium.ValueAsString(ctx, idCollection)) + }) + scenCtx.Step(`^I check that in the MongoDB "([^"]*)" collection, "([^"]*)" field does not exist or is empty for the "([^"]*)" _id$`, func(collectionName, fieldSearched, idCollection string) error { + return session.CheckMongoFieldDoesNotExistOrEmptyStep(ctx, golium.ValueAsString(ctx, collectionName), golium.ValueAsString(ctx, fieldSearched), golium.ValueAsString(ctx, idCollection)) + }) + scenCtx.Step(`^I create "(\d+)" documents in the MongoDB "([^"]*)" collection$`, func(num int, collectionName string) error { + return session.CreateDocumentscollectionNameStep(ctx, num, golium.ValueAsString(ctx, collectionName)) + }) + scenCtx.Step(`^I delete documents from the MongoDB "([^"]*)" collection whose "([^"]*)" field is "([^"]*)" value$`, func(collectionName, field, value string) error { + return session.DeleteDocumentscollectionNameStep(ctx, golium.ValueAsString(ctx, collectionName), golium.ValueAsString(ctx, field), value) + }) + scenCtx.Step(`^I delete all documents from the MongoDB "([^"]*)" collection$`, func(collectionName string) error { + return session.DeleteAllDocumentscollectionNameStep(ctx, golium.ValueAsString(ctx, collectionName)) + }) + scenCtx.Step(`^I check that the number of documents in collection "([^"]*)" is "(\d+)"$`, func(collectionName string, num int) error { + return session.CheckNumberDocumentscollectionNameStep(golium.ValueAsString(ctx, collectionName), num) + }) + + scenCtx.Step(`^I generate a UUID and store it$`, func() error { + return session.GenerateUUIDStoreItStep(ctx) + }) + + scenCtx.After(func(ctx context.Context, sc *godog.Scenario, err error) (context.Context, error) { + return ctx, session.MongoDisconnectionStep() + }) + + return ctx +} diff --git a/steps/redis/steps.go b/steps/redis/steps.go index 5e67e461..448cc149 100755 --- a/steps/redis/steps.go +++ b/steps/redis/steps.go @@ -30,7 +30,7 @@ type Steps struct { // InitializeSteps initializes all the steps. func (cs Steps) InitializeSteps(ctx context.Context, scenCtx *godog.ScenarioContext) context.Context { - // Initialize the HTTP session in the context + // Initialize the Redis session in the context ctx = InitializeContext(ctx) session := GetSession(ctx) // Initialize the steps diff --git a/test/acceptance/environments/ci.yml b/test/acceptance/environments/ci.yml index 7d7ab1f6..b5e29c75 100755 --- a/test/acceptance/environments/ci.yml +++ b/test/acceptance/environments/ci.yml @@ -12,14 +12,14 @@ endpoints: posts: api-endpoint: posts/ api-key: "valid_apiKey" - files-path: "./test_data" + files-path: "./test_data" users: api-endpoint: users/ api-key: "valid_apiKey" bin-empty: api-endpoint: "" api-key: "" - files-path: "./test_data" + files-path: "./test_data" # DNS settings dns: dns.google:53 @@ -28,7 +28,10 @@ dot: tls://dns.google:853 timeout: 2000 # RabbitMQ settings -rabbitmq: amqp://guest:guest@localhost:5672/ +rabbitmq: + schema: amqp:// + credentials: guest:guest + host: "@localhost:5672/" # JWT settings signSymmetricKey: sign_symmetric_key_that_is_long_enough_for_algorithm_HS512_(with_more_than 256 bits!) @@ -117,9 +120,17 @@ elasticsearch: addresses: - http://localhost:9200 +# MongoDB settings +mongoHost: localhost:27017 +mongoUsername: mongoadmin +mongoPassword: mongoadmin +mongoAuthSource: admin +mongoDatabase: golium-demo + # Redis redis: endpoint: localhost:6379 + # s3 s3Autoclean: true s3Bucket: golium-demo diff --git a/test/acceptance/environments/local.yml b/test/acceptance/environments/local.yml index 6f222fa4..167ad383 100755 --- a/test/acceptance/environments/local.yml +++ b/test/acceptance/environments/local.yml @@ -12,14 +12,14 @@ endpoints: posts: api-endpoint: posts/ api-key: "valid_apiKey" - files-path: "./test_data" + files-path: "./test_data" users: api-endpoint: users/ api-key: "valid_apiKey" bin-empty: api-endpoint: "" api-key: "" - files-path: "./test_data" + files-path: "./test_data" # DNS settings dns: dns.google:53 @@ -28,7 +28,10 @@ dot: tls://dns.google:853 timeout: 2000 # RabbitMQ settings -rabbitmq: amqp://guest:guest@rabbit:5672/ +rabbitmq: + schema: amqp:// + credentials: guest:guest + host: "@rabbit:5672/" # JWT settings signSymmetricKey: sign_symmetric_key_that_is_long_enough_for_algorithm_HS512_(with_more_than 256 bits!) @@ -121,9 +124,17 @@ elasticsearch: addresses: - http://elasticsearch:9200 +# MongoDB settings +mongoHost: mongo:27017 +mongoUsername: mongoadmin +mongoPassword: mongoadmin +mongoAuthSource: admin +mongoDatabase: golium-demo + # Redis redis: endpoint: redis:6379 + # s3 s3Autoclean: true s3Bucket: golium-demo diff --git a/test/acceptance/features/mongo.feature b/test/acceptance/features/mongo.feature new file mode 100644 index 00000000..23ed3a90 --- /dev/null +++ b/test/acceptance/features/mongo.feature @@ -0,0 +1,159 @@ +# Copyright (c) Telefónica Cybersecurity & Cloud Tech S.L. +# SDET Team + +Feature: MongoDB client + Examples of MongoDB access and data processing. It is possible to: + - Creation of documents. + - Deletion of documents. + - Checking the number of documents. + - Verification of the existence or not of the _id in the documents. + - Checking the existence or not of certain data in documents (texts, numbers, booleans, lists, objects). + - Checking the existence or not of fields in the collection. + - Checking null, empty and non-existent values. + + + Background: + # Prepare data: Collection "example" empty and create "_id" field + Given I connect to MongoDB + | field | value | + | User | [CONF:mongoUsername] | + | Password | [CONF:mongoPassword] | + | Host | [CONF:mongoHost] | + | AuthSource | [CONF:mongoAuthSource] | + | Database | [CONF:mongoDatabase] | + And I delete all documents from the MongoDB "example" collection + And I check that the number of documents in collection "example" is "0" + And I generate a UUID and store it + + + @mongodb + Scenario: Create a collection, check it and delete it. + Given I create "2" documents in the MongoDB "test-colection" collection + Then I check that the number of documents in collection "test-colection" is "2" + When I delete documents from the MongoDB "test-colection" collection whose "fieldString" field is "Example field string 1" value + Then I check that the number of documents in collection "test-colection" is "1" + When I delete documents from the MongoDB "test-colection" collection whose "fieldString" field is "Example field string 2" value + Then I check that the number of documents in collection "test-colection" is "0" + + + @mongodb + Scenario: Creating documents in the "Example" Collection + Given I create "2" documents in the MongoDB "example" collection + Then I check that the number of documents in collection "example" is "2" + + + @mongodb + Scenario: Delete documents from the "example" collection + Given I create "10" documents in the MongoDB "example" collection + When I check that the number of documents in collection "example" is "10" + # Delete a document by searching for a string + When I delete documents from the MongoDB "example" collection whose "fieldString" field is "Example field string 1" value + Then I check that the number of documents in collection "example" is "9" + # Delete a document by searching for an int + When I delete documents from the MongoDB "example" collection whose "fieldInt" field is "2" value + Then I check that the number of documents in collection "example" is "8" + # Delete a document by searching for an item in a slice + When I delete documents from the MongoDB "example" collection whose "fieldSlice.0" field is "itemSlice_3" value + Then I check that the number of documents in collection "example" is "7" + # Delete a document by searching for a string element in a map + When I delete documents from the MongoDB "example" collection whose "fieldMap.fieldString" field is "Example field in map string 4" value + Then I check that the number of documents in collection "example" is "6" + # Delete a document by searching for an int element in a map + When I delete documents from the MongoDB "example" collection whose "fieldMap.fieldInt" field is "50" value + Then I check that the number of documents in collection "example" is "5" + # Delete a document by searching for a string element on a map within another map + When I delete documents from the MongoDB "example" collection whose "fieldMap.fieldMap2.fieldString" field is "Example field in map map string 6" value + Then I check that the number of documents in collection "example" is "4" + # Delete a document by searching for an int element on a map within another map + When I delete documents from the MongoDB "example" collection whose "fieldMap.fieldMap2.fieldInt" field is "700" value + Then I check that the number of documents in collection "example" is "3" + # Delete a document by searching for an empty item on a map within another map + When I delete documents from the MongoDB "example" collection whose "fieldMap.fieldMap2.fieldEmptyText" field is "" value + Then I check that the number of documents in collection "example" is "0" + + + @mongodb + Scenario: Check the number of documents in the "example" collection + Given I create "5" documents in the MongoDB "example" collection + Then I check that the number of documents in collection "example" is "5" + When I delete documents from the MongoDB "example" collection whose "fieldInt" field is "1" value + Then I check that the number of documents in collection "example" is "4" + + + @mongodb + Scenario: Check that the _id "[CTXT:_ID]_1" exists in the "example" collection + Given I create "1" documents in the MongoDB "example" collection + Then I check that in the MongoDB "example" collection, "_id" field "does" exist for the "[CTXT:_ID]_1" _id + + + @mongodb + Scenario: Check that the "[CTXT:_ID]_0" _id does not exist in the "example" collection + Given I create "1" documents in the MongoDB "example" collection + When I check that the number of documents in collection "example" is "1" + Then I check that in the MongoDB "example" collection, "_id" field "does not" exist for the "[CTXT:_ID]_0" _id + + + @mongodb + Scenario: Check that there is some data in the "example" collection through a table in the feature + Given I create "1" documents in the MongoDB "example" collection + Then I check that these values of the MongoDB "example" collection with "[CTXT:_ID]_1" _id "do" exist + | field | value | + # General values + | _id | [CTXT:_ID]_1 | + | fieldString | Example field string 1 | + | fieldInt | [NUMBER:1] | + | fieldFloat | [NUMBER:3.14] | + | fieldBool | [TRUE] | + # Slice with data + | fieldSlice.# | [NUMBER:3] | + | fieldSlice.0 | itemSlice_1 | + | fieldSlice.1 | itemSlice20 | + | fieldSlice.2 | itemSlice30 | + | fieldEmpty | [EMPTY] | + # Map with data + | fieldMap.fieldString | Example field in map string 1 | + | fieldMap.fieldInt | [NUMBER:10] | + | fieldMap.fieldFloat | [NUMBER:1974.1976] | + | fieldMap.fieldBool | [FALSE] | + # Slice empty in map. It is possible to compare empty items + | fieldMap.fieldSliceEmpty.# | [NUMBER:0] | + | fieldMap.fieldSliceEmpty.0 | [NULL] | + | fieldMap.fieldSliceEmpty.1 | | + | fieldMap.fieldSliceEmpty.2 | [EMPTY] | + # Map in map with similar data + | fieldMap.fieldMap2.fieldString | Example field in map map string 1 | + | fieldMap.fieldMap2.fieldInt | [NUMBER:100] | + | fieldMap.fieldMap2.fieldFloat | [NUMBER:1974.1976] | + | fieldMap.fieldMap2.fieldBool | [FALSE] | + | fieldMap.fieldMap2.fieldEmpty | [NULL] | + | fieldMap.fieldMap2.fieldEmptyText | [EMPTY] | + + + @mongodb + Scenario: Check that there is no data in the "example" collection through a table in the feature + Given I create "1" documents in the MongoDB "example" collection + And I check that the number of documents in collection "example" is "1" + Then I check that these values of the MongoDB "example" collection with "[CTXT:_ID]_1" _id "do not" exist + | field | value | + | fieldString | Value does not exist | + + + @mongodb + Scenario: Check that the "fieldString" field exists in the "example" collection + Given I create "1" documents in the MongoDB "example" collection + Then I check that in the MongoDB "example" collection, "fieldString" field "does" exist for the "[CTXT:_ID]_1" _id + + + @mongodb + Scenario: Check that the "campoInexistente" field does not exist in the "example" collection + Given I create "1" documents in the MongoDB "example" collection + And I check that the number of documents in collection "example" is "1" + Then I check that in the MongoDB "example" collection, "campoInexistente" field "does not" exist for the "[CTXT:_ID]_1" _id + And I check that in the MongoDB "example" collection, "campoInexistente" field does not exist or is empty for the "[CTXT:_ID]_1" _id + + + @mongodb + Scenario: Check that the value of the "fiedlEmpty" field in the "example" collection does not exist or is null + Given I create "1" documents in the MongoDB "example" collection + When I check that in the MongoDB "example" collection, "_id" field "does" exist for the "[CTXT:_ID]_1" _id + Then I check that in the MongoDB "example" collection, "fieldEmpty" field does not exist or is empty for the "[CTXT:_ID]_1" _id diff --git a/test/acceptance/features/rabbit.feature b/test/acceptance/features/rabbit.feature index f70bf27d..82df7891 100755 --- a/test/acceptance/features/rabbit.feature +++ b/test/acceptance/features/rabbit.feature @@ -2,139 +2,139 @@ Feature: Rabbit client @rabbit Scenario: Publish and subscribe a text message - Given the rabbit endpoint "[CONF:rabbitmq]" - And I subscribe to the rabbit topic "test-rabbit-text-properties-topic" - When I publish a message to the rabbit topic "test-rabbit-text-properties-topic" with the text + Given the rabbit endpoint "[CONF:rabbitmq.schema][CONF:rabbitmq.credentials][CONF:rabbitmq.host]" + And I subscribe to the rabbit topic "test-rabbit-text-properties-topic" + When I publish a message to the rabbit topic "test-rabbit-text-properties-topic" with the text """ This is a test message """ - Then I wait up to "3" seconds for a rabbit message with the text + Then I wait up to "3" seconds for a rabbit message with the text """ This is a test message """ @rabbit Scenario: Publish and subscribe a JSON message - Given the rabbit endpoint "[CONF:rabbitmq]" - And I subscribe to the rabbit topic "test-rabbit-json-properties-topic" - And I set standard rabbit properties - | param | value | - | ContentType | application/json | - | CorrelationId | [CTXT:CorrelationId] | - When I publish a message to the rabbit topic "test-rabbit-json-properties-topic" with the JSON properties - | param | value | - | id | abc | - | name | Golium | - Then I wait up to "3" seconds for a rabbit message with the JSON properties - | param | value | - | id | abc | - | name | Golium | + Given the rabbit endpoint "[CONF:rabbitmq.schema][CONF:rabbitmq.credentials][CONF:rabbitmq.host]" + And I subscribe to the rabbit topic "test-rabbit-json-properties-topic" + And I set standard rabbit properties + | param | value | + | ContentType | application/json | + | CorrelationId | [CTXT:CorrelationId] | + When I publish a message to the rabbit topic "test-rabbit-json-properties-topic" with the JSON properties + | param | value | + | id | abc | + | name | Golium | + Then I wait up to "3" seconds for a rabbit message with the JSON properties + | param | value | + | id | abc | + | name | Golium | @rabbit Scenario: Publish and subscribe a JSON message. Use standard properties Given I generate a UUID and store it in context "CorrelationId" - Given the rabbit endpoint "[CONF:rabbitmq]" - And I subscribe to the rabbit topic "test-rabbit-json-properties-[CTXT:CorrelationId]" - And I set standard rabbit properties - | param | value | - | ContentType | application/json | - | CorrelationId | [CTXT:CorrelationId] | - When I publish a message to the rabbit topic "test-rabbit-json-properties-[CTXT:CorrelationId]" with the JSON properties - | param | value | - | id | abc | - | name | Golium | - Then I wait up to "3" seconds for a rabbit message with the standard properties - | param | value | - | ContentType | application/json | - | CorrelationId | [CTXT:CorrelationId] | - And the rabbit message body has the JSON properties - | param | value | - | id | abc | - | name | Golium | + Given the rabbit endpoint "[CONF:rabbitmq.schema][CONF:rabbitmq.credentials][CONF:rabbitmq.host]" + And I subscribe to the rabbit topic "test-rabbit-json-properties-[CTXT:CorrelationId]" + And I set standard rabbit properties + | param | value | + | ContentType | application/json | + | CorrelationId | [CTXT:CorrelationId] | + When I publish a message to the rabbit topic "test-rabbit-json-properties-[CTXT:CorrelationId]" with the JSON properties + | param | value | + | id | abc | + | name | Golium | + Then I wait up to "3" seconds for a rabbit message with the standard properties + | param | value | + | ContentType | application/json | + | CorrelationId | [CTXT:CorrelationId] | + And the rabbit message body has the JSON properties + | param | value | + | id | abc | + | name | Golium | @rabbit Scenario: Publish and subscribe three JSON messages. Use standard properties Given I generate a UUID and store it in context "CorrelationId" - Given the rabbit endpoint "[CONF:rabbitmq]" - And I subscribe to the rabbit topic "test-rabbit-json-properties-[CTXT:CorrelationId]" - And I set standard rabbit properties - | param | value | - | ContentType | application/json | - | CorrelationId | [CTXT:CorrelationId] | - When I publish a message to the rabbit topic "test-rabbit-json-properties-[CTXT:CorrelationId]" with the JSON properties - | param | value | - | id0 | abc0 | - | name0 | Golium0 | - When I publish a message to the rabbit topic "test-rabbit-json-properties-[CTXT:CorrelationId]" with the JSON properties - | param | value | - | id1 | abc1 | - | name1 | Golium1 | - When I publish a message to the rabbit topic "test-rabbit-json-properties-[CTXT:CorrelationId]" with the JSON properties - | param | value | - | id2 | abc2 | - | name2 | Golium2 | - Then I wait up to "5" seconds for exactly "3" rabbit messages with the standard properties - | param | value | - | ContentType | application/json | - | CorrelationId | [CTXT:CorrelationId] | - And the body of the rabbit message in position "0" has the JSON properties - | param | value | - | id0 | abc0 | - | name0 | Golium0 | - And the body of the rabbit message in position "1" has the JSON properties - | param | value | - | id1 | abc1 | - | name1 | Golium1 | - And the body of the rabbit message in position "2" has the JSON properties - | param | value | - | id2 | abc2 | - | name2 | Golium2 | + Given the rabbit endpoint "[CONF:rabbitmq.schema][CONF:rabbitmq.credentials][CONF:rabbitmq.host]" + And I subscribe to the rabbit topic "test-rabbit-json-properties-[CTXT:CorrelationId]" + And I set standard rabbit properties + | param | value | + | ContentType | application/json | + | CorrelationId | [CTXT:CorrelationId] | + When I publish a message to the rabbit topic "test-rabbit-json-properties-[CTXT:CorrelationId]" with the JSON properties + | param | value | + | id0 | abc0 | + | name0 | Golium0 | + When I publish a message to the rabbit topic "test-rabbit-json-properties-[CTXT:CorrelationId]" with the JSON properties + | param | value | + | id1 | abc1 | + | name1 | Golium1 | + When I publish a message to the rabbit topic "test-rabbit-json-properties-[CTXT:CorrelationId]" with the JSON properties + | param | value | + | id2 | abc2 | + | name2 | Golium2 | + Then I wait up to "5" seconds for exactly "3" rabbit messages with the standard properties + | param | value | + | ContentType | application/json | + | CorrelationId | [CTXT:CorrelationId] | + And the body of the rabbit message in position "0" has the JSON properties + | param | value | + | id0 | abc0 | + | name0 | Golium0 | + And the body of the rabbit message in position "1" has the JSON properties + | param | value | + | id1 | abc1 | + | name1 | Golium1 | + And the body of the rabbit message in position "2" has the JSON properties + | param | value | + | id2 | abc2 | + | name2 | Golium2 | @rabbit Scenario: Publish and subscribe a JSON message with rabbit headers and standard properties - Given the rabbit endpoint "[CONF:rabbitmq]" - And I subscribe to the rabbit topic "test-rabbit-headers-properties-topic" - And I set rabbit headers - | param | value | - | Header1 | value1 | - | Header2 | value2 | - When I publish a message to the rabbit topic "test-rabbit-headers-properties-topic" with the JSON properties - | param | value | - | id | abc | - | name | Golium | - Then I wait up to "3" seconds for a rabbit message with the JSON properties - | param | value | - | id | abc | - | name | Golium | - And the rabbit message has the rabbit headers - | param | value | - | Header1 | value1 | - | Header2 | value2 | + Given the rabbit endpoint "[CONF:rabbitmq.schema][CONF:rabbitmq.credentials][CONF:rabbitmq.host]" + And I subscribe to the rabbit topic "test-rabbit-headers-properties-topic" + And I set rabbit headers + | param | value | + | Header1 | value1 | + | Header2 | value2 | + When I publish a message to the rabbit topic "test-rabbit-headers-properties-topic" with the JSON properties + | param | value | + | id | abc | + | name | Golium | + Then I wait up to "3" seconds for a rabbit message with the JSON properties + | param | value | + | id | abc | + | name | Golium | + And the rabbit message has the rabbit headers + | param | value | + | Header1 | value1 | + | Header2 | value2 | @rabbit Scenario: Publish and subscribe a text message with rabbit headers - Given the rabbit endpoint "[CONF:rabbitmq]" - And I subscribe to the rabbit topic "test-rabbit-headers-topic" - And I set rabbit headers - | param | value | - | Header1 | value1 | - | Header2 | value2 | - When I publish a message to the rabbit topic "test-rabbit-headers-topic" with the text + Given the rabbit endpoint "[CONF:rabbitmq.schema][CONF:rabbitmq.credentials][CONF:rabbitmq.host]" + And I subscribe to the rabbit topic "test-rabbit-headers-topic" + And I set rabbit headers + | param | value | + | Header1 | value1 | + | Header2 | value2 | + When I publish a message to the rabbit topic "test-rabbit-headers-topic" with the text """ """ - Then I wait up to "3" seconds for a rabbit message with the text + Then I wait up to "3" seconds for a rabbit message with the text """ """ - And the rabbit message has the rabbit headers - | param | value | - | Header1 | value1 | - | Header2 | value2 | + And the rabbit message has the rabbit headers + | param | value | + | Header1 | value1 | + | Header2 | value2 | @rabbit Scenario: Subscribe and waits for no message Given I generate a UUID and store it in context "CorrelationId" - Given the rabbit endpoint "[CONF:rabbitmq]" - When I subscribe to the rabbit topic "test-rabbit-empty-topic" - Then I wait up to "3" seconds without a rabbit message with the standard properties - | param | value | - | CorrelationId | [CTXT:CorrelationId] | + Given the rabbit endpoint "[CONF:rabbitmq.schema][CONF:rabbitmq.credentials][CONF:rabbitmq.host]" + When I subscribe to the rabbit topic "test-rabbit-empty-topic" + Then I wait up to "3" seconds without a rabbit message with the standard properties + | param | value | + | CorrelationId | [CTXT:CorrelationId] | diff --git a/test/acceptance/golium_test.go b/test/acceptance/golium_test.go index 91f575ef..83ee4de1 100755 --- a/test/acceptance/golium_test.go +++ b/test/acceptance/golium_test.go @@ -26,6 +26,7 @@ import ( "github.com/TelefonicaTC2Tech/golium/steps/elasticsearch" "github.com/TelefonicaTC2Tech/golium/steps/http" "github.com/TelefonicaTC2Tech/golium/steps/jwt" + "github.com/TelefonicaTC2Tech/golium/steps/mongo" "github.com/TelefonicaTC2Tech/golium/steps/rabbit" "github.com/TelefonicaTC2Tech/golium/steps/redis" @@ -56,6 +57,7 @@ func InitializeScenario(ctx context.Context, scenarioCtx *godog.ScenarioContext) common.Steps{}, jwt.Steps{}, dns.Steps{}, + mongo.Steps{}, redis.Steps{}, rabbit.Steps{}, mockhttp.Steps{},