diff --git a/.gitignore b/.gitignore index a676215..ddc19ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .idea +.vscode bin +.coverage +allure-results \ No newline at end of file diff --git a/.golangci.yaml b/.golangci.yaml index 8441a0d..daa27d5 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -13,6 +13,10 @@ output: print-linter-name: true linters-settings: + gocyclo: + min-complexity: 15 + gocognit: + min-complexity: 20 govet: enable: - shadow @@ -28,6 +32,8 @@ linters: # - dupl - it's very slow, enable if you really know why you need it - errcheck - goconst + - gocognit + - gocyclo - goimports - gosec - govet diff --git a/Makefile b/Makefile index a289261..87b046e 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,27 @@ include make/lint.mk include make/build.mk +include make/coverage.mk +include make/generate.mk + +PKGS = $(shell go list ./... | grep -vE 'mock|config|generated|header|document|internal/pb') +INTEGRATION_TAG = integration lint: cart-lint loms-lint notifier-lint comments-lint build: cart-build loms-build notifier-build comments-build +coverage: cart-coverage loms-coverage notifier-coverage comments-coverage + +generate: cart-generate loms-generate notifier-generate comments-generate + run-all: echo "starting build" docker-compose up --build + +integration-test: + ALLURE_OUTPUT_PATH=$(shell pwd) go test -v -tags=$(INTEGRATION_TAG) ./cart/tests/integration + +bench: + go test -bench=BenchmarkInMemoryRepository -benchmem ./cart/internal/domain/cart/repository -benchtime 3s + +.PHONY: lint, coverage diff --git a/cart/Makefile b/cart/Makefile index 5dabc33..0e71e38 100644 --- a/cart/Makefile +++ b/cart/Makefile @@ -1,8 +1,14 @@ BINDIR=${CURDIR}/bin PACKAGE=route256/cart +PKGS = $(shell go list ./... | grep -vE 'mock|config|generated|header|document|internal/pb') + bindir: mkdir -p ${BINDIR} build: bindir echo "build cart" + +coverage: + @go test -race -coverprofile=coverage.txt -covermode=atomic $(PKGS) + @go tool cover -html=coverage.txt -o coverage.html diff --git a/cart/go.mod b/cart/go.mod index 04a1ed6..902f551 100644 --- a/cart/go.mod +++ b/cart/go.mod @@ -1,25 +1,79 @@ module route256/cart -go 1.23.1 +go 1.23.9 require ( github.com/rs/zerolog v1.34.0 + github.com/stretchr/testify v1.10.0 gopkg.in/yaml.v3 v3.0.1 ) require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v28.0.1+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/ebitengine/purego v0.8.2 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/leodido/go-urn v1.4.0 // indirect - golang.org/x/crypto v0.33.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/text v0.22.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.10 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/user v0.1.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/ozontech/allure-go/pkg/allure v0.6.14 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/shirou/gopsutil/v4 v4.25.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/sdk v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/proto/otlp v1.6.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/text v0.24.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect + google.golang.org/protobuf v1.36.6 // indirect ) require ( github.com/go-playground/validator/v10 v10.26.0 + github.com/gojuno/minimock/v3 v3.4.5 github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect - golang.org/x/sys v0.30.0 // indirect + github.com/ozontech/allure-go/pkg/framework v0.6.34 + github.com/testcontainers/testcontainers-go v0.37.0 + golang.org/x/sys v0.33.0 // indirect ) diff --git a/cart/go.sum b/cart/go.sum index 442862f..d738f81 100644 --- a/cart/go.sum +++ b/cart/go.sum @@ -1,6 +1,48 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0= +github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= +github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +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= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= @@ -8,30 +50,166 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gojuno/minimock/v3 v3.4.5 h1:Jcb0tEYZvVlQNtAAYpg3jCOoSwss2c1/rNugYTzj304= +github.com/gojuno/minimock/v3 v3.4.5/go.mod h1:o9F8i2IT8v3yirA7mmdpNGzh1WNesm6iQakMtQV6KiE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +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/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +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.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/ozontech/allure-go/pkg/allure v0.6.14 h1:lDamtSF+WtHQLg2+qQYijtC4Fk3KLGb6txNxxTZwUGc= +github.com/ozontech/allure-go/pkg/allure v0.6.14/go.mod h1:4oEG2yq+DGOzJS/ZjPc87C/mx3tAnlYpYonk77Ru/vQ= +github.com/ozontech/allure-go/pkg/framework v0.6.34 h1:IjM65Y3JP7ale7Ug3aBnFV4+c1NYYBCrgl/VrtEd/FY= +github.com/ozontech/allure-go/pkg/framework v0.6.34/go.mod h1:oISDLE6Tfww35TBQz+1nrtbLtyBqR6ELxOtJ+MVjHOw= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs= +github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/testcontainers/testcontainers-go v0.37.0 h1:L2Qc0vkTw2EHWQ08djon0D2uw7Z/PtHS/QzZZ5Ra/hg= +github.com/testcontainers/testcontainers-go v0.37.0/go.mod h1:QPzbxZhQ6Bclip9igjLFj6z0hs01bU8lrl2dHQmgFGM= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0 h1:dNzwXjZKpMpE2JhmO+9HsPl42NIXFIFSUSSs0fiqra0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.36.0/go.mod h1:90PoxvaEB5n6AOdZvi+yWJQoE95U8Dhhw2bSyRqnTD0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= +go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= +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.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +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-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +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-20250428153025-10db94c68c34 h1:0PeQib/pH3nB/5pEmFeVQJotzGohV0dq4Vcp09H5yhE= +google.golang.org/genproto/googleapis/api v0.0.0-20250428153025-10db94c68c34/go.mod h1:0awUlEkap+Pb1UMeJwJQQAdJQrt3moU7J2moTy69irI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= +google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/cart/internal/app/app.go b/cart/internal/app/app.go index 7dc9bfe..3ce8ba5 100644 --- a/cart/internal/app/app.go +++ b/cart/internal/app/app.go @@ -52,7 +52,7 @@ func NewApp(configPath string) (*App, error) { app := &App{ config: c, } - app.server.Handler = app.bootstrapHandlers() + app.server.Handler = app.BootstrapHandlers(app.setupCartService()) return app, nil } @@ -70,7 +70,7 @@ func (app *App) ListenAndServe() error { return app.server.Serve(l) } -func (app *App) bootstrapHandlers() http.Handler { +func (app *App) setupCartService() *service.CartService { transport := http.DefaultTransport transport = round_trippers.NewLogRoundTripper(transport) transport = round_trippers.NewRetryRoundTripper(transport, productsRetryAttemptsDefault, productsInitialDelaySecDefault) @@ -88,8 +88,11 @@ func (app *App) bootstrapHandlers() http.Handler { const userCartCap = 100 cartRepository := repository.NewInMemoryRepository(userCartCap) - cartService := service.NewCartService(cartRepository, productService) + return service.NewCartService(cartRepository, productService) +} + +func (app *App) BootstrapHandlers(cartService *service.CartService) http.Handler { s := server.NewServer(cartService) mx := http.NewServeMux() diff --git a/cart/internal/app/server/get_cart_handler.go b/cart/internal/app/server/get_cart_handler.go index bb5182a..2fa01a9 100644 --- a/cart/internal/app/server/get_cart_handler.go +++ b/cart/internal/app/server/get_cart_handler.go @@ -42,7 +42,7 @@ func (s *Server) GetItemsByUserIDHandler(w http.ResponseWriter, r *http.Request) if err != nil { makeErrorResponse(w, err, http.StatusNotFound) - log.Trace().Err(err).Msgf("cartService.GetItemsByUserID: %d", http.StatusInternalServerError) + log.Trace().Err(err).Msgf("cartService.GetItemsByUserID: %d", http.StatusNotFound) return } diff --git a/cart/internal/domain/cart/repository/in_memory_repository_bench_test.go b/cart/internal/domain/cart/repository/in_memory_repository_bench_test.go new file mode 100644 index 0000000..7af2215 --- /dev/null +++ b/cart/internal/domain/cart/repository/in_memory_repository_bench_test.go @@ -0,0 +1,59 @@ +package repository + +import ( + "context" + "testing" + + "route256/cart/internal/domain/entity" + "route256/cart/internal/domain/model" +) + +var ( + ctx = context.Background() + testItem = &model.Item{ + Product: &model.Product{ + Sku: 1, + Name: "bench", + Price: 100, + }, + Count: 1, + } +) + +func BenchmarkInMemoryRepository_AddItem(b *testing.B) { + repo := NewInMemoryRepository(1024) + + b.ResetTimer() + for i := 1; i < b.N; i++ { + uid := entity.UID(i % 1000000) + _ = repo.AddItem(ctx, uid, testItem) + } +} + +func BenchmarkInMemoryRepository_AddItemParallel(b *testing.B) { + repo := NewInMemoryRepository(1024) + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + var i int + for pb.Next() { + uid := entity.UID(i % 1000000) + _ = repo.AddItem(ctx, uid, testItem) + i++ + } + }) +} + +func BenchmarkInMemoryRepository_DeleteItem(b *testing.B) { + repo := NewInMemoryRepository(1024) + + for i := 1; i < 1_000; i++ { + _ = repo.AddItem(ctx, entity.UID(i), testItem) + } + + b.ResetTimer() + for i := 1; i < b.N; i++ { + uid := entity.UID(i % 1000000) + _ = repo.DeleteItem(ctx, uid, entity.Sku(testItem.Product.Sku)) + } +} diff --git a/cart/internal/domain/cart/repository/in_memory_repository_test.go b/cart/internal/domain/cart/repository/in_memory_repository_test.go new file mode 100644 index 0000000..5542a7b --- /dev/null +++ b/cart/internal/domain/cart/repository/in_memory_repository_test.go @@ -0,0 +1,269 @@ +package repository + +import ( + "context" + "testing" + + "route256/cart/internal/domain/entity" + "route256/cart/internal/domain/model" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAddItem(t *testing.T) { + t.Parallel() + + var ( + repo = NewInMemoryRepository(10) + uid = entity.UID(1337) + sku = entity.Sku(123) + item = &model.Item{ + Product: &model.Product{ + Name: "", + Price: 0, + Sku: sku, + }, + Count: 2, + } + ctx = context.Background() + ) + + err := repo.AddItem(ctx, uid, item) + require.NoError(t, err, "check add item error") + + assert.Len(t, repo.storage, 1, "check map length") + assert.Len(t, repo.storage[uid].Items, 1, "check list length") + assert.Equal(t, uint32(2), repo.storage[uid].ItemCount[sku], "check count value") + assert.Equal(t, uid, repo.storage[uid].UserID, "check uid") +} + +func TestAddItemMultiple(t *testing.T) { + t.Parallel() + + var ( + repo = NewInMemoryRepository(10) + uid = entity.UID(1337) + item1 = &model.Item{ + Product: &model.Product{ + Name: "", + Price: 0, + Sku: 1, + }, + Count: 1, + } + item2 = &model.Item{ + Product: &model.Product{ + Name: "", + Price: 0, + Sku: 2, + }, + Count: 2, + } + ctx = context.Background() + ) + + err := repo.AddItem(ctx, uid, item1) + require.NoError(t, err, "check add item error") + + err = repo.AddItem(ctx, uid, item2) + require.NoError(t, err, "check add item error") + + assert.Len(t, repo.storage, 1, "check map length") + assert.Len(t, repo.storage[uid].Items, 2, "check list length") + assert.Equal(t, uint32(1), repo.storage[uid].ItemCount[1], "check count value of item 1") + assert.Equal(t, uint32(2), repo.storage[uid].ItemCount[2], "check count value of item 2") + assert.Equal(t, uid, repo.storage[uid].UserID, "check uid") +} + +func TestGetItemsByUserID(t *testing.T) { + t.Parallel() + + var ( + repo = NewInMemoryRepository(10) + uid = entity.UID(1337) + item1 = &model.Item{ + Product: &model.Product{ + Name: "", + Price: 0, + Sku: 1, + }, + Count: 1, + } + item2 = &model.Item{ + Product: &model.Product{ + Name: "", + Price: 0, + Sku: 2, + }, + Count: 2, + } + ctx = context.Background() + ) + + err := repo.AddItem(ctx, uid, item1) + require.NoError(t, err, "check add item error") + + err = repo.AddItem(ctx, uid, item2) + require.NoError(t, err, "check add item error") + + cart, err := repo.GetItemsByUserID(ctx, uid) + require.NoError(t, err, "check get items error") + + require.Equal(t, uid, cart.UserID, "check cart uid") + require.Len(t, cart.Items, 2, "check cart len") + require.Equal(t, uint32(1), cart.ItemCount[item1.Product.Sku], "check item 1 count") + require.Equal(t, uint32(2), cart.ItemCount[item2.Product.Sku], "check item 2 count") +} + +func TestGetItemsByUserIDNoCart(t *testing.T) { + t.Parallel() + + var ( + repo = NewInMemoryRepository(10) + uid = entity.UID(1337) + ctx = context.Background() + ) + + cart, err := repo.GetItemsByUserID(ctx, uid) + require.NoError(t, err, "check get items error") + + require.Equal(t, entity.Cart{}, cart, "check cart") +} + +func TestDeleteItem(t *testing.T) { + t.Parallel() + + var ( + repo = NewInMemoryRepository(10) + uid = entity.UID(1337) + sku = entity.Sku(123) + item = &model.Item{ + Product: &model.Product{ + Name: "", + Price: 0, + Sku: sku, + }, + Count: 2, + } + ctx = context.Background() + ) + + err := repo.AddItem(ctx, uid, item) + require.NoError(t, err, "check add item error") + + assert.Len(t, repo.storage, 1, "check map length") + assert.Len(t, repo.storage[uid].Items, 1, "check list length") + + err = repo.DeleteItem(ctx, uid, item.Product.Sku) + require.NoError(t, err, "check delete item error") + + assert.Len(t, repo.storage[uid].Items, 0, "check list length") +} + +func TestDeleteItemUnknownUser(t *testing.T) { + t.Parallel() + + var ( + repo = NewInMemoryRepository(10) + uid = entity.UID(1337) + sku = entity.Sku(123) + ctx = context.Background() + ) + + err := repo.DeleteItem(ctx, uid, sku) + require.ErrorIs(t, err, model.ErrCartNotFound, "check cart not found err") +} + +func TestDeleteItemUnknownItem(t *testing.T) { + t.Parallel() + + var ( + repo = NewInMemoryRepository(10) + uid = entity.UID(1337) + sku = entity.Sku(123) + item = &model.Item{ + Product: &model.Product{ + Name: "", + Price: 0, + Sku: sku, + }, + Count: 2, + } + ctx = context.Background() + ) + + err := repo.AddItem(ctx, uid, item) + require.NoError(t, err, "check add item error") + + assert.Len(t, repo.storage, 1, "check map length") + assert.Len(t, repo.storage[uid].Items, 1, "check list length") + + err = repo.DeleteItem(ctx, uid, 1) + require.ErrorIs(t, err, model.ErrItemNotFoundInCart, "check delete item error") + + assert.Len(t, repo.storage[uid].Items, 1, "check list length") +} + +func TestDeleteItemEmptyCart(t *testing.T) { + t.Parallel() + + var ( + repo = NewInMemoryRepository(10) + uid = entity.UID(1337) + sku = entity.Sku(123) + item = &model.Item{ + Product: &model.Product{ + Name: "", + Price: 0, + Sku: sku, + }, + Count: 2, + } + ctx = context.Background() + ) + + err := repo.AddItem(ctx, uid, item) + require.NoError(t, err, "check add item error") + + assert.Len(t, repo.storage, 1, "check map length") + assert.Len(t, repo.storage[uid].Items, 1, "check list length") + + err = repo.DeleteItem(ctx, uid, item.Product.Sku) + require.NoError(t, err, "check delete item error") + + assert.Len(t, repo.storage[uid].Items, 0, "check list length") + + err = repo.DeleteItem(ctx, uid, item.Product.Sku) + require.NoError(t, err, "check empty cart no err") +} + +func TestDeleteItemsByUserID(t *testing.T) { + t.Parallel() + + var ( + repo = NewInMemoryRepository(10) + uid = entity.UID(1337) + sku = entity.Sku(123) + item = &model.Item{ + Product: &model.Product{ + Name: "", + Price: 0, + Sku: sku, + }, + Count: 2, + } + ctx = context.Background() + ) + + err := repo.AddItem(ctx, uid, item) + require.NoError(t, err, "check add item error") + + assert.Len(t, repo.storage, 1, "check map length") + assert.Len(t, repo.storage[uid].Items, 1, "check list length") + + err = repo.DeleteItemsByUserID(ctx, uid) + require.NoError(t, err, "check delete items error") + + assert.Len(t, repo.storage, 0, "check storage length") +} diff --git a/cart/internal/domain/cart/service/mock/repository_mock.go b/cart/internal/domain/cart/service/mock/repository_mock.go new file mode 100644 index 0000000..54ab2eb --- /dev/null +++ b/cart/internal/domain/cart/service/mock/repository_mock.go @@ -0,0 +1,1546 @@ +// Code generated by http://github.com/gojuno/minimock (v3.4.5). DO NOT EDIT. + +package mock + +//go:generate minimock -i route256/cart/internal/domain/cart/service.Repository -o repository_mock.go -n RepositoryMock -p mock + +import ( + "context" + "route256/cart/internal/domain/entity" + "route256/cart/internal/domain/model" + "sync" + mm_atomic "sync/atomic" + mm_time "time" + + "github.com/gojuno/minimock/v3" +) + +// RepositoryMock implements mm_service.Repository +type RepositoryMock struct { + t minimock.Tester + finishOnce sync.Once + + funcAddItem func(ctx context.Context, userID entity.UID, item *model.Item) (err error) + funcAddItemOrigin string + inspectFuncAddItem func(ctx context.Context, userID entity.UID, item *model.Item) + afterAddItemCounter uint64 + beforeAddItemCounter uint64 + AddItemMock mRepositoryMockAddItem + + funcDeleteItem func(ctx context.Context, userID entity.UID, sku entity.Sku) (err error) + funcDeleteItemOrigin string + inspectFuncDeleteItem func(ctx context.Context, userID entity.UID, sku entity.Sku) + afterDeleteItemCounter uint64 + beforeDeleteItemCounter uint64 + DeleteItemMock mRepositoryMockDeleteItem + + funcDeleteItemsByUserID func(ctx context.Context, userID entity.UID) (err error) + funcDeleteItemsByUserIDOrigin string + inspectFuncDeleteItemsByUserID func(ctx context.Context, userID entity.UID) + afterDeleteItemsByUserIDCounter uint64 + beforeDeleteItemsByUserIDCounter uint64 + DeleteItemsByUserIDMock mRepositoryMockDeleteItemsByUserID + + funcGetItemsByUserID func(ctx context.Context, userID entity.UID) (c2 entity.Cart, err error) + funcGetItemsByUserIDOrigin string + inspectFuncGetItemsByUserID func(ctx context.Context, userID entity.UID) + afterGetItemsByUserIDCounter uint64 + beforeGetItemsByUserIDCounter uint64 + GetItemsByUserIDMock mRepositoryMockGetItemsByUserID +} + +// NewRepositoryMock returns a mock for mm_service.Repository +func NewRepositoryMock(t minimock.Tester) *RepositoryMock { + m := &RepositoryMock{t: t} + + if controller, ok := t.(minimock.MockController); ok { + controller.RegisterMocker(m) + } + + m.AddItemMock = mRepositoryMockAddItem{mock: m} + m.AddItemMock.callArgs = []*RepositoryMockAddItemParams{} + + m.DeleteItemMock = mRepositoryMockDeleteItem{mock: m} + m.DeleteItemMock.callArgs = []*RepositoryMockDeleteItemParams{} + + m.DeleteItemsByUserIDMock = mRepositoryMockDeleteItemsByUserID{mock: m} + m.DeleteItemsByUserIDMock.callArgs = []*RepositoryMockDeleteItemsByUserIDParams{} + + m.GetItemsByUserIDMock = mRepositoryMockGetItemsByUserID{mock: m} + m.GetItemsByUserIDMock.callArgs = []*RepositoryMockGetItemsByUserIDParams{} + + t.Cleanup(m.MinimockFinish) + + return m +} + +type mRepositoryMockAddItem struct { + optional bool + mock *RepositoryMock + defaultExpectation *RepositoryMockAddItemExpectation + expectations []*RepositoryMockAddItemExpectation + + callArgs []*RepositoryMockAddItemParams + mutex sync.RWMutex + + expectedInvocations uint64 + expectedInvocationsOrigin string +} + +// RepositoryMockAddItemExpectation specifies expectation struct of the Repository.AddItem +type RepositoryMockAddItemExpectation struct { + mock *RepositoryMock + params *RepositoryMockAddItemParams + paramPtrs *RepositoryMockAddItemParamPtrs + expectationOrigins RepositoryMockAddItemExpectationOrigins + results *RepositoryMockAddItemResults + returnOrigin string + Counter uint64 +} + +// RepositoryMockAddItemParams contains parameters of the Repository.AddItem +type RepositoryMockAddItemParams struct { + ctx context.Context + userID entity.UID + item *model.Item +} + +// RepositoryMockAddItemParamPtrs contains pointers to parameters of the Repository.AddItem +type RepositoryMockAddItemParamPtrs struct { + ctx *context.Context + userID *entity.UID + item **model.Item +} + +// RepositoryMockAddItemResults contains results of the Repository.AddItem +type RepositoryMockAddItemResults struct { + err error +} + +// RepositoryMockAddItemOrigins contains origins of expectations of the Repository.AddItem +type RepositoryMockAddItemExpectationOrigins struct { + origin string + originCtx string + originUserID string + originItem string +} + +// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning +// the test will fail minimock's automatic final call check if the mocked method was not called at least once. +// Optional() makes method check to work in '0 or more' mode. +// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to +// catch the problems when the expected method call is totally skipped during test run. +func (mmAddItem *mRepositoryMockAddItem) Optional() *mRepositoryMockAddItem { + mmAddItem.optional = true + return mmAddItem +} + +// Expect sets up expected params for Repository.AddItem +func (mmAddItem *mRepositoryMockAddItem) Expect(ctx context.Context, userID entity.UID, item *model.Item) *mRepositoryMockAddItem { + if mmAddItem.mock.funcAddItem != nil { + mmAddItem.mock.t.Fatalf("RepositoryMock.AddItem mock is already set by Set") + } + + if mmAddItem.defaultExpectation == nil { + mmAddItem.defaultExpectation = &RepositoryMockAddItemExpectation{} + } + + if mmAddItem.defaultExpectation.paramPtrs != nil { + mmAddItem.mock.t.Fatalf("RepositoryMock.AddItem mock is already set by ExpectParams functions") + } + + mmAddItem.defaultExpectation.params = &RepositoryMockAddItemParams{ctx, userID, item} + mmAddItem.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1) + for _, e := range mmAddItem.expectations { + if minimock.Equal(e.params, mmAddItem.defaultExpectation.params) { + mmAddItem.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmAddItem.defaultExpectation.params) + } + } + + return mmAddItem +} + +// ExpectCtxParam1 sets up expected param ctx for Repository.AddItem +func (mmAddItem *mRepositoryMockAddItem) ExpectCtxParam1(ctx context.Context) *mRepositoryMockAddItem { + if mmAddItem.mock.funcAddItem != nil { + mmAddItem.mock.t.Fatalf("RepositoryMock.AddItem mock is already set by Set") + } + + if mmAddItem.defaultExpectation == nil { + mmAddItem.defaultExpectation = &RepositoryMockAddItemExpectation{} + } + + if mmAddItem.defaultExpectation.params != nil { + mmAddItem.mock.t.Fatalf("RepositoryMock.AddItem mock is already set by Expect") + } + + if mmAddItem.defaultExpectation.paramPtrs == nil { + mmAddItem.defaultExpectation.paramPtrs = &RepositoryMockAddItemParamPtrs{} + } + mmAddItem.defaultExpectation.paramPtrs.ctx = &ctx + mmAddItem.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1) + + return mmAddItem +} + +// ExpectUserIDParam2 sets up expected param userID for Repository.AddItem +func (mmAddItem *mRepositoryMockAddItem) ExpectUserIDParam2(userID entity.UID) *mRepositoryMockAddItem { + if mmAddItem.mock.funcAddItem != nil { + mmAddItem.mock.t.Fatalf("RepositoryMock.AddItem mock is already set by Set") + } + + if mmAddItem.defaultExpectation == nil { + mmAddItem.defaultExpectation = &RepositoryMockAddItemExpectation{} + } + + if mmAddItem.defaultExpectation.params != nil { + mmAddItem.mock.t.Fatalf("RepositoryMock.AddItem mock is already set by Expect") + } + + if mmAddItem.defaultExpectation.paramPtrs == nil { + mmAddItem.defaultExpectation.paramPtrs = &RepositoryMockAddItemParamPtrs{} + } + mmAddItem.defaultExpectation.paramPtrs.userID = &userID + mmAddItem.defaultExpectation.expectationOrigins.originUserID = minimock.CallerInfo(1) + + return mmAddItem +} + +// ExpectItemParam3 sets up expected param item for Repository.AddItem +func (mmAddItem *mRepositoryMockAddItem) ExpectItemParam3(item *model.Item) *mRepositoryMockAddItem { + if mmAddItem.mock.funcAddItem != nil { + mmAddItem.mock.t.Fatalf("RepositoryMock.AddItem mock is already set by Set") + } + + if mmAddItem.defaultExpectation == nil { + mmAddItem.defaultExpectation = &RepositoryMockAddItemExpectation{} + } + + if mmAddItem.defaultExpectation.params != nil { + mmAddItem.mock.t.Fatalf("RepositoryMock.AddItem mock is already set by Expect") + } + + if mmAddItem.defaultExpectation.paramPtrs == nil { + mmAddItem.defaultExpectation.paramPtrs = &RepositoryMockAddItemParamPtrs{} + } + mmAddItem.defaultExpectation.paramPtrs.item = &item + mmAddItem.defaultExpectation.expectationOrigins.originItem = minimock.CallerInfo(1) + + return mmAddItem +} + +// Inspect accepts an inspector function that has same arguments as the Repository.AddItem +func (mmAddItem *mRepositoryMockAddItem) Inspect(f func(ctx context.Context, userID entity.UID, item *model.Item)) *mRepositoryMockAddItem { + if mmAddItem.mock.inspectFuncAddItem != nil { + mmAddItem.mock.t.Fatalf("Inspect function is already set for RepositoryMock.AddItem") + } + + mmAddItem.mock.inspectFuncAddItem = f + + return mmAddItem +} + +// Return sets up results that will be returned by Repository.AddItem +func (mmAddItem *mRepositoryMockAddItem) Return(err error) *RepositoryMock { + if mmAddItem.mock.funcAddItem != nil { + mmAddItem.mock.t.Fatalf("RepositoryMock.AddItem mock is already set by Set") + } + + if mmAddItem.defaultExpectation == nil { + mmAddItem.defaultExpectation = &RepositoryMockAddItemExpectation{mock: mmAddItem.mock} + } + mmAddItem.defaultExpectation.results = &RepositoryMockAddItemResults{err} + mmAddItem.defaultExpectation.returnOrigin = minimock.CallerInfo(1) + return mmAddItem.mock +} + +// Set uses given function f to mock the Repository.AddItem method +func (mmAddItem *mRepositoryMockAddItem) Set(f func(ctx context.Context, userID entity.UID, item *model.Item) (err error)) *RepositoryMock { + if mmAddItem.defaultExpectation != nil { + mmAddItem.mock.t.Fatalf("Default expectation is already set for the Repository.AddItem method") + } + + if len(mmAddItem.expectations) > 0 { + mmAddItem.mock.t.Fatalf("Some expectations are already set for the Repository.AddItem method") + } + + mmAddItem.mock.funcAddItem = f + mmAddItem.mock.funcAddItemOrigin = minimock.CallerInfo(1) + return mmAddItem.mock +} + +// When sets expectation for the Repository.AddItem which will trigger the result defined by the following +// Then helper +func (mmAddItem *mRepositoryMockAddItem) When(ctx context.Context, userID entity.UID, item *model.Item) *RepositoryMockAddItemExpectation { + if mmAddItem.mock.funcAddItem != nil { + mmAddItem.mock.t.Fatalf("RepositoryMock.AddItem mock is already set by Set") + } + + expectation := &RepositoryMockAddItemExpectation{ + mock: mmAddItem.mock, + params: &RepositoryMockAddItemParams{ctx, userID, item}, + expectationOrigins: RepositoryMockAddItemExpectationOrigins{origin: minimock.CallerInfo(1)}, + } + mmAddItem.expectations = append(mmAddItem.expectations, expectation) + return expectation +} + +// Then sets up Repository.AddItem return parameters for the expectation previously defined by the When method +func (e *RepositoryMockAddItemExpectation) Then(err error) *RepositoryMock { + e.results = &RepositoryMockAddItemResults{err} + return e.mock +} + +// Times sets number of times Repository.AddItem should be invoked +func (mmAddItem *mRepositoryMockAddItem) Times(n uint64) *mRepositoryMockAddItem { + if n == 0 { + mmAddItem.mock.t.Fatalf("Times of RepositoryMock.AddItem mock can not be zero") + } + mm_atomic.StoreUint64(&mmAddItem.expectedInvocations, n) + mmAddItem.expectedInvocationsOrigin = minimock.CallerInfo(1) + return mmAddItem +} + +func (mmAddItem *mRepositoryMockAddItem) invocationsDone() bool { + if len(mmAddItem.expectations) == 0 && mmAddItem.defaultExpectation == nil && mmAddItem.mock.funcAddItem == nil { + return true + } + + totalInvocations := mm_atomic.LoadUint64(&mmAddItem.mock.afterAddItemCounter) + expectedInvocations := mm_atomic.LoadUint64(&mmAddItem.expectedInvocations) + + return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) +} + +// AddItem implements mm_service.Repository +func (mmAddItem *RepositoryMock) AddItem(ctx context.Context, userID entity.UID, item *model.Item) (err error) { + mm_atomic.AddUint64(&mmAddItem.beforeAddItemCounter, 1) + defer mm_atomic.AddUint64(&mmAddItem.afterAddItemCounter, 1) + + mmAddItem.t.Helper() + + if mmAddItem.inspectFuncAddItem != nil { + mmAddItem.inspectFuncAddItem(ctx, userID, item) + } + + mm_params := RepositoryMockAddItemParams{ctx, userID, item} + + // Record call args + mmAddItem.AddItemMock.mutex.Lock() + mmAddItem.AddItemMock.callArgs = append(mmAddItem.AddItemMock.callArgs, &mm_params) + mmAddItem.AddItemMock.mutex.Unlock() + + for _, e := range mmAddItem.AddItemMock.expectations { + if minimock.Equal(*e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.err + } + } + + if mmAddItem.AddItemMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmAddItem.AddItemMock.defaultExpectation.Counter, 1) + mm_want := mmAddItem.AddItemMock.defaultExpectation.params + mm_want_ptrs := mmAddItem.AddItemMock.defaultExpectation.paramPtrs + + mm_got := RepositoryMockAddItemParams{ctx, userID, item} + + if mm_want_ptrs != nil { + + if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { + mmAddItem.t.Errorf("RepositoryMock.AddItem got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmAddItem.AddItemMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + } + + if mm_want_ptrs.userID != nil && !minimock.Equal(*mm_want_ptrs.userID, mm_got.userID) { + mmAddItem.t.Errorf("RepositoryMock.AddItem got unexpected parameter userID, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmAddItem.AddItemMock.defaultExpectation.expectationOrigins.originUserID, *mm_want_ptrs.userID, mm_got.userID, minimock.Diff(*mm_want_ptrs.userID, mm_got.userID)) + } + + if mm_want_ptrs.item != nil && !minimock.Equal(*mm_want_ptrs.item, mm_got.item) { + mmAddItem.t.Errorf("RepositoryMock.AddItem got unexpected parameter item, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmAddItem.AddItemMock.defaultExpectation.expectationOrigins.originItem, *mm_want_ptrs.item, mm_got.item, minimock.Diff(*mm_want_ptrs.item, mm_got.item)) + } + + } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmAddItem.t.Errorf("RepositoryMock.AddItem got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmAddItem.AddItemMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmAddItem.AddItemMock.defaultExpectation.results + if mm_results == nil { + mmAddItem.t.Fatal("No results are set for the RepositoryMock.AddItem") + } + return (*mm_results).err + } + if mmAddItem.funcAddItem != nil { + return mmAddItem.funcAddItem(ctx, userID, item) + } + mmAddItem.t.Fatalf("Unexpected call to RepositoryMock.AddItem. %v %v %v", ctx, userID, item) + return +} + +// AddItemAfterCounter returns a count of finished RepositoryMock.AddItem invocations +func (mmAddItem *RepositoryMock) AddItemAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmAddItem.afterAddItemCounter) +} + +// AddItemBeforeCounter returns a count of RepositoryMock.AddItem invocations +func (mmAddItem *RepositoryMock) AddItemBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmAddItem.beforeAddItemCounter) +} + +// Calls returns a list of arguments used in each call to RepositoryMock.AddItem. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmAddItem *mRepositoryMockAddItem) Calls() []*RepositoryMockAddItemParams { + mmAddItem.mutex.RLock() + + argCopy := make([]*RepositoryMockAddItemParams, len(mmAddItem.callArgs)) + copy(argCopy, mmAddItem.callArgs) + + mmAddItem.mutex.RUnlock() + + return argCopy +} + +// MinimockAddItemDone returns true if the count of the AddItem invocations corresponds +// the number of defined expectations +func (m *RepositoryMock) MinimockAddItemDone() bool { + if m.AddItemMock.optional { + // Optional methods provide '0 or more' call count restriction. + return true + } + + for _, e := range m.AddItemMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + return m.AddItemMock.invocationsDone() +} + +// MinimockAddItemInspect logs each unmet expectation +func (m *RepositoryMock) MinimockAddItemInspect() { + for _, e := range m.AddItemMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to RepositoryMock.AddItem at\n%s with params: %#v", e.expectationOrigins.origin, *e.params) + } + } + + afterAddItemCounter := mm_atomic.LoadUint64(&m.afterAddItemCounter) + // if default expectation was set then invocations count should be greater than zero + if m.AddItemMock.defaultExpectation != nil && afterAddItemCounter < 1 { + if m.AddItemMock.defaultExpectation.params == nil { + m.t.Errorf("Expected call to RepositoryMock.AddItem at\n%s", m.AddItemMock.defaultExpectation.returnOrigin) + } else { + m.t.Errorf("Expected call to RepositoryMock.AddItem at\n%s with params: %#v", m.AddItemMock.defaultExpectation.expectationOrigins.origin, *m.AddItemMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcAddItem != nil && afterAddItemCounter < 1 { + m.t.Errorf("Expected call to RepositoryMock.AddItem at\n%s", m.funcAddItemOrigin) + } + + if !m.AddItemMock.invocationsDone() && afterAddItemCounter > 0 { + m.t.Errorf("Expected %d calls to RepositoryMock.AddItem at\n%s but found %d calls", + mm_atomic.LoadUint64(&m.AddItemMock.expectedInvocations), m.AddItemMock.expectedInvocationsOrigin, afterAddItemCounter) + } +} + +type mRepositoryMockDeleteItem struct { + optional bool + mock *RepositoryMock + defaultExpectation *RepositoryMockDeleteItemExpectation + expectations []*RepositoryMockDeleteItemExpectation + + callArgs []*RepositoryMockDeleteItemParams + mutex sync.RWMutex + + expectedInvocations uint64 + expectedInvocationsOrigin string +} + +// RepositoryMockDeleteItemExpectation specifies expectation struct of the Repository.DeleteItem +type RepositoryMockDeleteItemExpectation struct { + mock *RepositoryMock + params *RepositoryMockDeleteItemParams + paramPtrs *RepositoryMockDeleteItemParamPtrs + expectationOrigins RepositoryMockDeleteItemExpectationOrigins + results *RepositoryMockDeleteItemResults + returnOrigin string + Counter uint64 +} + +// RepositoryMockDeleteItemParams contains parameters of the Repository.DeleteItem +type RepositoryMockDeleteItemParams struct { + ctx context.Context + userID entity.UID + sku entity.Sku +} + +// RepositoryMockDeleteItemParamPtrs contains pointers to parameters of the Repository.DeleteItem +type RepositoryMockDeleteItemParamPtrs struct { + ctx *context.Context + userID *entity.UID + sku *entity.Sku +} + +// RepositoryMockDeleteItemResults contains results of the Repository.DeleteItem +type RepositoryMockDeleteItemResults struct { + err error +} + +// RepositoryMockDeleteItemOrigins contains origins of expectations of the Repository.DeleteItem +type RepositoryMockDeleteItemExpectationOrigins struct { + origin string + originCtx string + originUserID string + originSku string +} + +// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning +// the test will fail minimock's automatic final call check if the mocked method was not called at least once. +// Optional() makes method check to work in '0 or more' mode. +// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to +// catch the problems when the expected method call is totally skipped during test run. +func (mmDeleteItem *mRepositoryMockDeleteItem) Optional() *mRepositoryMockDeleteItem { + mmDeleteItem.optional = true + return mmDeleteItem +} + +// Expect sets up expected params for Repository.DeleteItem +func (mmDeleteItem *mRepositoryMockDeleteItem) Expect(ctx context.Context, userID entity.UID, sku entity.Sku) *mRepositoryMockDeleteItem { + if mmDeleteItem.mock.funcDeleteItem != nil { + mmDeleteItem.mock.t.Fatalf("RepositoryMock.DeleteItem mock is already set by Set") + } + + if mmDeleteItem.defaultExpectation == nil { + mmDeleteItem.defaultExpectation = &RepositoryMockDeleteItemExpectation{} + } + + if mmDeleteItem.defaultExpectation.paramPtrs != nil { + mmDeleteItem.mock.t.Fatalf("RepositoryMock.DeleteItem mock is already set by ExpectParams functions") + } + + mmDeleteItem.defaultExpectation.params = &RepositoryMockDeleteItemParams{ctx, userID, sku} + mmDeleteItem.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1) + for _, e := range mmDeleteItem.expectations { + if minimock.Equal(e.params, mmDeleteItem.defaultExpectation.params) { + mmDeleteItem.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmDeleteItem.defaultExpectation.params) + } + } + + return mmDeleteItem +} + +// ExpectCtxParam1 sets up expected param ctx for Repository.DeleteItem +func (mmDeleteItem *mRepositoryMockDeleteItem) ExpectCtxParam1(ctx context.Context) *mRepositoryMockDeleteItem { + if mmDeleteItem.mock.funcDeleteItem != nil { + mmDeleteItem.mock.t.Fatalf("RepositoryMock.DeleteItem mock is already set by Set") + } + + if mmDeleteItem.defaultExpectation == nil { + mmDeleteItem.defaultExpectation = &RepositoryMockDeleteItemExpectation{} + } + + if mmDeleteItem.defaultExpectation.params != nil { + mmDeleteItem.mock.t.Fatalf("RepositoryMock.DeleteItem mock is already set by Expect") + } + + if mmDeleteItem.defaultExpectation.paramPtrs == nil { + mmDeleteItem.defaultExpectation.paramPtrs = &RepositoryMockDeleteItemParamPtrs{} + } + mmDeleteItem.defaultExpectation.paramPtrs.ctx = &ctx + mmDeleteItem.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1) + + return mmDeleteItem +} + +// ExpectUserIDParam2 sets up expected param userID for Repository.DeleteItem +func (mmDeleteItem *mRepositoryMockDeleteItem) ExpectUserIDParam2(userID entity.UID) *mRepositoryMockDeleteItem { + if mmDeleteItem.mock.funcDeleteItem != nil { + mmDeleteItem.mock.t.Fatalf("RepositoryMock.DeleteItem mock is already set by Set") + } + + if mmDeleteItem.defaultExpectation == nil { + mmDeleteItem.defaultExpectation = &RepositoryMockDeleteItemExpectation{} + } + + if mmDeleteItem.defaultExpectation.params != nil { + mmDeleteItem.mock.t.Fatalf("RepositoryMock.DeleteItem mock is already set by Expect") + } + + if mmDeleteItem.defaultExpectation.paramPtrs == nil { + mmDeleteItem.defaultExpectation.paramPtrs = &RepositoryMockDeleteItemParamPtrs{} + } + mmDeleteItem.defaultExpectation.paramPtrs.userID = &userID + mmDeleteItem.defaultExpectation.expectationOrigins.originUserID = minimock.CallerInfo(1) + + return mmDeleteItem +} + +// ExpectSkuParam3 sets up expected param sku for Repository.DeleteItem +func (mmDeleteItem *mRepositoryMockDeleteItem) ExpectSkuParam3(sku entity.Sku) *mRepositoryMockDeleteItem { + if mmDeleteItem.mock.funcDeleteItem != nil { + mmDeleteItem.mock.t.Fatalf("RepositoryMock.DeleteItem mock is already set by Set") + } + + if mmDeleteItem.defaultExpectation == nil { + mmDeleteItem.defaultExpectation = &RepositoryMockDeleteItemExpectation{} + } + + if mmDeleteItem.defaultExpectation.params != nil { + mmDeleteItem.mock.t.Fatalf("RepositoryMock.DeleteItem mock is already set by Expect") + } + + if mmDeleteItem.defaultExpectation.paramPtrs == nil { + mmDeleteItem.defaultExpectation.paramPtrs = &RepositoryMockDeleteItemParamPtrs{} + } + mmDeleteItem.defaultExpectation.paramPtrs.sku = &sku + mmDeleteItem.defaultExpectation.expectationOrigins.originSku = minimock.CallerInfo(1) + + return mmDeleteItem +} + +// Inspect accepts an inspector function that has same arguments as the Repository.DeleteItem +func (mmDeleteItem *mRepositoryMockDeleteItem) Inspect(f func(ctx context.Context, userID entity.UID, sku entity.Sku)) *mRepositoryMockDeleteItem { + if mmDeleteItem.mock.inspectFuncDeleteItem != nil { + mmDeleteItem.mock.t.Fatalf("Inspect function is already set for RepositoryMock.DeleteItem") + } + + mmDeleteItem.mock.inspectFuncDeleteItem = f + + return mmDeleteItem +} + +// Return sets up results that will be returned by Repository.DeleteItem +func (mmDeleteItem *mRepositoryMockDeleteItem) Return(err error) *RepositoryMock { + if mmDeleteItem.mock.funcDeleteItem != nil { + mmDeleteItem.mock.t.Fatalf("RepositoryMock.DeleteItem mock is already set by Set") + } + + if mmDeleteItem.defaultExpectation == nil { + mmDeleteItem.defaultExpectation = &RepositoryMockDeleteItemExpectation{mock: mmDeleteItem.mock} + } + mmDeleteItem.defaultExpectation.results = &RepositoryMockDeleteItemResults{err} + mmDeleteItem.defaultExpectation.returnOrigin = minimock.CallerInfo(1) + return mmDeleteItem.mock +} + +// Set uses given function f to mock the Repository.DeleteItem method +func (mmDeleteItem *mRepositoryMockDeleteItem) Set(f func(ctx context.Context, userID entity.UID, sku entity.Sku) (err error)) *RepositoryMock { + if mmDeleteItem.defaultExpectation != nil { + mmDeleteItem.mock.t.Fatalf("Default expectation is already set for the Repository.DeleteItem method") + } + + if len(mmDeleteItem.expectations) > 0 { + mmDeleteItem.mock.t.Fatalf("Some expectations are already set for the Repository.DeleteItem method") + } + + mmDeleteItem.mock.funcDeleteItem = f + mmDeleteItem.mock.funcDeleteItemOrigin = minimock.CallerInfo(1) + return mmDeleteItem.mock +} + +// When sets expectation for the Repository.DeleteItem which will trigger the result defined by the following +// Then helper +func (mmDeleteItem *mRepositoryMockDeleteItem) When(ctx context.Context, userID entity.UID, sku entity.Sku) *RepositoryMockDeleteItemExpectation { + if mmDeleteItem.mock.funcDeleteItem != nil { + mmDeleteItem.mock.t.Fatalf("RepositoryMock.DeleteItem mock is already set by Set") + } + + expectation := &RepositoryMockDeleteItemExpectation{ + mock: mmDeleteItem.mock, + params: &RepositoryMockDeleteItemParams{ctx, userID, sku}, + expectationOrigins: RepositoryMockDeleteItemExpectationOrigins{origin: minimock.CallerInfo(1)}, + } + mmDeleteItem.expectations = append(mmDeleteItem.expectations, expectation) + return expectation +} + +// Then sets up Repository.DeleteItem return parameters for the expectation previously defined by the When method +func (e *RepositoryMockDeleteItemExpectation) Then(err error) *RepositoryMock { + e.results = &RepositoryMockDeleteItemResults{err} + return e.mock +} + +// Times sets number of times Repository.DeleteItem should be invoked +func (mmDeleteItem *mRepositoryMockDeleteItem) Times(n uint64) *mRepositoryMockDeleteItem { + if n == 0 { + mmDeleteItem.mock.t.Fatalf("Times of RepositoryMock.DeleteItem mock can not be zero") + } + mm_atomic.StoreUint64(&mmDeleteItem.expectedInvocations, n) + mmDeleteItem.expectedInvocationsOrigin = minimock.CallerInfo(1) + return mmDeleteItem +} + +func (mmDeleteItem *mRepositoryMockDeleteItem) invocationsDone() bool { + if len(mmDeleteItem.expectations) == 0 && mmDeleteItem.defaultExpectation == nil && mmDeleteItem.mock.funcDeleteItem == nil { + return true + } + + totalInvocations := mm_atomic.LoadUint64(&mmDeleteItem.mock.afterDeleteItemCounter) + expectedInvocations := mm_atomic.LoadUint64(&mmDeleteItem.expectedInvocations) + + return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) +} + +// DeleteItem implements mm_service.Repository +func (mmDeleteItem *RepositoryMock) DeleteItem(ctx context.Context, userID entity.UID, sku entity.Sku) (err error) { + mm_atomic.AddUint64(&mmDeleteItem.beforeDeleteItemCounter, 1) + defer mm_atomic.AddUint64(&mmDeleteItem.afterDeleteItemCounter, 1) + + mmDeleteItem.t.Helper() + + if mmDeleteItem.inspectFuncDeleteItem != nil { + mmDeleteItem.inspectFuncDeleteItem(ctx, userID, sku) + } + + mm_params := RepositoryMockDeleteItemParams{ctx, userID, sku} + + // Record call args + mmDeleteItem.DeleteItemMock.mutex.Lock() + mmDeleteItem.DeleteItemMock.callArgs = append(mmDeleteItem.DeleteItemMock.callArgs, &mm_params) + mmDeleteItem.DeleteItemMock.mutex.Unlock() + + for _, e := range mmDeleteItem.DeleteItemMock.expectations { + if minimock.Equal(*e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.err + } + } + + if mmDeleteItem.DeleteItemMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmDeleteItem.DeleteItemMock.defaultExpectation.Counter, 1) + mm_want := mmDeleteItem.DeleteItemMock.defaultExpectation.params + mm_want_ptrs := mmDeleteItem.DeleteItemMock.defaultExpectation.paramPtrs + + mm_got := RepositoryMockDeleteItemParams{ctx, userID, sku} + + if mm_want_ptrs != nil { + + if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { + mmDeleteItem.t.Errorf("RepositoryMock.DeleteItem got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmDeleteItem.DeleteItemMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + } + + if mm_want_ptrs.userID != nil && !minimock.Equal(*mm_want_ptrs.userID, mm_got.userID) { + mmDeleteItem.t.Errorf("RepositoryMock.DeleteItem got unexpected parameter userID, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmDeleteItem.DeleteItemMock.defaultExpectation.expectationOrigins.originUserID, *mm_want_ptrs.userID, mm_got.userID, minimock.Diff(*mm_want_ptrs.userID, mm_got.userID)) + } + + if mm_want_ptrs.sku != nil && !minimock.Equal(*mm_want_ptrs.sku, mm_got.sku) { + mmDeleteItem.t.Errorf("RepositoryMock.DeleteItem got unexpected parameter sku, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmDeleteItem.DeleteItemMock.defaultExpectation.expectationOrigins.originSku, *mm_want_ptrs.sku, mm_got.sku, minimock.Diff(*mm_want_ptrs.sku, mm_got.sku)) + } + + } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmDeleteItem.t.Errorf("RepositoryMock.DeleteItem got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmDeleteItem.DeleteItemMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmDeleteItem.DeleteItemMock.defaultExpectation.results + if mm_results == nil { + mmDeleteItem.t.Fatal("No results are set for the RepositoryMock.DeleteItem") + } + return (*mm_results).err + } + if mmDeleteItem.funcDeleteItem != nil { + return mmDeleteItem.funcDeleteItem(ctx, userID, sku) + } + mmDeleteItem.t.Fatalf("Unexpected call to RepositoryMock.DeleteItem. %v %v %v", ctx, userID, sku) + return +} + +// DeleteItemAfterCounter returns a count of finished RepositoryMock.DeleteItem invocations +func (mmDeleteItem *RepositoryMock) DeleteItemAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmDeleteItem.afterDeleteItemCounter) +} + +// DeleteItemBeforeCounter returns a count of RepositoryMock.DeleteItem invocations +func (mmDeleteItem *RepositoryMock) DeleteItemBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmDeleteItem.beforeDeleteItemCounter) +} + +// Calls returns a list of arguments used in each call to RepositoryMock.DeleteItem. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmDeleteItem *mRepositoryMockDeleteItem) Calls() []*RepositoryMockDeleteItemParams { + mmDeleteItem.mutex.RLock() + + argCopy := make([]*RepositoryMockDeleteItemParams, len(mmDeleteItem.callArgs)) + copy(argCopy, mmDeleteItem.callArgs) + + mmDeleteItem.mutex.RUnlock() + + return argCopy +} + +// MinimockDeleteItemDone returns true if the count of the DeleteItem invocations corresponds +// the number of defined expectations +func (m *RepositoryMock) MinimockDeleteItemDone() bool { + if m.DeleteItemMock.optional { + // Optional methods provide '0 or more' call count restriction. + return true + } + + for _, e := range m.DeleteItemMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + return m.DeleteItemMock.invocationsDone() +} + +// MinimockDeleteItemInspect logs each unmet expectation +func (m *RepositoryMock) MinimockDeleteItemInspect() { + for _, e := range m.DeleteItemMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to RepositoryMock.DeleteItem at\n%s with params: %#v", e.expectationOrigins.origin, *e.params) + } + } + + afterDeleteItemCounter := mm_atomic.LoadUint64(&m.afterDeleteItemCounter) + // if default expectation was set then invocations count should be greater than zero + if m.DeleteItemMock.defaultExpectation != nil && afterDeleteItemCounter < 1 { + if m.DeleteItemMock.defaultExpectation.params == nil { + m.t.Errorf("Expected call to RepositoryMock.DeleteItem at\n%s", m.DeleteItemMock.defaultExpectation.returnOrigin) + } else { + m.t.Errorf("Expected call to RepositoryMock.DeleteItem at\n%s with params: %#v", m.DeleteItemMock.defaultExpectation.expectationOrigins.origin, *m.DeleteItemMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcDeleteItem != nil && afterDeleteItemCounter < 1 { + m.t.Errorf("Expected call to RepositoryMock.DeleteItem at\n%s", m.funcDeleteItemOrigin) + } + + if !m.DeleteItemMock.invocationsDone() && afterDeleteItemCounter > 0 { + m.t.Errorf("Expected %d calls to RepositoryMock.DeleteItem at\n%s but found %d calls", + mm_atomic.LoadUint64(&m.DeleteItemMock.expectedInvocations), m.DeleteItemMock.expectedInvocationsOrigin, afterDeleteItemCounter) + } +} + +type mRepositoryMockDeleteItemsByUserID struct { + optional bool + mock *RepositoryMock + defaultExpectation *RepositoryMockDeleteItemsByUserIDExpectation + expectations []*RepositoryMockDeleteItemsByUserIDExpectation + + callArgs []*RepositoryMockDeleteItemsByUserIDParams + mutex sync.RWMutex + + expectedInvocations uint64 + expectedInvocationsOrigin string +} + +// RepositoryMockDeleteItemsByUserIDExpectation specifies expectation struct of the Repository.DeleteItemsByUserID +type RepositoryMockDeleteItemsByUserIDExpectation struct { + mock *RepositoryMock + params *RepositoryMockDeleteItemsByUserIDParams + paramPtrs *RepositoryMockDeleteItemsByUserIDParamPtrs + expectationOrigins RepositoryMockDeleteItemsByUserIDExpectationOrigins + results *RepositoryMockDeleteItemsByUserIDResults + returnOrigin string + Counter uint64 +} + +// RepositoryMockDeleteItemsByUserIDParams contains parameters of the Repository.DeleteItemsByUserID +type RepositoryMockDeleteItemsByUserIDParams struct { + ctx context.Context + userID entity.UID +} + +// RepositoryMockDeleteItemsByUserIDParamPtrs contains pointers to parameters of the Repository.DeleteItemsByUserID +type RepositoryMockDeleteItemsByUserIDParamPtrs struct { + ctx *context.Context + userID *entity.UID +} + +// RepositoryMockDeleteItemsByUserIDResults contains results of the Repository.DeleteItemsByUserID +type RepositoryMockDeleteItemsByUserIDResults struct { + err error +} + +// RepositoryMockDeleteItemsByUserIDOrigins contains origins of expectations of the Repository.DeleteItemsByUserID +type RepositoryMockDeleteItemsByUserIDExpectationOrigins struct { + origin string + originCtx string + originUserID string +} + +// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning +// the test will fail minimock's automatic final call check if the mocked method was not called at least once. +// Optional() makes method check to work in '0 or more' mode. +// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to +// catch the problems when the expected method call is totally skipped during test run. +func (mmDeleteItemsByUserID *mRepositoryMockDeleteItemsByUserID) Optional() *mRepositoryMockDeleteItemsByUserID { + mmDeleteItemsByUserID.optional = true + return mmDeleteItemsByUserID +} + +// Expect sets up expected params for Repository.DeleteItemsByUserID +func (mmDeleteItemsByUserID *mRepositoryMockDeleteItemsByUserID) Expect(ctx context.Context, userID entity.UID) *mRepositoryMockDeleteItemsByUserID { + if mmDeleteItemsByUserID.mock.funcDeleteItemsByUserID != nil { + mmDeleteItemsByUserID.mock.t.Fatalf("RepositoryMock.DeleteItemsByUserID mock is already set by Set") + } + + if mmDeleteItemsByUserID.defaultExpectation == nil { + mmDeleteItemsByUserID.defaultExpectation = &RepositoryMockDeleteItemsByUserIDExpectation{} + } + + if mmDeleteItemsByUserID.defaultExpectation.paramPtrs != nil { + mmDeleteItemsByUserID.mock.t.Fatalf("RepositoryMock.DeleteItemsByUserID mock is already set by ExpectParams functions") + } + + mmDeleteItemsByUserID.defaultExpectation.params = &RepositoryMockDeleteItemsByUserIDParams{ctx, userID} + mmDeleteItemsByUserID.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1) + for _, e := range mmDeleteItemsByUserID.expectations { + if minimock.Equal(e.params, mmDeleteItemsByUserID.defaultExpectation.params) { + mmDeleteItemsByUserID.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmDeleteItemsByUserID.defaultExpectation.params) + } + } + + return mmDeleteItemsByUserID +} + +// ExpectCtxParam1 sets up expected param ctx for Repository.DeleteItemsByUserID +func (mmDeleteItemsByUserID *mRepositoryMockDeleteItemsByUserID) ExpectCtxParam1(ctx context.Context) *mRepositoryMockDeleteItemsByUserID { + if mmDeleteItemsByUserID.mock.funcDeleteItemsByUserID != nil { + mmDeleteItemsByUserID.mock.t.Fatalf("RepositoryMock.DeleteItemsByUserID mock is already set by Set") + } + + if mmDeleteItemsByUserID.defaultExpectation == nil { + mmDeleteItemsByUserID.defaultExpectation = &RepositoryMockDeleteItemsByUserIDExpectation{} + } + + if mmDeleteItemsByUserID.defaultExpectation.params != nil { + mmDeleteItemsByUserID.mock.t.Fatalf("RepositoryMock.DeleteItemsByUserID mock is already set by Expect") + } + + if mmDeleteItemsByUserID.defaultExpectation.paramPtrs == nil { + mmDeleteItemsByUserID.defaultExpectation.paramPtrs = &RepositoryMockDeleteItemsByUserIDParamPtrs{} + } + mmDeleteItemsByUserID.defaultExpectation.paramPtrs.ctx = &ctx + mmDeleteItemsByUserID.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1) + + return mmDeleteItemsByUserID +} + +// ExpectUserIDParam2 sets up expected param userID for Repository.DeleteItemsByUserID +func (mmDeleteItemsByUserID *mRepositoryMockDeleteItemsByUserID) ExpectUserIDParam2(userID entity.UID) *mRepositoryMockDeleteItemsByUserID { + if mmDeleteItemsByUserID.mock.funcDeleteItemsByUserID != nil { + mmDeleteItemsByUserID.mock.t.Fatalf("RepositoryMock.DeleteItemsByUserID mock is already set by Set") + } + + if mmDeleteItemsByUserID.defaultExpectation == nil { + mmDeleteItemsByUserID.defaultExpectation = &RepositoryMockDeleteItemsByUserIDExpectation{} + } + + if mmDeleteItemsByUserID.defaultExpectation.params != nil { + mmDeleteItemsByUserID.mock.t.Fatalf("RepositoryMock.DeleteItemsByUserID mock is already set by Expect") + } + + if mmDeleteItemsByUserID.defaultExpectation.paramPtrs == nil { + mmDeleteItemsByUserID.defaultExpectation.paramPtrs = &RepositoryMockDeleteItemsByUserIDParamPtrs{} + } + mmDeleteItemsByUserID.defaultExpectation.paramPtrs.userID = &userID + mmDeleteItemsByUserID.defaultExpectation.expectationOrigins.originUserID = minimock.CallerInfo(1) + + return mmDeleteItemsByUserID +} + +// Inspect accepts an inspector function that has same arguments as the Repository.DeleteItemsByUserID +func (mmDeleteItemsByUserID *mRepositoryMockDeleteItemsByUserID) Inspect(f func(ctx context.Context, userID entity.UID)) *mRepositoryMockDeleteItemsByUserID { + if mmDeleteItemsByUserID.mock.inspectFuncDeleteItemsByUserID != nil { + mmDeleteItemsByUserID.mock.t.Fatalf("Inspect function is already set for RepositoryMock.DeleteItemsByUserID") + } + + mmDeleteItemsByUserID.mock.inspectFuncDeleteItemsByUserID = f + + return mmDeleteItemsByUserID +} + +// Return sets up results that will be returned by Repository.DeleteItemsByUserID +func (mmDeleteItemsByUserID *mRepositoryMockDeleteItemsByUserID) Return(err error) *RepositoryMock { + if mmDeleteItemsByUserID.mock.funcDeleteItemsByUserID != nil { + mmDeleteItemsByUserID.mock.t.Fatalf("RepositoryMock.DeleteItemsByUserID mock is already set by Set") + } + + if mmDeleteItemsByUserID.defaultExpectation == nil { + mmDeleteItemsByUserID.defaultExpectation = &RepositoryMockDeleteItemsByUserIDExpectation{mock: mmDeleteItemsByUserID.mock} + } + mmDeleteItemsByUserID.defaultExpectation.results = &RepositoryMockDeleteItemsByUserIDResults{err} + mmDeleteItemsByUserID.defaultExpectation.returnOrigin = minimock.CallerInfo(1) + return mmDeleteItemsByUserID.mock +} + +// Set uses given function f to mock the Repository.DeleteItemsByUserID method +func (mmDeleteItemsByUserID *mRepositoryMockDeleteItemsByUserID) Set(f func(ctx context.Context, userID entity.UID) (err error)) *RepositoryMock { + if mmDeleteItemsByUserID.defaultExpectation != nil { + mmDeleteItemsByUserID.mock.t.Fatalf("Default expectation is already set for the Repository.DeleteItemsByUserID method") + } + + if len(mmDeleteItemsByUserID.expectations) > 0 { + mmDeleteItemsByUserID.mock.t.Fatalf("Some expectations are already set for the Repository.DeleteItemsByUserID method") + } + + mmDeleteItemsByUserID.mock.funcDeleteItemsByUserID = f + mmDeleteItemsByUserID.mock.funcDeleteItemsByUserIDOrigin = minimock.CallerInfo(1) + return mmDeleteItemsByUserID.mock +} + +// When sets expectation for the Repository.DeleteItemsByUserID which will trigger the result defined by the following +// Then helper +func (mmDeleteItemsByUserID *mRepositoryMockDeleteItemsByUserID) When(ctx context.Context, userID entity.UID) *RepositoryMockDeleteItemsByUserIDExpectation { + if mmDeleteItemsByUserID.mock.funcDeleteItemsByUserID != nil { + mmDeleteItemsByUserID.mock.t.Fatalf("RepositoryMock.DeleteItemsByUserID mock is already set by Set") + } + + expectation := &RepositoryMockDeleteItemsByUserIDExpectation{ + mock: mmDeleteItemsByUserID.mock, + params: &RepositoryMockDeleteItemsByUserIDParams{ctx, userID}, + expectationOrigins: RepositoryMockDeleteItemsByUserIDExpectationOrigins{origin: minimock.CallerInfo(1)}, + } + mmDeleteItemsByUserID.expectations = append(mmDeleteItemsByUserID.expectations, expectation) + return expectation +} + +// Then sets up Repository.DeleteItemsByUserID return parameters for the expectation previously defined by the When method +func (e *RepositoryMockDeleteItemsByUserIDExpectation) Then(err error) *RepositoryMock { + e.results = &RepositoryMockDeleteItemsByUserIDResults{err} + return e.mock +} + +// Times sets number of times Repository.DeleteItemsByUserID should be invoked +func (mmDeleteItemsByUserID *mRepositoryMockDeleteItemsByUserID) Times(n uint64) *mRepositoryMockDeleteItemsByUserID { + if n == 0 { + mmDeleteItemsByUserID.mock.t.Fatalf("Times of RepositoryMock.DeleteItemsByUserID mock can not be zero") + } + mm_atomic.StoreUint64(&mmDeleteItemsByUserID.expectedInvocations, n) + mmDeleteItemsByUserID.expectedInvocationsOrigin = minimock.CallerInfo(1) + return mmDeleteItemsByUserID +} + +func (mmDeleteItemsByUserID *mRepositoryMockDeleteItemsByUserID) invocationsDone() bool { + if len(mmDeleteItemsByUserID.expectations) == 0 && mmDeleteItemsByUserID.defaultExpectation == nil && mmDeleteItemsByUserID.mock.funcDeleteItemsByUserID == nil { + return true + } + + totalInvocations := mm_atomic.LoadUint64(&mmDeleteItemsByUserID.mock.afterDeleteItemsByUserIDCounter) + expectedInvocations := mm_atomic.LoadUint64(&mmDeleteItemsByUserID.expectedInvocations) + + return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) +} + +// DeleteItemsByUserID implements mm_service.Repository +func (mmDeleteItemsByUserID *RepositoryMock) DeleteItemsByUserID(ctx context.Context, userID entity.UID) (err error) { + mm_atomic.AddUint64(&mmDeleteItemsByUserID.beforeDeleteItemsByUserIDCounter, 1) + defer mm_atomic.AddUint64(&mmDeleteItemsByUserID.afterDeleteItemsByUserIDCounter, 1) + + mmDeleteItemsByUserID.t.Helper() + + if mmDeleteItemsByUserID.inspectFuncDeleteItemsByUserID != nil { + mmDeleteItemsByUserID.inspectFuncDeleteItemsByUserID(ctx, userID) + } + + mm_params := RepositoryMockDeleteItemsByUserIDParams{ctx, userID} + + // Record call args + mmDeleteItemsByUserID.DeleteItemsByUserIDMock.mutex.Lock() + mmDeleteItemsByUserID.DeleteItemsByUserIDMock.callArgs = append(mmDeleteItemsByUserID.DeleteItemsByUserIDMock.callArgs, &mm_params) + mmDeleteItemsByUserID.DeleteItemsByUserIDMock.mutex.Unlock() + + for _, e := range mmDeleteItemsByUserID.DeleteItemsByUserIDMock.expectations { + if minimock.Equal(*e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.err + } + } + + if mmDeleteItemsByUserID.DeleteItemsByUserIDMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmDeleteItemsByUserID.DeleteItemsByUserIDMock.defaultExpectation.Counter, 1) + mm_want := mmDeleteItemsByUserID.DeleteItemsByUserIDMock.defaultExpectation.params + mm_want_ptrs := mmDeleteItemsByUserID.DeleteItemsByUserIDMock.defaultExpectation.paramPtrs + + mm_got := RepositoryMockDeleteItemsByUserIDParams{ctx, userID} + + if mm_want_ptrs != nil { + + if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { + mmDeleteItemsByUserID.t.Errorf("RepositoryMock.DeleteItemsByUserID got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmDeleteItemsByUserID.DeleteItemsByUserIDMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + } + + if mm_want_ptrs.userID != nil && !minimock.Equal(*mm_want_ptrs.userID, mm_got.userID) { + mmDeleteItemsByUserID.t.Errorf("RepositoryMock.DeleteItemsByUserID got unexpected parameter userID, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmDeleteItemsByUserID.DeleteItemsByUserIDMock.defaultExpectation.expectationOrigins.originUserID, *mm_want_ptrs.userID, mm_got.userID, minimock.Diff(*mm_want_ptrs.userID, mm_got.userID)) + } + + } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmDeleteItemsByUserID.t.Errorf("RepositoryMock.DeleteItemsByUserID got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmDeleteItemsByUserID.DeleteItemsByUserIDMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmDeleteItemsByUserID.DeleteItemsByUserIDMock.defaultExpectation.results + if mm_results == nil { + mmDeleteItemsByUserID.t.Fatal("No results are set for the RepositoryMock.DeleteItemsByUserID") + } + return (*mm_results).err + } + if mmDeleteItemsByUserID.funcDeleteItemsByUserID != nil { + return mmDeleteItemsByUserID.funcDeleteItemsByUserID(ctx, userID) + } + mmDeleteItemsByUserID.t.Fatalf("Unexpected call to RepositoryMock.DeleteItemsByUserID. %v %v", ctx, userID) + return +} + +// DeleteItemsByUserIDAfterCounter returns a count of finished RepositoryMock.DeleteItemsByUserID invocations +func (mmDeleteItemsByUserID *RepositoryMock) DeleteItemsByUserIDAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmDeleteItemsByUserID.afterDeleteItemsByUserIDCounter) +} + +// DeleteItemsByUserIDBeforeCounter returns a count of RepositoryMock.DeleteItemsByUserID invocations +func (mmDeleteItemsByUserID *RepositoryMock) DeleteItemsByUserIDBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmDeleteItemsByUserID.beforeDeleteItemsByUserIDCounter) +} + +// Calls returns a list of arguments used in each call to RepositoryMock.DeleteItemsByUserID. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmDeleteItemsByUserID *mRepositoryMockDeleteItemsByUserID) Calls() []*RepositoryMockDeleteItemsByUserIDParams { + mmDeleteItemsByUserID.mutex.RLock() + + argCopy := make([]*RepositoryMockDeleteItemsByUserIDParams, len(mmDeleteItemsByUserID.callArgs)) + copy(argCopy, mmDeleteItemsByUserID.callArgs) + + mmDeleteItemsByUserID.mutex.RUnlock() + + return argCopy +} + +// MinimockDeleteItemsByUserIDDone returns true if the count of the DeleteItemsByUserID invocations corresponds +// the number of defined expectations +func (m *RepositoryMock) MinimockDeleteItemsByUserIDDone() bool { + if m.DeleteItemsByUserIDMock.optional { + // Optional methods provide '0 or more' call count restriction. + return true + } + + for _, e := range m.DeleteItemsByUserIDMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + return m.DeleteItemsByUserIDMock.invocationsDone() +} + +// MinimockDeleteItemsByUserIDInspect logs each unmet expectation +func (m *RepositoryMock) MinimockDeleteItemsByUserIDInspect() { + for _, e := range m.DeleteItemsByUserIDMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to RepositoryMock.DeleteItemsByUserID at\n%s with params: %#v", e.expectationOrigins.origin, *e.params) + } + } + + afterDeleteItemsByUserIDCounter := mm_atomic.LoadUint64(&m.afterDeleteItemsByUserIDCounter) + // if default expectation was set then invocations count should be greater than zero + if m.DeleteItemsByUserIDMock.defaultExpectation != nil && afterDeleteItemsByUserIDCounter < 1 { + if m.DeleteItemsByUserIDMock.defaultExpectation.params == nil { + m.t.Errorf("Expected call to RepositoryMock.DeleteItemsByUserID at\n%s", m.DeleteItemsByUserIDMock.defaultExpectation.returnOrigin) + } else { + m.t.Errorf("Expected call to RepositoryMock.DeleteItemsByUserID at\n%s with params: %#v", m.DeleteItemsByUserIDMock.defaultExpectation.expectationOrigins.origin, *m.DeleteItemsByUserIDMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcDeleteItemsByUserID != nil && afterDeleteItemsByUserIDCounter < 1 { + m.t.Errorf("Expected call to RepositoryMock.DeleteItemsByUserID at\n%s", m.funcDeleteItemsByUserIDOrigin) + } + + if !m.DeleteItemsByUserIDMock.invocationsDone() && afterDeleteItemsByUserIDCounter > 0 { + m.t.Errorf("Expected %d calls to RepositoryMock.DeleteItemsByUserID at\n%s but found %d calls", + mm_atomic.LoadUint64(&m.DeleteItemsByUserIDMock.expectedInvocations), m.DeleteItemsByUserIDMock.expectedInvocationsOrigin, afterDeleteItemsByUserIDCounter) + } +} + +type mRepositoryMockGetItemsByUserID struct { + optional bool + mock *RepositoryMock + defaultExpectation *RepositoryMockGetItemsByUserIDExpectation + expectations []*RepositoryMockGetItemsByUserIDExpectation + + callArgs []*RepositoryMockGetItemsByUserIDParams + mutex sync.RWMutex + + expectedInvocations uint64 + expectedInvocationsOrigin string +} + +// RepositoryMockGetItemsByUserIDExpectation specifies expectation struct of the Repository.GetItemsByUserID +type RepositoryMockGetItemsByUserIDExpectation struct { + mock *RepositoryMock + params *RepositoryMockGetItemsByUserIDParams + paramPtrs *RepositoryMockGetItemsByUserIDParamPtrs + expectationOrigins RepositoryMockGetItemsByUserIDExpectationOrigins + results *RepositoryMockGetItemsByUserIDResults + returnOrigin string + Counter uint64 +} + +// RepositoryMockGetItemsByUserIDParams contains parameters of the Repository.GetItemsByUserID +type RepositoryMockGetItemsByUserIDParams struct { + ctx context.Context + userID entity.UID +} + +// RepositoryMockGetItemsByUserIDParamPtrs contains pointers to parameters of the Repository.GetItemsByUserID +type RepositoryMockGetItemsByUserIDParamPtrs struct { + ctx *context.Context + userID *entity.UID +} + +// RepositoryMockGetItemsByUserIDResults contains results of the Repository.GetItemsByUserID +type RepositoryMockGetItemsByUserIDResults struct { + c2 entity.Cart + err error +} + +// RepositoryMockGetItemsByUserIDOrigins contains origins of expectations of the Repository.GetItemsByUserID +type RepositoryMockGetItemsByUserIDExpectationOrigins struct { + origin string + originCtx string + originUserID string +} + +// Marks this method to be optional. The default behavior of any method with Return() is '1 or more', meaning +// the test will fail minimock's automatic final call check if the mocked method was not called at least once. +// Optional() makes method check to work in '0 or more' mode. +// It is NOT RECOMMENDED to use this option unless you really need it, as default behaviour helps to +// catch the problems when the expected method call is totally skipped during test run. +func (mmGetItemsByUserID *mRepositoryMockGetItemsByUserID) Optional() *mRepositoryMockGetItemsByUserID { + mmGetItemsByUserID.optional = true + return mmGetItemsByUserID +} + +// Expect sets up expected params for Repository.GetItemsByUserID +func (mmGetItemsByUserID *mRepositoryMockGetItemsByUserID) Expect(ctx context.Context, userID entity.UID) *mRepositoryMockGetItemsByUserID { + if mmGetItemsByUserID.mock.funcGetItemsByUserID != nil { + mmGetItemsByUserID.mock.t.Fatalf("RepositoryMock.GetItemsByUserID mock is already set by Set") + } + + if mmGetItemsByUserID.defaultExpectation == nil { + mmGetItemsByUserID.defaultExpectation = &RepositoryMockGetItemsByUserIDExpectation{} + } + + if mmGetItemsByUserID.defaultExpectation.paramPtrs != nil { + mmGetItemsByUserID.mock.t.Fatalf("RepositoryMock.GetItemsByUserID mock is already set by ExpectParams functions") + } + + mmGetItemsByUserID.defaultExpectation.params = &RepositoryMockGetItemsByUserIDParams{ctx, userID} + mmGetItemsByUserID.defaultExpectation.expectationOrigins.origin = minimock.CallerInfo(1) + for _, e := range mmGetItemsByUserID.expectations { + if minimock.Equal(e.params, mmGetItemsByUserID.defaultExpectation.params) { + mmGetItemsByUserID.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmGetItemsByUserID.defaultExpectation.params) + } + } + + return mmGetItemsByUserID +} + +// ExpectCtxParam1 sets up expected param ctx for Repository.GetItemsByUserID +func (mmGetItemsByUserID *mRepositoryMockGetItemsByUserID) ExpectCtxParam1(ctx context.Context) *mRepositoryMockGetItemsByUserID { + if mmGetItemsByUserID.mock.funcGetItemsByUserID != nil { + mmGetItemsByUserID.mock.t.Fatalf("RepositoryMock.GetItemsByUserID mock is already set by Set") + } + + if mmGetItemsByUserID.defaultExpectation == nil { + mmGetItemsByUserID.defaultExpectation = &RepositoryMockGetItemsByUserIDExpectation{} + } + + if mmGetItemsByUserID.defaultExpectation.params != nil { + mmGetItemsByUserID.mock.t.Fatalf("RepositoryMock.GetItemsByUserID mock is already set by Expect") + } + + if mmGetItemsByUserID.defaultExpectation.paramPtrs == nil { + mmGetItemsByUserID.defaultExpectation.paramPtrs = &RepositoryMockGetItemsByUserIDParamPtrs{} + } + mmGetItemsByUserID.defaultExpectation.paramPtrs.ctx = &ctx + mmGetItemsByUserID.defaultExpectation.expectationOrigins.originCtx = minimock.CallerInfo(1) + + return mmGetItemsByUserID +} + +// ExpectUserIDParam2 sets up expected param userID for Repository.GetItemsByUserID +func (mmGetItemsByUserID *mRepositoryMockGetItemsByUserID) ExpectUserIDParam2(userID entity.UID) *mRepositoryMockGetItemsByUserID { + if mmGetItemsByUserID.mock.funcGetItemsByUserID != nil { + mmGetItemsByUserID.mock.t.Fatalf("RepositoryMock.GetItemsByUserID mock is already set by Set") + } + + if mmGetItemsByUserID.defaultExpectation == nil { + mmGetItemsByUserID.defaultExpectation = &RepositoryMockGetItemsByUserIDExpectation{} + } + + if mmGetItemsByUserID.defaultExpectation.params != nil { + mmGetItemsByUserID.mock.t.Fatalf("RepositoryMock.GetItemsByUserID mock is already set by Expect") + } + + if mmGetItemsByUserID.defaultExpectation.paramPtrs == nil { + mmGetItemsByUserID.defaultExpectation.paramPtrs = &RepositoryMockGetItemsByUserIDParamPtrs{} + } + mmGetItemsByUserID.defaultExpectation.paramPtrs.userID = &userID + mmGetItemsByUserID.defaultExpectation.expectationOrigins.originUserID = minimock.CallerInfo(1) + + return mmGetItemsByUserID +} + +// Inspect accepts an inspector function that has same arguments as the Repository.GetItemsByUserID +func (mmGetItemsByUserID *mRepositoryMockGetItemsByUserID) Inspect(f func(ctx context.Context, userID entity.UID)) *mRepositoryMockGetItemsByUserID { + if mmGetItemsByUserID.mock.inspectFuncGetItemsByUserID != nil { + mmGetItemsByUserID.mock.t.Fatalf("Inspect function is already set for RepositoryMock.GetItemsByUserID") + } + + mmGetItemsByUserID.mock.inspectFuncGetItemsByUserID = f + + return mmGetItemsByUserID +} + +// Return sets up results that will be returned by Repository.GetItemsByUserID +func (mmGetItemsByUserID *mRepositoryMockGetItemsByUserID) Return(c2 entity.Cart, err error) *RepositoryMock { + if mmGetItemsByUserID.mock.funcGetItemsByUserID != nil { + mmGetItemsByUserID.mock.t.Fatalf("RepositoryMock.GetItemsByUserID mock is already set by Set") + } + + if mmGetItemsByUserID.defaultExpectation == nil { + mmGetItemsByUserID.defaultExpectation = &RepositoryMockGetItemsByUserIDExpectation{mock: mmGetItemsByUserID.mock} + } + mmGetItemsByUserID.defaultExpectation.results = &RepositoryMockGetItemsByUserIDResults{c2, err} + mmGetItemsByUserID.defaultExpectation.returnOrigin = minimock.CallerInfo(1) + return mmGetItemsByUserID.mock +} + +// Set uses given function f to mock the Repository.GetItemsByUserID method +func (mmGetItemsByUserID *mRepositoryMockGetItemsByUserID) Set(f func(ctx context.Context, userID entity.UID) (c2 entity.Cart, err error)) *RepositoryMock { + if mmGetItemsByUserID.defaultExpectation != nil { + mmGetItemsByUserID.mock.t.Fatalf("Default expectation is already set for the Repository.GetItemsByUserID method") + } + + if len(mmGetItemsByUserID.expectations) > 0 { + mmGetItemsByUserID.mock.t.Fatalf("Some expectations are already set for the Repository.GetItemsByUserID method") + } + + mmGetItemsByUserID.mock.funcGetItemsByUserID = f + mmGetItemsByUserID.mock.funcGetItemsByUserIDOrigin = minimock.CallerInfo(1) + return mmGetItemsByUserID.mock +} + +// When sets expectation for the Repository.GetItemsByUserID which will trigger the result defined by the following +// Then helper +func (mmGetItemsByUserID *mRepositoryMockGetItemsByUserID) When(ctx context.Context, userID entity.UID) *RepositoryMockGetItemsByUserIDExpectation { + if mmGetItemsByUserID.mock.funcGetItemsByUserID != nil { + mmGetItemsByUserID.mock.t.Fatalf("RepositoryMock.GetItemsByUserID mock is already set by Set") + } + + expectation := &RepositoryMockGetItemsByUserIDExpectation{ + mock: mmGetItemsByUserID.mock, + params: &RepositoryMockGetItemsByUserIDParams{ctx, userID}, + expectationOrigins: RepositoryMockGetItemsByUserIDExpectationOrigins{origin: minimock.CallerInfo(1)}, + } + mmGetItemsByUserID.expectations = append(mmGetItemsByUserID.expectations, expectation) + return expectation +} + +// Then sets up Repository.GetItemsByUserID return parameters for the expectation previously defined by the When method +func (e *RepositoryMockGetItemsByUserIDExpectation) Then(c2 entity.Cart, err error) *RepositoryMock { + e.results = &RepositoryMockGetItemsByUserIDResults{c2, err} + return e.mock +} + +// Times sets number of times Repository.GetItemsByUserID should be invoked +func (mmGetItemsByUserID *mRepositoryMockGetItemsByUserID) Times(n uint64) *mRepositoryMockGetItemsByUserID { + if n == 0 { + mmGetItemsByUserID.mock.t.Fatalf("Times of RepositoryMock.GetItemsByUserID mock can not be zero") + } + mm_atomic.StoreUint64(&mmGetItemsByUserID.expectedInvocations, n) + mmGetItemsByUserID.expectedInvocationsOrigin = minimock.CallerInfo(1) + return mmGetItemsByUserID +} + +func (mmGetItemsByUserID *mRepositoryMockGetItemsByUserID) invocationsDone() bool { + if len(mmGetItemsByUserID.expectations) == 0 && mmGetItemsByUserID.defaultExpectation == nil && mmGetItemsByUserID.mock.funcGetItemsByUserID == nil { + return true + } + + totalInvocations := mm_atomic.LoadUint64(&mmGetItemsByUserID.mock.afterGetItemsByUserIDCounter) + expectedInvocations := mm_atomic.LoadUint64(&mmGetItemsByUserID.expectedInvocations) + + return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) +} + +// GetItemsByUserID implements mm_service.Repository +func (mmGetItemsByUserID *RepositoryMock) GetItemsByUserID(ctx context.Context, userID entity.UID) (c2 entity.Cart, err error) { + mm_atomic.AddUint64(&mmGetItemsByUserID.beforeGetItemsByUserIDCounter, 1) + defer mm_atomic.AddUint64(&mmGetItemsByUserID.afterGetItemsByUserIDCounter, 1) + + mmGetItemsByUserID.t.Helper() + + if mmGetItemsByUserID.inspectFuncGetItemsByUserID != nil { + mmGetItemsByUserID.inspectFuncGetItemsByUserID(ctx, userID) + } + + mm_params := RepositoryMockGetItemsByUserIDParams{ctx, userID} + + // Record call args + mmGetItemsByUserID.GetItemsByUserIDMock.mutex.Lock() + mmGetItemsByUserID.GetItemsByUserIDMock.callArgs = append(mmGetItemsByUserID.GetItemsByUserIDMock.callArgs, &mm_params) + mmGetItemsByUserID.GetItemsByUserIDMock.mutex.Unlock() + + for _, e := range mmGetItemsByUserID.GetItemsByUserIDMock.expectations { + if minimock.Equal(*e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.c2, e.results.err + } + } + + if mmGetItemsByUserID.GetItemsByUserIDMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmGetItemsByUserID.GetItemsByUserIDMock.defaultExpectation.Counter, 1) + mm_want := mmGetItemsByUserID.GetItemsByUserIDMock.defaultExpectation.params + mm_want_ptrs := mmGetItemsByUserID.GetItemsByUserIDMock.defaultExpectation.paramPtrs + + mm_got := RepositoryMockGetItemsByUserIDParams{ctx, userID} + + if mm_want_ptrs != nil { + + if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { + mmGetItemsByUserID.t.Errorf("RepositoryMock.GetItemsByUserID got unexpected parameter ctx, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmGetItemsByUserID.GetItemsByUserIDMock.defaultExpectation.expectationOrigins.originCtx, *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + } + + if mm_want_ptrs.userID != nil && !minimock.Equal(*mm_want_ptrs.userID, mm_got.userID) { + mmGetItemsByUserID.t.Errorf("RepositoryMock.GetItemsByUserID got unexpected parameter userID, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmGetItemsByUserID.GetItemsByUserIDMock.defaultExpectation.expectationOrigins.originUserID, *mm_want_ptrs.userID, mm_got.userID, minimock.Diff(*mm_want_ptrs.userID, mm_got.userID)) + } + + } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmGetItemsByUserID.t.Errorf("RepositoryMock.GetItemsByUserID got unexpected parameters, expected at\n%s:\nwant: %#v\n got: %#v%s\n", + mmGetItemsByUserID.GetItemsByUserIDMock.defaultExpectation.expectationOrigins.origin, *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmGetItemsByUserID.GetItemsByUserIDMock.defaultExpectation.results + if mm_results == nil { + mmGetItemsByUserID.t.Fatal("No results are set for the RepositoryMock.GetItemsByUserID") + } + return (*mm_results).c2, (*mm_results).err + } + if mmGetItemsByUserID.funcGetItemsByUserID != nil { + return mmGetItemsByUserID.funcGetItemsByUserID(ctx, userID) + } + mmGetItemsByUserID.t.Fatalf("Unexpected call to RepositoryMock.GetItemsByUserID. %v %v", ctx, userID) + return +} + +// GetItemsByUserIDAfterCounter returns a count of finished RepositoryMock.GetItemsByUserID invocations +func (mmGetItemsByUserID *RepositoryMock) GetItemsByUserIDAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmGetItemsByUserID.afterGetItemsByUserIDCounter) +} + +// GetItemsByUserIDBeforeCounter returns a count of RepositoryMock.GetItemsByUserID invocations +func (mmGetItemsByUserID *RepositoryMock) GetItemsByUserIDBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmGetItemsByUserID.beforeGetItemsByUserIDCounter) +} + +// Calls returns a list of arguments used in each call to RepositoryMock.GetItemsByUserID. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmGetItemsByUserID *mRepositoryMockGetItemsByUserID) Calls() []*RepositoryMockGetItemsByUserIDParams { + mmGetItemsByUserID.mutex.RLock() + + argCopy := make([]*RepositoryMockGetItemsByUserIDParams, len(mmGetItemsByUserID.callArgs)) + copy(argCopy, mmGetItemsByUserID.callArgs) + + mmGetItemsByUserID.mutex.RUnlock() + + return argCopy +} + +// MinimockGetItemsByUserIDDone returns true if the count of the GetItemsByUserID invocations corresponds +// the number of defined expectations +func (m *RepositoryMock) MinimockGetItemsByUserIDDone() bool { + if m.GetItemsByUserIDMock.optional { + // Optional methods provide '0 or more' call count restriction. + return true + } + + for _, e := range m.GetItemsByUserIDMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + return m.GetItemsByUserIDMock.invocationsDone() +} + +// MinimockGetItemsByUserIDInspect logs each unmet expectation +func (m *RepositoryMock) MinimockGetItemsByUserIDInspect() { + for _, e := range m.GetItemsByUserIDMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to RepositoryMock.GetItemsByUserID at\n%s with params: %#v", e.expectationOrigins.origin, *e.params) + } + } + + afterGetItemsByUserIDCounter := mm_atomic.LoadUint64(&m.afterGetItemsByUserIDCounter) + // if default expectation was set then invocations count should be greater than zero + if m.GetItemsByUserIDMock.defaultExpectation != nil && afterGetItemsByUserIDCounter < 1 { + if m.GetItemsByUserIDMock.defaultExpectation.params == nil { + m.t.Errorf("Expected call to RepositoryMock.GetItemsByUserID at\n%s", m.GetItemsByUserIDMock.defaultExpectation.returnOrigin) + } else { + m.t.Errorf("Expected call to RepositoryMock.GetItemsByUserID at\n%s with params: %#v", m.GetItemsByUserIDMock.defaultExpectation.expectationOrigins.origin, *m.GetItemsByUserIDMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcGetItemsByUserID != nil && afterGetItemsByUserIDCounter < 1 { + m.t.Errorf("Expected call to RepositoryMock.GetItemsByUserID at\n%s", m.funcGetItemsByUserIDOrigin) + } + + if !m.GetItemsByUserIDMock.invocationsDone() && afterGetItemsByUserIDCounter > 0 { + m.t.Errorf("Expected %d calls to RepositoryMock.GetItemsByUserID at\n%s but found %d calls", + mm_atomic.LoadUint64(&m.GetItemsByUserIDMock.expectedInvocations), m.GetItemsByUserIDMock.expectedInvocationsOrigin, afterGetItemsByUserIDCounter) + } +} + +// MinimockFinish checks that all mocked methods have been called the expected number of times +func (m *RepositoryMock) MinimockFinish() { + m.finishOnce.Do(func() { + if !m.minimockDone() { + m.MinimockAddItemInspect() + + m.MinimockDeleteItemInspect() + + m.MinimockDeleteItemsByUserIDInspect() + + m.MinimockGetItemsByUserIDInspect() + } + }) +} + +// MinimockWait waits for all mocked methods to be called the expected number of times +func (m *RepositoryMock) MinimockWait(timeout mm_time.Duration) { + timeoutCh := mm_time.After(timeout) + for { + if m.minimockDone() { + return + } + select { + case <-timeoutCh: + m.MinimockFinish() + return + case <-mm_time.After(10 * mm_time.Millisecond): + } + } +} + +func (m *RepositoryMock) minimockDone() bool { + done := true + return done && + m.MinimockAddItemDone() && + m.MinimockDeleteItemDone() && + m.MinimockDeleteItemsByUserIDDone() && + m.MinimockGetItemsByUserIDDone() +} diff --git a/cart/internal/domain/cart/service/service.go b/cart/internal/domain/cart/service/service.go index e5e6790..338f2f6 100644 --- a/cart/internal/domain/cart/service/service.go +++ b/cart/internal/domain/cart/service/service.go @@ -12,6 +12,7 @@ import ( "github.com/rs/zerolog/log" ) +//go:generate minimock -i Repository -o ./mock -s _mock.go type Repository interface { AddItem(ctx context.Context, userID entity.UID, item *model.Item) error GetItemsByUserID(ctx context.Context, userID entity.UID) (entity.Cart, error) @@ -87,7 +88,10 @@ func (s *CartService) GetItemsByUserID(ctx context.Context, userID entity.UID) ( errCh := make(chan error, 1) - var wg sync.WaitGroup + var ( + wg sync.WaitGroup + sumMutex sync.Mutex + ) for idx, sku := range cart.Items { wg.Add(1) @@ -111,7 +115,10 @@ func (s *CartService) GetItemsByUserID(ctx context.Context, userID entity.UID) ( Product: product, Count: count, } + + sumMutex.Lock() resultCart.TotalPrice += uint32(product.Price) * count + sumMutex.Unlock() }(sku, cart.ItemCount[sku], idx) } diff --git a/cart/internal/domain/cart/service/service_test.go b/cart/internal/domain/cart/service/service_test.go new file mode 100644 index 0000000..49aacf1 --- /dev/null +++ b/cart/internal/domain/cart/service/service_test.go @@ -0,0 +1,575 @@ +package service + +import ( + "context" + "errors" + "testing" + + "github.com/gojuno/minimock/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "route256/cart/internal/domain/cart/service/mock" + "route256/cart/internal/domain/entity" + "route256/cart/internal/domain/model" +) + +const ( + validPrice = 123 + validName = "some product name" +) + +type ProductServiceFake struct{} + +func (f *ProductServiceFake) GetProductBySku(_ context.Context, sku entity.Sku) (*model.Product, error) { + if sku%2 == 0 { + return nil, errors.New("empty shelf") + } + + return &model.Product{ + Name: validName, + Price: validPrice, + Sku: sku, + }, nil +} + +func TestCartService_AddItem(t *testing.T) { + t.Parallel() + + ctx := context.Background() + mc := minimock.NewController(t) + + testSKU := entity.Sku(199) + testItem := model.Item{ + Product: &model.Product{ + Name: validName, + Price: 123, + Sku: testSKU, + }, + Count: 1, + } + + type fields struct { + repository Repository + productService ProductService + } + type args struct { + ctx context.Context + item *model.Item + userID entity.UID + } + + tests := []struct { + name string + fields fields + args args + wantErr require.ErrorAssertionFunc + }{ + { + name: "success", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + AddItemMock. + Expect(ctx, 1337, &testItem). + Return(nil), + productService: &ProductServiceFake{}, + }, + args: args{ + ctx: ctx, + item: &testItem, + userID: 1337, + }, + wantErr: require.NoError, + }, + { + name: "invalid item", + fields: fields{ + repository: nil, + productService: nil, + }, + args: args{ + ctx: ctx, + item: &model.Item{ + Product: &model.Product{ + Name: "name", + Price: 123, + Sku: 0, + }, + Count: 0, + }, + userID: 0, + }, + wantErr: require.Error, + }, + { + name: "invalid user id", + fields: fields{ + repository: nil, + productService: nil, + }, + args: args{ + ctx: ctx, + item: &testItem, + userID: 0, + }, + wantErr: require.Error, + }, + { + name: "product service error", + fields: fields{ + repository: nil, + productService: &ProductServiceFake{}, + }, + args: args{ + ctx: ctx, + item: &model.Item{ + Product: &model.Product{ + Name: "", + Price: 0, + Sku: 4, + }, + Count: 1, + }, + userID: 1337, + }, + wantErr: require.Error, + }, + { + name: "repository error", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + AddItemMock. + Expect(ctx, 1337, &testItem). + Return(assert.AnError), + productService: &ProductServiceFake{}, + }, + args: args{ + ctx: ctx, + item: &testItem, + userID: 1337, + }, + wantErr: require.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + s := &CartService{ + repository: tt.fields.repository, + productService: tt.fields.productService, + } + + err := s.AddItem(tt.args.ctx, tt.args.userID, tt.args.item) + tt.wantErr(t, err, "check add review error") + }) + } +} + +func TestCartService_GetItemsByUserID(t *testing.T) { + t.Parallel() + + ctx := context.Background() + mc := minimock.NewController(t) + + testUID := entity.UID(1337) + testSKU := entity.Sku(199) + testModelCart := model.Cart{ + UserID: testUID, + Items: []*model.Item{ + { + Product: &model.Product{ + Name: validName, + Price: validPrice, + Sku: testSKU, + }, + Count: 1, + }, + }, + TotalPrice: validPrice, + } + + testEntityCart := entity.Cart{ + UserID: testUID, + Items: []entity.Sku{testSKU}, + ItemCount: map[entity.Sku]uint32{testSKU: 1}, + } + + type fields struct { + repository Repository + productService ProductService + } + type args struct { + ctx context.Context + userID entity.UID + } + + tests := []struct { + name string + fields fields + args args + want *model.Cart + wantErr require.ErrorAssertionFunc + }{ + { + name: "success 1 item", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + GetItemsByUserIDMock. + Expect(ctx, testUID). + Return(testEntityCart, nil), + productService: &ProductServiceFake{}, + }, + args: args{ + ctx: ctx, + userID: testUID, + }, + want: &testModelCart, + wantErr: require.NoError, + }, + { + name: "success 2 items", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + GetItemsByUserIDMock. + Expect(ctx, testUID). + Return(entity.Cart{ + UserID: testUID, + Items: []entity.Sku{testSKU}, + ItemCount: map[entity.Sku]uint32{testSKU: 2}, + }, nil), + productService: &ProductServiceFake{}, + }, + args: args{ + ctx: ctx, + userID: testUID, + }, + want: &model.Cart{ + UserID: testUID, + Items: []*model.Item{ + { + Product: &model.Product{ + Name: validName, + Price: validPrice, + Sku: testSKU, + }, + Count: 2, + }, + }, + TotalPrice: validPrice * 2, + }, + wantErr: require.NoError, + }, + { + name: "success 2 different items", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + GetItemsByUserIDMock. + Expect(ctx, testUID). + Return(entity.Cart{ + UserID: testUID, + Items: []entity.Sku{testSKU, 1}, + ItemCount: map[entity.Sku]uint32{testSKU: 1, 1: 1}, + }, nil), + productService: &ProductServiceFake{}, + }, + args: args{ + ctx: ctx, + userID: testUID, + }, + want: &model.Cart{ + UserID: testUID, + Items: []*model.Item{ + { + Product: &model.Product{ + Name: validName, + Price: validPrice, + Sku: 1, + }, + Count: 1, + }, + { + Product: &model.Product{ + Name: validName, + Price: validPrice, + Sku: testSKU, + }, + Count: 1, + }, + }, + TotalPrice: validPrice * 2, + }, + wantErr: require.NoError, + }, + { + name: "invalid user id", + fields: fields{ + repository: nil, + productService: nil, + }, + args: args{ + ctx: ctx, + userID: 0, + }, + want: nil, + wantErr: require.Error, + }, + { + name: "repository error", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + GetItemsByUserIDMock. + Expect(ctx, testUID). + Return(entity.Cart{}, assert.AnError), + productService: nil, + }, + args: args{ + ctx: ctx, + userID: testUID, + }, + want: nil, + wantErr: require.Error, + }, + { + name: "empty cart", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + GetItemsByUserIDMock. + Expect(ctx, testUID). + Return(entity.Cart{}, nil), + productService: nil, + }, + args: args{ + ctx: ctx, + userID: testUID, + }, + want: nil, + wantErr: require.Error, + }, + { + name: "product service error", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + GetItemsByUserIDMock. + Expect(ctx, testUID). + Return(entity.Cart{ + UserID: testUID, + Items: []entity.Sku{2}, + ItemCount: map[entity.Sku]uint32{2: 1}, + }, nil), + productService: &ProductServiceFake{}, + }, + args: args{ + ctx: ctx, + userID: testUID, + }, + want: nil, + wantErr: require.Error, + }, + { + name: "product service error", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + GetItemsByUserIDMock. + Expect(ctx, testUID). + Return(entity.Cart{ + UserID: testUID, + Items: []entity.Sku{2}, + ItemCount: map[entity.Sku]uint32{2: 1}, + }, nil), + productService: &ProductServiceFake{}, + }, + args: args{ + ctx: ctx, + userID: testUID, + }, + want: nil, + wantErr: require.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + s := &CartService{ + repository: tt.fields.repository, + productService: tt.fields.productService, + } + + got, err := s.GetItemsByUserID(tt.args.ctx, tt.args.userID) + + tt.wantErr(t, err, "check add review error") + + assert.True(t, assert.EqualExportedValues(t, tt.want, got), "got unexpected cart") + }) + } +} + +func TestCartService_DeleteItem(t *testing.T) { + t.Parallel() + + ctx := context.Background() + mc := minimock.NewController(t) + + testSKU := entity.Sku(199) + testUID := entity.UID(1337) + + type fields struct { + repository Repository + } + type args struct { + ctx context.Context + userID entity.UID + sku entity.Sku + } + + tests := []struct { + name string + fields fields + args args + wantErr require.ErrorAssertionFunc + }{ + { + name: "success", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + DeleteItemMock. + Expect(ctx, testUID, testSKU). + Return(nil), + }, + args: args{ + ctx: ctx, + userID: testUID, + sku: testSKU, + }, + wantErr: require.NoError, + }, + { + name: "invalid user id", + fields: fields{ + repository: nil, + }, + args: args{ + ctx: ctx, + userID: 0, + sku: testSKU, + }, + wantErr: require.Error, + }, + { + name: "invalid sku", + fields: fields{ + repository: nil, + }, + args: args{ + ctx: ctx, + userID: testUID, + sku: 0, + }, + wantErr: require.Error, + }, + { + name: "repository error", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + DeleteItemMock. + Expect(ctx, testUID, testSKU). + Return(assert.AnError), + }, + args: args{ + ctx: ctx, + sku: testSKU, + userID: testUID, + }, + wantErr: require.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + s := &CartService{ + repository: tt.fields.repository, + } + + err := s.DeleteItem(tt.args.ctx, tt.args.userID, tt.args.sku) + tt.wantErr(t, err, "check add review error") + }) + } +} + +func TestCartService_DeleteItemsByUserID(t *testing.T) { + t.Parallel() + + ctx := context.Background() + mc := minimock.NewController(t) + + testUID := entity.UID(1337) + + type fields struct { + repository Repository + } + type args struct { + ctx context.Context + userID entity.UID + } + + tests := []struct { + name string + fields fields + args args + wantErr require.ErrorAssertionFunc + }{ + { + name: "success", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + DeleteItemsByUserIDMock. + Expect(ctx, testUID). + Return(nil), + }, + args: args{ + ctx: ctx, + userID: testUID, + }, + wantErr: require.NoError, + }, + { + name: "invalid user id", + fields: fields{ + repository: nil, + }, + args: args{ + ctx: ctx, + userID: 0, + }, + wantErr: require.Error, + }, + { + name: "repository error", + fields: fields{ + repository: mock.NewRepositoryMock(mc). + DeleteItemsByUserIDMock. + Expect(ctx, testUID). + Return(assert.AnError), + }, + args: args{ + ctx: ctx, + userID: testUID, + }, + wantErr: require.Error, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + s := &CartService{ + repository: tt.fields.repository, + } + + err := s.DeleteItemsByUserID(tt.args.ctx, tt.args.userID) + tt.wantErr(t, err, "check add review error") + }) + } +} diff --git a/cart/tests/integration/cart_integration_test.go b/cart/tests/integration/cart_integration_test.go new file mode 100644 index 0000000..16e59a0 --- /dev/null +++ b/cart/tests/integration/cart_integration_test.go @@ -0,0 +1,193 @@ +//go:build integration +// +build integration + +package integration + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/ozontech/allure-go/pkg/framework/provider" + "github.com/ozontech/allure-go/pkg/framework/suite" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" + + "route256/cart/internal/app" + "route256/cart/internal/domain/cart/repository" + cartService "route256/cart/internal/domain/cart/service" + "route256/cart/internal/domain/entity" + "route256/cart/internal/domain/model" + productsService "route256/cart/internal/domain/products/service" +) + +const ( + productServiceImage = "gitlab-registry.ozon.dev/go/classroom-18/students/base/products:latest" + testToken = "testToken" + + testSku = 1076963 + testName = "Теория нравственных чувств | Смит Адам" + testUID = entity.UID(1337) +) + +type CartHandlerSuite struct { + suite.Suite + + psContainer testcontainers.Container + server *httptest.Server + + cartSvc *cartService.CartService +} + +func TestCartHandlerSuite(t *testing.T) { + suite.RunSuite(t, new(CartHandlerSuite)) +} + +func (s *CartHandlerSuite) BeforeAll(t provider.T) { + ctx := context.Background() + + t.WithNewStep("start product-service container", func(sCtx provider.StepCtx) { + req := testcontainers.ContainerRequest{ + Image: productServiceImage, + ExposedPorts: []string{"8082/tcp"}, + WaitingFor: wait.ForHTTP("/docs").WithStartupTimeout(10 * time.Second), + } + container, err := testcontainers.GenericContainer(ctx, + testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + }) + sCtx.Require().NoError(err, "create container") + s.psContainer = container + }) + + var productURL string + t.WithNewStep("discover product-service URL", func(sCtx provider.StepCtx) { + endpoint, err := s.psContainer.Endpoint(ctx, "") + sCtx.Require().NoError(err) + productURL = endpoint + }) + + t.WithNewStep("init cart-service", func(sCtx provider.StepCtx) { + prodClient := productsService.NewProductService( + *http.DefaultClient, + testToken, + productURL, + ) + + repo := repository.NewInMemoryRepository(10) + s.cartSvc = cartService.NewCartService(repo, prodClient) + + appSrv := &app.App{} + + s.server = httptest.NewServer(appSrv.BootstrapHandlers(s.cartSvc)) + }) +} + +func (s *CartHandlerSuite) AfterAll(t provider.T) { + _ = s.psContainer.Terminate(context.Background()) + s.server.Close() +} + +// DELETE /user//cart/ +func (s *CartHandlerSuite) TestDeleteItemPositive(t provider.T) { + ctx := context.Background() + + item := &model.Item{ + Product: &model.Product{ + Sku: testSku, + }, + Count: 1, + } + + t.WithNewStep("fill cart in usecase", func(sCtx provider.StepCtx) { + err := s.cartSvc.AddItem(context.Background(), + testUID, item) + sCtx.Require().NoError(err) + + cart, err := s.cartSvc.GetItemsByUserID(ctx, testUID) + sCtx.Require().NoError(err) + + sCtx.Require().Len(cart.Items, 1, "check cart contains product") + sCtx.Require().Equal(testUID, cart.UserID, "check user ID equals") + sCtx.Require().Equal(item.Product.Sku, cart.Items[0].Product.Sku, "check product sku equals") + }) + + t.WithNewStep("call DELETE handler", func(sCtx provider.StepCtx) { + url := fmt.Sprintf("%s/user/%d/cart/%d", s.server.URL, testUID, testSku) + + req, err := http.NewRequest(http.MethodDelete, url, nil) + sCtx.Require().NoError(err) + + resp, err := s.server.Client().Do(req) + sCtx.Require().NoError(err) + defer resp.Body.Close() + + sCtx.Require().Equal(http.StatusNoContent, resp.StatusCode, "delete handler unexpected status code") + }) + + t.WithNewStep("check cart empty in usecase", func(sCtx provider.StepCtx) { + _, err := s.cartSvc.GetItemsByUserID(ctx, testUID) + sCtx.Require().Error(err) + }) +} + +// GET /user//cart +func (s *CartHandlerSuite) TestGetCartPositive(t provider.T) { + ctx := context.Background() + + item := &model.Item{ + Product: &model.Product{ + Sku: testSku, + }, + Count: 2, + } + + t.WithNewStep("fill cart in usecase", func(sCtx provider.StepCtx) { + err := s.cartSvc.AddItem(context.Background(), + testUID, item) + sCtx.Require().NoError(err) + + cart, err := s.cartSvc.GetItemsByUserID(ctx, testUID) + sCtx.Require().NoError(err) + + sCtx.Require().Len(cart.Items, 1, "check cart contains product") + sCtx.Require().Equal(testUID, cart.UserID, "check user ID equals") + sCtx.Require().Equal(item.Product.Sku, cart.Items[0].Product.Sku, "check product sku equals") + sCtx.Require().Equal(item.Count, cart.Items[0].Count, "check product count") + }) + + t.WithNewStep("call GET handler", func(sCtx provider.StepCtx) { + url := fmt.Sprintf("%s/user/%d/cart", s.server.URL, testUID) + + req, err := http.NewRequest(http.MethodGet, url, nil) + sCtx.Require().NoError(err) + + resp, err := s.server.Client().Do(req) + sCtx.Require().NoError(err) + defer resp.Body.Close() + + sCtx.Require().Equal(http.StatusOK, resp.StatusCode, "check GET status code") + sCtx.Require().Equal("application/json", resp.Header.Get("Content-Type"), "check GET content type") + + var body struct { + Items []struct { + Sku int64 `json:"sku"` + Name string `json:"name"` + Count uint32 `json:"count"` + Price uint32 `json:"price"` + } `json:"items"` + TotalPrice uint32 `json:"total_price"` + } + sCtx.Require().NoError(json.NewDecoder(resp.Body).Decode(&body)) + + sCtx.Require().Len(body.Items, 1, "check items len") + sCtx.Require().Equal(int64(testSku), body.Items[0].Sku, "check sku") + sCtx.Require().Equal(uint32(2), body.Items[0].Count, "check item count") + sCtx.Require().Greater(body.TotalPrice, uint32(0), "check price") + }) +} diff --git a/docs/README.md b/docs/README.md index 36737af..714fe20 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,7 +2,7 @@ ## Введение - В рамках учебного проекта предстоит реализовать систему, состоящую из нескольких сервисов, которая +В рамках учебного проекта предстоит реализовать систему, состоящую из нескольких сервисов, которая будет моделировать работу простого интернет магазина включая такие бизнес процессы как: - добавление товаров в корзину и их удаление из нее @@ -15,4 +15,4 @@ ## Содержание 1. [Основы Go](./homework-1) - +2. [Тестирование в Go](./homework-2) diff --git a/docs/homework-1/README.md b/docs/homework-1/README.md index 194c3a1..17a84af 100644 --- a/docs/homework-1/README.md +++ b/docs/homework-1/README.md @@ -4,13 +4,13 @@ ## Основное задание - Необходимо имплементировать сервис, отвечающий за работу с корзиной пользователя (сервис Cart). Логика работы методов +Необходимо имплементировать сервис, отвечающий за работу с корзиной пользователя (сервис Cart). Логика работы методов и их контракты описаны ниже. Требования к решению: 1. Используем HTTP, на основе стандартной библиотеки Go 1.23 -2. Для определения существования товара делаем поход в сервис `products` +2. Для определения существования товара делаем поход в сервис `product-service` 3. Состояние храним в in-memory, персистентное хранилище на данный момент не требуется 4. Никакого резерва стоков не делаем, логика простейшая @@ -19,13 +19,13 @@ 1. Делаем Middleware, который будет логировать поступающие запросы 2. Делаем валидацию входящих структур на основе любой Open Source библиотеки (можно подсмотреть тут - https://awesome-go.com/validation/) -3. Делаем ретраи в `products` на 420/429 статус в виде Client Middleware. 3 ретрая, потом ошибка +3. Делаем ретраи в `product-service` на 420/429 статус в виде Client Middleware. 3 ретрая, потом ошибка ## Спецификация ### Добавить товар в корзину - Идентификатором товара является числовой идентификатор SKU. Метод добавляет указанный товар в корзину +Идентификатором товара является числовой идентификатор SKU. Метод добавляет указанный товар в корзину определенного пользователя. Каждый пользователь имеет числовой идентификатор userID. При добавлении в корзину проверяем, что товар существует в специальном сервисе. @@ -54,7 +54,7 @@ | Добавление валидного SKU для пользователя (user_id) с нулевым или отрицательным значением | 400 | Идентификатор пользователя должен быть натуральным числом (больше нуля) | | Добавление SKU с нулевым или отрицательным значением | 400 | SKU должен быть натуральным числом (больше нуля) | | Добавление SKU с нулевым или отрицательным количеством (count) | 400 | Количество должно быть натуральным числом (больше нуля) | -| Добавление несуществующего SKU в корзину | 412 | SKU должен существовать в сервисе `products` | +| Добавление несуществующего SKU в корзину | 412 | SKU должен существовать в сервисе `product-service` | **Диаграмма последовательности:** @@ -63,7 +63,7 @@ ### Удалить товар из корзины - Метод полностью удаляет все количество товара из корзины пользователя. Если у пользователя вовсе нет данной позиции, +Метод полностью удаляет все количество товара из корзины пользователя. Если у пользователя вовсе нет данной позиции, то возвращается такой же ответ, как будто бы все позиции данного sku были успешно удалены. | Метод | URI | @@ -95,7 +95,7 @@ ### Очистить корзину пользователя - Метод полностью очищает корзину пользователя. Если у пользователя нет корзины или она пуста, то, как и при успешной +Метод полностью очищает корзину пользователя. Если у пользователя нет корзины или она пуста, то, как и при успешной очистке корзины, необходимо вернуть код ответа 204 No Content. | Метод | URI | @@ -125,7 +125,7 @@ ### Получить содержимое корзины - Метод возвращает содержимое корзины пользователя на текущий момент. Если корзины у переданного пользователя нет, +Метод возвращает содержимое корзины пользователя на текущий момент. Если корзины у переданного пользователя нет, либо она пуста, следует вернуть 404 код ответа. Товары в корзине упорядочены в порядке возрастания sku. | Метод | URI | @@ -188,14 +188,14 @@ Если, вызвав `make run-all`, развернуть деплоймент, swagger этого сервиса можно увидеть локально по адресу: [http://localhost:8082/docs/](http://localhost:8082/docs/) -Сервис поддерживает следующие операции: +Сервис поддерживает следующие операции: #### GET /product?count=10&start_after_sku=0 -Эта операция имеет два необязательных параметра: - - `count` — сколько элементов вернуть и - - `start_after_sku` — после какого элемента начать вывод. +Эта операция имеет два необязательных параметра: +- `count` — сколько элементов вернуть и +- `start_after_sku` — после какого элемента начать вывод. Response: ``` @@ -221,25 +221,25 @@ Response: } ``` -Обратите внимание, сервис `products` отдаёт цены в `int32` формате. +Обратите внимание, сервис `product-service` отдаёт цены в `int32` формате. В JSON нет беззнаковых целых, поэтому `int32`. Вам же, внутри своих сервисов, для совместимости их между собой -следует использовать беззнаковый тип `uint32`. +следует использовать беззнаковый тип `uint32`. Также следует обратить внимание на то, что он выведен на `localhost:8082` лишь -для вашего удобства работы с данными. +для вашего удобства работы с данными. -Ваш сервис `cart`, запущенный докер-контейнером, сможет подключиться к `products` -по адресу `products:8082` — заниматься маршрутизацией будет сам докер. +Ваш сервис `cart`, запущенный докер-контейнером, сможет подключиться к `product-service` +по адресу `product-service:8082` — заниматься маршрутизацией будет сам докер. -Авторизация на запрос выполняется с помощью заголовка `X-API-KEY` и токена `testToken`. +Авторизация на запрос выполняется с помощью заголовка `X-API-KEY` и токена `testToken`. ## Makefile В рамках данного задания необходимо имплементировать следующие таргеты: -- run-all — запускает сервисы. На данный момент их должно стать два: - - `products` — уже реализован +- run-all — запускает сервисы. На данный момент их должно стать два: + - `product-service` — уже реализован - `cart` — разрабатываемый вами в качестве домашнего задания ## Ожидаемый результат @@ -270,5 +270,5 @@ Response: Сценарий тестирования следует описать в [cart.http](./cart.http) -### Дедлайны сдачи и проверки задания: +### Дедлайны сдачи и проверки задания: - 24 мая 23:59 (сдача) / 27 мая, 23:59 (проверка) diff --git a/docs/homework-1/cart.http b/docs/homework-1/cart.http index aa5ed20..8bbc7b1 100644 --- a/docs/homework-1/cart.http +++ b/docs/homework-1/cart.http @@ -66,7 +66,7 @@ Content-Type: application/json ### delete whole sku from cart DELETE http://localhost:8080/user/31337/cart/1076963 Content-Type: application/json -### expected {} 200 OK; must delete item from cart +### expected {} 204 No Content; must delete item from cart ### delete whole cart DELETE http://localhost:8080/user/31337/cart diff --git a/docs/homework-2/README.md b/docs/homework-2/README.md new file mode 100644 index 0000000..b519e98 --- /dev/null +++ b/docs/homework-2/README.md @@ -0,0 +1,33 @@ +# Домашнее задание по модулю "Тестирование в Go" + +Необходимо покрыть Unit-тестами слой `UseCase` для всех запросов сервиса Cart. Покрыть Unit-тестами репозиторий сервиса Cart (in-memory) + +## Основное задание + +1. Написаны Unit-тесты слоя `UseCase` для каждого запроса +2. Написаны Unit-тесты слоя `Repository` сервиса Cart +3. Процент покрытия тестируемых слоев должен быть не менее 60% +4. Использовать библиотеку `minimock` для создания моков (библиотека minimock — https://github.com/gojuno/minimock) +5. В `Makefile` создать команду для расчета coverage + +## Дополнительное задание + +1. Написать два позитивных e2e теста: для хендлеров `DELETE /user//cart/` и `GET /user//cart` +2. Настроить линтер для проверки цикломатической и когнитивной сложности (https://github.com/fzipp/gocyclo и https://github.com/uudashr/gocognit) +3. Написать бенчмарк для `In-memory Storage` сервиса Cart (хотя бы одну операцию — добавление/удаление и т.д.) + +## Автоматические проверки + +Ваше решение должно проходить автоматические проверки: + +- Компиляция +- Линтер +- Unit-тесты +- Code coverage >40% +- Автотесты + +Прохождение автоматических проверок влияет на итоговую оценку за домашнюю работу. + +### Дедлайны сдачи и проверки задания: + +- 31 мая 23:59 (сдача) / 3 июня, 23:59 (проверка) diff --git a/go.work b/go.work index f1c118c..950f2ec 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.23.1 +go 1.23.9 use ( ./cart diff --git a/go.work.sum b/go.work.sum index 6005469..99d003a 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,8 +1,16 @@ -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/hexdigest/gowrap v1.4.2/go.mod h1:s+1hE6qakgdaaLqgdwPAj5qKYVBCSbPJhEbx+I1ef/Q= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/make/coverage.mk b/make/coverage.mk new file mode 100644 index 0000000..1963120 --- /dev/null +++ b/make/coverage.mk @@ -0,0 +1,28 @@ +COV_REPORT_DIR := .coverage + +define coverage + @if [ -f "$(1)/go.mod" ]; then \ + echo "===== coverage for $(1) ====="; \ + outdir=$(COV_REPORT_DIR)/$(1); \ + mkdir -p $$outdir; \ + pkgs=$$(go list ./$(1)/... | grep -vE 'mock|config|generated|header|document|internal/pb'); \ + if [ -z "$$pkgs" ]; then \ + echo "no packages to test in $(1)"; exit 0; \ + fi; \ + go test -race -covermode=atomic -coverprofile=$$outdir/coverage.out $$pkgs; \ + go tool cover -html=$$outdir/coverage.out -o $$outdir/coverage.html; \ + echo "html report: $$outdir/coverage.html"; \ + fi +endef + +cart-coverage: + $(call coverage,cart) + +loms-coverage: + $(call coverage,loms) + +notifier-coverage: + $(call coverage,notifier) + +comments-coverage: + $(call coverage,comments) diff --git a/make/generate.mk b/make/generate.mk new file mode 100644 index 0000000..aae174a --- /dev/null +++ b/make/generate.mk @@ -0,0 +1,25 @@ + + +define generate + @if [ -f "$(1)/go.mod" ]; then \ + echo "===== running go generate for $(1) ====="; \ + if [ ! -d "$(1)/internal" ]; then \ + echo "no internal package in $(1)"; \ + exit 0; \ + fi; \ + go generate ./$(1)/internal/...; \ + fi +endef + + +cart-generate: + $(call generate,cart) + +loms-generate: + $(call generate,loms) + +notifier-generate: + $(call generate,notifier) + +comments-generate: + $(call generate,comments)