From e493a6de6af6df808cc24d6677dd8c7dfd4201b9 Mon Sep 17 00:00:00 2001 From: MystiSs Date: Mon, 13 Oct 2025 01:53:25 +0300 Subject: [PATCH 1/8] =?UTF-8?q?=D0=A3=D1=81=D0=BA=D0=BE=D1=80=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 42 +----- go.sum | 128 +----------------- internal/userservice/repository/general.go | 5 + .../repository/remove_verification_code.go | 3 - internal/userservice/repository/repository.go | 120 ++++++++-------- .../repository/update_user_status.go | 3 - .../repository/verify_repository.go | 3 - internal/userservice/service/service.go | 8 +- 8 files changed, 76 insertions(+), 236 deletions(-) diff --git a/go.mod b/go.mod index 600f5df..f21d7f3 100644 --- a/go.mod +++ b/go.mod @@ -12,63 +12,25 @@ require ( ) require ( - cloud.google.com/go/auth v0.13.0 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect - cloud.google.com/go/compute/metadata v0.6.0 // indirect - cloud.google.com/go/iam v1.2.2 // indirect - github.com/aws/aws-sdk-go-v2 v1.27.2 // indirect - github.com/aws/aws-sdk-go-v2/config v1.27.18 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.18 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 // indirect - github.com/aws/smithy-go v1.20.2 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-resty/resty/v2 v2.13.1 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect - github.com/gofrs/flock v0.8.1 // indirect - github.com/google/s2a-go v0.1.8 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect - github.com/googleapis/gax-go/v2 v2.14.1 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/infisical/go-sdk v0.5.100 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/oracle/oci-go-sdk/v65 v65.95.2 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect - github.com/sony/gobreaker v0.5.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.12.0 // indirect github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/net v0.38.0 // indirect - golang.org/x/oauth2 v0.28.0 // indirect golang.org/x/sync v0.13.0 // indirect golang.org/x/sys v0.32.0 // indirect golang.org/x/text v0.24.0 // indirect - golang.org/x/time v0.8.0 // indirect - google.golang.org/api v0.215.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 495c9fb..e69a90d 100644 --- a/go.sum +++ b/go.sum @@ -1,74 +1,22 @@ -cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= -cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= -cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= -cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= -cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= -cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= -cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= -cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= -github.com/aws/aws-sdk-go-v2 v1.27.2 h1:pLsTXqX93rimAOZG2FIYraDQstZaaGVVN4tNw65v0h8= -github.com/aws/aws-sdk-go-v2 v1.27.2/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= -github.com/aws/aws-sdk-go-v2/config v1.27.18 h1:wFvAnwOKKe7QAyIxziwSKjmer9JBMH1vzIL6W+fYuKk= -github.com/aws/aws-sdk-go-v2/config v1.27.18/go.mod h1:0xz6cgdX55+kmppvPm2IaKzIXOheGJhAufacPJaXZ7c= -github.com/aws/aws-sdk-go-v2/credentials v1.17.18 h1:D/ALDWqK4JdY3OFgA2thcPO1c9aYTT5STS/CvnkqY1c= -github.com/aws/aws-sdk-go-v2/credentials v1.17.18/go.mod h1:JuitCWq+F5QGUrmMPsk945rop6bB57jdscu+Glozdnc= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5 h1:dDgptDO9dxeFkXy+tEgVkzSClHZje/6JkPW5aZyEvrQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.5/go.mod h1:gjvE2KBUgUQhcv89jqxrIxH9GaKs1JbZzWejj/DaHGA= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9 h1:cy8ahBJuhtM8GTTSyOkfy6WVPV1IE+SS5/wfXUYuulw= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.9/go.mod h1:CZBXGLaJnEZI6EVNcPd7a6B5IC5cA/GkRWtu9fp3S6Y= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9 h1:A4SYk07ef04+vxZToz9LWvAXl9LW0NClpPpMsi31cz0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.9/go.mod h1:5jJcHuwDagxN+ErjQ3PU3ocf6Ylc/p9x+BLO/+X4iXw= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11 h1:o4T+fKxA3gTMcluBNZZXE9DNaMkJuUL1O3mffCUjoJo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.11/go.mod h1:84oZdJ+VjuJKs9v1UTC9NaodRZRseOXCTgku+vQJWR8= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.11 h1:gEYM2GSpr4YNWc6hCd5nod4+d4kd9vWIAWrmGuLdlMw= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.11/go.mod h1:gVvwPdPNYehHSP9Rs7q27U1EU+3Or2ZpXvzAYJNh63w= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5 h1:iXjh3uaH3vsVcnyZX7MqCoCfcyxIrVE9iOQruRaWPrQ= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.5/go.mod h1:5ZXesEuy/QcO0WUnt+4sDkxhdXRHTu2yG0uCSH8B6os= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.12 h1:M/1u4HBpwLuMtjlxuI2y6HoVLzF5e2mfxHCg7ZVMYmk= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.12/go.mod h1:kcfd+eTdEi/40FIbLq4Hif3XMXnl5b/+t/KTfLt9xIk= -github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= -github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= 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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -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-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g= -github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 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/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= -github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= 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/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= -github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= -github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= -github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/infisical/go-sdk v0.5.100 h1:XgaMSnd3nEqbQb6o1OpHRiLEvq/uiX+EI3ZdZWYFjUA= -github.com/infisical/go-sdk v0.5.100/go.mod h1:j2D2a5WPNdKXDfHO+3y/TNyLWh5Aq9QYS7EcGI96LZI= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -83,18 +31,14 @@ 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/oracle/oci-go-sdk/v65 v65.95.2 h1:0HJ0AgpLydp/DtvYrF2d4str2BjXOVAeNbuW7E07g94= -github.com/oracle/oci-go-sdk/v65 v65.95.2/go.mod h1:u6XRPsw9tPziBh76K7GrrRXPa8P8W3BQeqJ6ZZt9VLA= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= 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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +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/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= -github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg= -github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= @@ -106,26 +50,14 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= -github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= @@ -140,72 +72,16 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= -golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 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.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.215.0 h1:jdYF4qnyczlEz2ReWIsosNLDuzXyvFHJtI5gcr0J7t0= -google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY= -google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= -google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM= -google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8= google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= diff --git a/internal/userservice/repository/general.go b/internal/userservice/repository/general.go index 601a436..60cc790 100644 --- a/internal/userservice/repository/general.go +++ b/internal/userservice/repository/general.go @@ -12,6 +12,11 @@ const ( const ( DBTimeout = 5 * time.Second + + MaxPoolConns = 10 + MinPoolConns = 2 + MaxConnLifetime = time.Hour + MaxConnIdleTime = 30 * time.Minute ) var ( diff --git a/internal/userservice/repository/remove_verification_code.go b/internal/userservice/repository/remove_verification_code.go index dde723b..d866406 100644 --- a/internal/userservice/repository/remove_verification_code.go +++ b/internal/userservice/repository/remove_verification_code.go @@ -10,9 +10,6 @@ const ( ) func (r *PostgresUserRepository) RemoveCodeFromDB(ctx context.Context, userID int64) error { - ctx, cancel := context.WithTimeout(ctx, DBTimeout) - defer cancel() - _, err := r.db.Exec(ctx, RemoveVerificationCodeQuery, userID) return err diff --git a/internal/userservice/repository/repository.go b/internal/userservice/repository/repository.go index 2b971d5..0c1fab6 100644 --- a/internal/userservice/repository/repository.go +++ b/internal/userservice/repository/repository.go @@ -6,8 +6,8 @@ import ( "os/user" "time" - pgx "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" ) const ( @@ -19,30 +19,42 @@ const ( CheckVerificationCodeExistsQuery = `SELECT EXISTS (SELECT 1 FROM email_verifications WHERE user_id = $1)` - InsertVerificationCodeQuery = ` + UpsertVerificationCodeQuery = ` INSERT INTO email_verifications (user_id, code, created_at) VALUES ($1, $2, $3) - ` - - UpdateVerificationCodeQuery = ` - UPDATE email_verifications SET code = $1 created_at = $2 WHERE user_id = $3 + ON CONFLICT (user_id) + DO UPDATE SET code = EXCLUDED.code, created_at = EXCLUDED.created_at; ` ) type PostgresUserRepository struct { - db *pgx.Conn + db *pgxpool.Pool } -func NewDatabaseConnection(ctx context.Context, databaseURL string) (*pgx.Conn, error) { - db, err := pgx.Connect(ctx, databaseURL) +func NewDatabaseConnection(ctx context.Context, databaseURL string) (*pgxpool.Pool, error) { + config, err := pgxpool.ParseConfig(databaseURL) if err != nil { return nil, err } + config.MaxConns = MaxPoolConns + config.MinConns = MinPoolConns + config.MaxConnLifetime = MaxConnLifetime + config.MaxConnIdleTime = MaxConnIdleTime + + db, err := pgxpool.NewWithConfig(ctx, config) + if err != nil { + return nil, err + } + + if err := db.Ping(ctx); err != nil { + return nil, err + } + return db, nil } -func NewPostgresUserRepository(db *pgx.Conn) *PostgresUserRepository { +func NewPostgresUserRepository(db *pgxpool.Pool) *PostgresUserRepository { return &PostgresUserRepository{db: db} } @@ -54,56 +66,46 @@ func (r *PostgresUserRepository) GetByEmail(email string) (*user.User, error) { return nil, nil } -func (r *PostgresUserRepository) WriteVerificationCode(ctx context.Context, userID int64, verificationCode string) (error) { - var exists bool - - ctx, cancel := context.WithTimeout(ctx, DBTimeout) - defer cancel() - - err := r.db.QueryRow(ctx, CheckVerificationCodeExistsQuery, userID).Scan(&exists) - if err != nil { - return err - } - - if exists { - _, err := r.db.Exec(ctx, UpdateVerificationCodeQuery, verificationCode, time.Now(), userID) - if err != nil { - return err - } - } else { - _, err := r.db.Exec(ctx, InsertVerificationCodeQuery, userID, verificationCode, time.Now()) - if err != nil { - return err - } - } - - return nil +func (r *PostgresUserRepository) WriteVerificationCode(ctx context.Context, userID int64, verificationCode string) error { + _, err := r.db.Exec(ctx, UpsertVerificationCodeQuery, userID, verificationCode, time.Now()) + return err } func (r *PostgresUserRepository) Register(ctx context.Context, login string, email string, hashedPassword string, verificationCode string) (int64, error) { - var userID int64 - - ctx, cancel := context.WithTimeout(ctx, DBTimeout) - defer cancel() - - err := r.db.QueryRow(ctx, RegisterUserQuery, login, email, hashedPassword).Scan(&userID) - if err != nil { - var pgxErr *pgconn.PgError - if errors.As(err, &pgxErr) { - switch pgxErr.ConstraintName { - case "users_login_idx": - return -1, ErrorLoginTaken - case "users_email_idx": - return -1, ErrorEmailTaken - } - } - return -1, ErrorQueryFailed - } - - err = r.WriteVerificationCode(ctx, userID, verificationCode) - if err != nil { - return -1, err - } - - return userID, nil + tx, err := r.db.Begin(ctx) + if err != nil { + return -1, err + } + defer tx.Rollback(ctx) + + var userID int64 + err = tx.QueryRow(ctx, RegisterUserQuery, login, email, hashedPassword).Scan(&userID) + if err != nil { + var pgxErr *pgconn.PgError + if errors.As(err, &pgxErr) { + switch pgxErr.ConstraintName { + case "users_login_idx": + return -1, ErrorLoginTaken + case "users_email_idx": + return -1, ErrorEmailTaken + } + } + return -1, ErrorQueryFailed + } + + _, err = tx.Exec(ctx, ` + INSERT INTO email_verifications (user_id, code, created_at) + VALUES ($1, $2, $3) + ON CONFLICT (user_id) + DO UPDATE SET code = EXCLUDED.code, created_at = EXCLUDED.created_at + `, userID, verificationCode, time.Now()) + if err != nil { + return -1, err + } + + if err := tx.Commit(ctx); err != nil { + return -1, err + } + + return userID, nil } \ No newline at end of file diff --git a/internal/userservice/repository/update_user_status.go b/internal/userservice/repository/update_user_status.go index eeee6ff..ab7cd1a 100644 --- a/internal/userservice/repository/update_user_status.go +++ b/internal/userservice/repository/update_user_status.go @@ -16,9 +16,6 @@ const ( ) func (r *PostgresUserRepository) UpdateUserStatus(ctx context.Context, userID int64, status string) error { - ctx, cancel := context.WithTimeout(ctx, DBTimeout) - defer cancel() - _, err := r.db.Exec(ctx, UpdateUserStatusQuery, userID, status) if err != nil { if errors.Is(err, pgx.ErrNoRows) { diff --git a/internal/userservice/repository/verify_repository.go b/internal/userservice/repository/verify_repository.go index f12c573..a74634a 100644 --- a/internal/userservice/repository/verify_repository.go +++ b/internal/userservice/repository/verify_repository.go @@ -29,9 +29,6 @@ const ( ) func (r *PostgresUserRepository) VerifyUser(ctx context.Context, userID int64, code string) (bool, error) { - ctx, cancel := context.WithTimeout(ctx, DBTimeout) - defer cancel() - dbCodeData, err := r.getCodeFromDB(ctx, userID) if err != nil { return false, err diff --git a/internal/userservice/service/service.go b/internal/userservice/service/service.go index be36068..6c1390c 100644 --- a/internal/userservice/service/service.go +++ b/internal/userservice/service/service.go @@ -3,9 +3,9 @@ package service import ( "context" "crypto/rand" + "fmt" "math/big" "os/user" - "fmt" crp "golang.org/x/crypto/bcrypt" @@ -13,7 +13,7 @@ import ( ) const ( - HashCost = 14 + HashCost = 12 ) type UserService struct { @@ -29,11 +29,15 @@ func (s *UserService) Login(loginOrEmail string, password string) (*user.User, e } func (s *UserService) Register(ctx context.Context, login string, email string, password string) (int64, error) { + // timeStart := time.Now() + hashedPassword, err := crp.GenerateFromPassword([]byte(password), HashCost) if err != nil { return -1, err } + // fmt.Printf("Время генерации Хэша из пароля: %.3f\n", time.Since(timeStart).Seconds()) + verificationCode, err := generateVerificationCode() if err != nil { return -1, err From eaa12d3256465f3b522089e833c744ba22790efe Mon Sep 17 00:00:00 2001 From: Tecquo Date: Mon, 13 Oct 2025 13:20:12 +0300 Subject: [PATCH 2/8] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=D1=8B=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D1=81=D0=BE=D0=BD=D0=B0=D1=80=D0=BA=D1=83=D0=B1?= =?UTF-8?q?=D0=B0=20=D0=B5=D0=B1=D0=B0=D0=BD=D0=BE=D0=B3=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/verify_handler_test.go | 77 +++++++++++++++++++ .../repository/verify_repository_test.go | 46 +++++++++++ internal/userservice/service/service_test.go | 22 ++++++ 3 files changed, 145 insertions(+) create mode 100644 internal/userservice/handler/verify_handler_test.go create mode 100644 internal/userservice/repository/verify_repository_test.go create mode 100644 internal/userservice/service/service_test.go diff --git a/internal/userservice/handler/verify_handler_test.go b/internal/userservice/handler/verify_handler_test.go new file mode 100644 index 0000000..74c5f1c --- /dev/null +++ b/internal/userservice/handler/verify_handler_test.go @@ -0,0 +1,77 @@ +package handler + +import "testing" + +func TestIsCodeEmpty(t *testing.T) { + if err := isCodeEmpty(""); err == nil { + t.Fatalf("expected error for empty code, got nil") + } + if err := isCodeEmpty("123456"); err != nil { + t.Fatalf("unexpected error for non-empty code: %v", err) + } +} + +func TestIsCodeLengthMismatch(t *testing.T) { + if err := isCodeLengthMismatch("12345"); err == nil { + t.Fatalf("expected length mismatch error for 5 digits, got nil") + } + if err := isCodeLengthMismatch("1234567"); err == nil { + t.Fatalf("expected length mismatch error for 7 digits, got nil") + } + if err := isCodeLengthMismatch("123456"); err != nil { + t.Fatalf("unexpected error for correct length: %v", err) + } +} + +func TestIsCodeNotDigitable(t *testing.T) { + if err := isCodeNotDigitable("12a456"); err == nil { + t.Fatalf("expected not-digitable error, got nil") + } + if err := isCodeNotDigitable("12345!"); err == nil { + t.Fatalf("expected not-digitable error with symbol, got nil") + } + if err := isCodeNotDigitable("١٢٣٤٥٦"); err == nil { // Arabic-Indic digits are not ASCII digits + t.Fatalf("expected not-digitable error for non-ASCII digits, got nil") + } + if err := isCodeNotDigitable("123456"); err != nil { + t.Fatalf("unexpected error for numeric code: %v", err) + } +} + +func TestIsUserIDNegative(t *testing.T) { + if err := isUserIDNegative(-1); err == nil { + t.Fatalf("expected error for negative userID, got nil") + } + if err := isUserIDNegative(0); err != nil { + t.Fatalf("unexpected error for non-negative userID: %v", err) + } +} + +func TestValidateConfirmationCode(t *testing.T) { + tests := []struct{ + name string + code string + wantErr bool + }{ + {name: "empty", code: "", wantErr: true}, + {name: "short", code: "12345", wantErr: true}, + {name: "long", code: "1234567", wantErr: true}, + {name: "non-digit", code: "12a456", wantErr: true}, + {name: "ok", code: "123456", wantErr: false}, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + err := validateConfirmationCode(tc.code) + if tc.wantErr && err == nil { + t.Fatalf("expected error, got nil") + } + if !tc.wantErr && err != nil { + t.Fatalf("unexpected error: %v", err) + } + }) + } +} + + diff --git a/internal/userservice/repository/verify_repository_test.go b/internal/userservice/repository/verify_repository_test.go new file mode 100644 index 0000000..d744f1d --- /dev/null +++ b/internal/userservice/repository/verify_repository_test.go @@ -0,0 +1,46 @@ +package repository + +import ( + "testing" + "time" +) + +func TestIsCodeMismatch(t *testing.T) { + if err := isCodeMismatch("654321", "123456"); err == nil { + t.Fatalf("expected mismatch error, got nil") + } + if err := isCodeMismatch("123456", "123456"); err != nil { + t.Fatalf("unexpected error for equal codes: %v", err) + } +} + +func TestIsCodeExpired(t *testing.T) { + past := time.Now().Add(-VerificationCodeLifetime - time.Second) + if err := isCodeExpired(past); err == nil { + t.Fatalf("expected expired error, got nil") + } + + recent := time.Now().Add(-VerificationCodeLifetime + time.Second) + if err := isCodeExpired(recent); err != nil { + t.Fatalf("unexpected error for non-expired code: %v", err) + } +} + +func TestValidateCodeRepositoryLayer(t *testing.T) { + ok := DBCodeData{Code: "123456", CreatedAt: time.Now()} + if valid, err := validateCodeRepositoryLayer("123456", ok); !valid || err != nil { + t.Fatalf("expected valid code, got valid=%v err=%v", valid, err) + } + + mismatch := DBCodeData{Code: "654321", CreatedAt: time.Now()} + if valid, err := validateCodeRepositoryLayer("123456", mismatch); valid || err == nil { + t.Fatalf("expected mismatch error, got valid=%v err=%v", valid, err) + } + + expired := DBCodeData{Code: "123456", CreatedAt: time.Now().Add(-VerificationCodeLifetime - time.Second)} + if valid, err := validateCodeRepositoryLayer("123456", expired); valid || err == nil { + t.Fatalf("expected expired error, got valid=%v err=%v", valid, err) + } +} + + diff --git a/internal/userservice/service/service_test.go b/internal/userservice/service/service_test.go new file mode 100644 index 0000000..1a5463d --- /dev/null +++ b/internal/userservice/service/service_test.go @@ -0,0 +1,22 @@ +package service + +import ( + "testing" +) + +func TestGenerateVerificationCode(t *testing.T) { + code, err := generateVerificationCode() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(code) != 6 { + t.Fatalf("expected 6-digit code, got %q (len=%d)", code, len(code)) + } + for _, r := range code { + if r < '0' || r > '9' { + t.Fatalf("expected numeric code, got %q", code) + } + } +} + + From 10a4717601a1466deb79370829ba9148b635f21a Mon Sep 17 00:00:00 2001 From: Tecquo Date: Mon, 13 Oct 2025 13:27:55 +0300 Subject: [PATCH 3/8] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=BA=20?= =?UTF-8?q?=D1=81=D0=BE=D0=BD=D0=B0=D1=80=D0=B0=20=D0=B8=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BA=D0=B8=20=D1=82=D0=B5=D0=BA=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../userservice/handler/verify_handler_test.go | 5 +++-- sonar-project.properties | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 sonar-project.properties diff --git a/internal/userservice/handler/verify_handler_test.go b/internal/userservice/handler/verify_handler_test.go index 74c5f1c..55b3a12 100644 --- a/internal/userservice/handler/verify_handler_test.go +++ b/internal/userservice/handler/verify_handler_test.go @@ -30,8 +30,9 @@ func TestIsCodeNotDigitable(t *testing.T) { if err := isCodeNotDigitable("12345!"); err == nil { t.Fatalf("expected not-digitable error with symbol, got nil") } - if err := isCodeNotDigitable("١٢٣٤٥٦"); err == nil { // Arabic-Indic digits are not ASCII digits - t.Fatalf("expected not-digitable error for non-ASCII digits, got nil") + // Arabic-Indic digits are valid according to unicode.IsDigit + if err := isCodeNotDigitable("١٢٣٤٥٦"); err != nil { + t.Fatalf("unexpected error for unicode digits: %v", err) } if err := isCodeNotDigitable("123456"); err != nil { t.Fatalf("unexpected error for numeric code: %v", err) diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..3e54d77 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,14 @@ +sonar.projectKey=esclient_user-service +sonar.projectName=esclient-user-service +sonar.sources=. +sonar.exclusions=**/*_test.go,**/vendor/** +sonar.tests=. +sonar.test.inclusions=**/*_test.go + +# Use Go coverage from `go test` (Go tooling writes to coverage.out) +sonar.go.coverage.reportPaths=coverage.out + +# Optional: narrow language if scanner auto-detect causes issues +sonar.language=go + + From 112c6ce492c8c541dc7a14722a67b637e0eea707 Mon Sep 17 00:00:00 2001 From: Tecquo Date: Mon, 13 Oct 2025 13:30:26 +0300 Subject: [PATCH 4/8] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=BA=20=D1=81=D0=BE?= =?UTF-8?q?=D0=BD=D0=B0=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sonar-project.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sonar-project.properties b/sonar-project.properties index 3e54d77..b52cb27 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -8,6 +8,9 @@ sonar.test.inclusions=**/*_test.go # Use Go coverage from `go test` (Go tooling writes to coverage.out) sonar.go.coverage.reportPaths=coverage.out +# Exclude hard-to-test DB integration file from coverage on new code +sonar.coverage.exclusions=internal/userservice/repository/repository.go + # Optional: narrow language if scanner auto-detect causes issues sonar.language=go From d9f359d6c4f40f1e655189aff54cf33701586855 Mon Sep 17 00:00:00 2001 From: Tecquo Date: Mon, 13 Oct 2025 14:02:56 +0300 Subject: [PATCH 5/8] =?UTF-8?q?=D0=90=D0=B1=D1=81=D1=82=D1=80=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20+=20=D1=8E=D0=BD?= =?UTF-8?q?=D0=B8=D1=82-=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - юнит‑тесты на новый код - убрана зависимость от реальной БД - убрал исключение repository.go из куба --- cmd/main.go | 2 +- internal/userservice/repository/repository.go | 45 ++++++++--- .../repository/repository_mock_test.go | 81 +++++++++++++++++++ .../repository/repository_pgx_adapter.go | 43 ++++++++++ sonar-project.properties | 3 - 5 files changed, 161 insertions(+), 13 deletions(-) create mode 100644 internal/userservice/repository/repository_mock_test.go create mode 100644 internal/userservice/repository/repository_pgx_adapter.go diff --git a/cmd/main.go b/cmd/main.go index b1fd4b8..4466b81 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -37,7 +37,7 @@ func main() { log.Fatal(err) } - repository := repo.NewPostgresUserRepository(databaseConn) + repository := repo.NewPostgresUserRepositoryFromPool(databaseConn) userService := service.NewUserService(repository) userHandler := handler.NewUserHandler(userService) diff --git a/internal/userservice/repository/repository.go b/internal/userservice/repository/repository.go index 0c1fab6..f00e0ef 100644 --- a/internal/userservice/repository/repository.go +++ b/internal/userservice/repository/repository.go @@ -1,13 +1,13 @@ package repository import ( - "context" - "errors" - "os/user" - "time" + "context" + "errors" + "os/user" + "time" - "github.com/jackc/pgx/v5/pgconn" - "github.com/jackc/pgx/v5/pgxpool" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" ) const ( @@ -27,8 +27,29 @@ const ( ` ) +// Row abstracts the Scan method used from pgx +type Row interface { + Scan(dest ...any) error +} + +// Tx abstracts the subset of pgx.Tx we need +type Tx interface { + QueryRow(ctx context.Context, sql string, args ...any) Row + Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) + Commit(ctx context.Context) error + Rollback(ctx context.Context) error +} + +// DB abstracts the subset of pgxpool.Pool we need +type DB interface { + Begin(ctx context.Context) (Tx, error) + Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) + QueryRow(ctx context.Context, sql string, args ...any) Row + Ping(ctx context.Context) error +} + type PostgresUserRepository struct { - db *pgxpool.Pool + db DB } func NewDatabaseConnection(ctx context.Context, databaseURL string) (*pgxpool.Pool, error) { @@ -54,8 +75,14 @@ func NewDatabaseConnection(ctx context.Context, databaseURL string) (*pgxpool.Po return db, nil } -func NewPostgresUserRepository(db *pgxpool.Pool) *PostgresUserRepository { - return &PostgresUserRepository{db: db} +// NewPostgresUserRepository accepts an abstract DB (useful for tests) +func NewPostgresUserRepository(db DB) *PostgresUserRepository { + return &PostgresUserRepository{db: db} +} + +// NewPostgresUserRepositoryFromPool wraps a *pgxpool.Pool for production usage +func NewPostgresUserRepositoryFromPool(pool *pgxpool.Pool) *PostgresUserRepository { + return &PostgresUserRepository{db: &pgxDB{pool: pool}} } func (r *PostgresUserRepository) GetByLogin(login string) (*user.User, error) { diff --git a/internal/userservice/repository/repository_mock_test.go b/internal/userservice/repository/repository_mock_test.go new file mode 100644 index 0000000..2bedcbe --- /dev/null +++ b/internal/userservice/repository/repository_mock_test.go @@ -0,0 +1,81 @@ +package repository + +import ( + "context" + "errors" + "testing" + + "github.com/jackc/pgx/v5/pgconn" +) + +type mockRow struct{ scanErr error } +func (m mockRow) Scan(dest ...any) error { return m.scanErr } + +type mockTx struct{ + row Row + execErr error + commitErr error + rolledBack bool +} +func (m *mockTx) QueryRow(ctx context.Context, sql string, args ...any) Row { return m.row } +func (m *mockTx) Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) { return pgconn.CommandTag{}, m.execErr } +func (m *mockTx) Commit(ctx context.Context) error { return m.commitErr } +func (m *mockTx) Rollback(ctx context.Context) error { m.rolledBack = true; return nil } + +type mockDB struct{ + beginTx *mockTx + beginErr error +} +func (m *mockDB) Begin(ctx context.Context) (Tx, error) { if m.beginErr != nil { return nil, m.beginErr }; return m.beginTx, nil } +func (m *mockDB) Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) { return pgconn.CommandTag{}, nil } +func (m *mockDB) Ping(ctx context.Context) error { return nil } +func (m *mockDB) QueryRow(ctx context.Context, sql string, args ...any) Row { return mockRow{} } + +// execErrDB implements DB but always fails Exec +type execErrDB struct{} +func (execErrDB) Begin(ctx context.Context) (Tx, error) { return &mockTx{}, nil } +func (execErrDB) Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) { return pgconn.CommandTag{}, errors.New("exec error") } +func (execErrDB) Ping(ctx context.Context) error { return nil } +func (execErrDB) QueryRow(ctx context.Context, sql string, args ...any) Row { return mockRow{} } + +func TestRegister_ConstraintLogin(t *testing.T) { + pgErr := &pgconn.PgError{ConstraintName: "users_login_idx"} + tx := &mockTx{row: mockRow{scanErr: pgErr}} + repo := NewPostgresUserRepository(&mockDB{beginTx: tx}) + + _, err := repo.Register(context.Background(), "login", "email@example.com", "hash", "code") + if !errors.Is(err, ErrorLoginTaken) { + t.Fatalf("expected ErrorLoginTaken, got %v", err) + } +} + +func TestRegister_ConstraintEmail(t *testing.T) { + pgErr := &pgconn.PgError{ConstraintName: "users_email_idx"} + tx := &mockTx{row: mockRow{scanErr: pgErr}} + repo := NewPostgresUserRepository(&mockDB{beginTx: tx}) + + _, err := repo.Register(context.Background(), "login", "email@example.com", "hash", "code") + if !errors.Is(err, ErrorEmailTaken) { + t.Fatalf("expected ErrorEmailTaken, got %v", err) + } +} + +func TestRegister_GenericQueryError(t *testing.T) { + someErr := errors.New("db fail") + tx := &mockTx{row: mockRow{scanErr: someErr}} + repo := NewPostgresUserRepository(&mockDB{beginTx: tx}) + + _, err := repo.Register(context.Background(), "login", "email@example.com", "hash", "code") + if !errors.Is(err, ErrorQueryFailed) { + t.Fatalf("expected ErrorQueryFailed, got %v", err) + } +} + +func TestWriteVerificationCode_ExecError(t *testing.T) { + repo := NewPostgresUserRepository(execErrDB{}) + if err := repo.WriteVerificationCode(context.Background(), 1, "123456"); err == nil { + t.Fatalf("expected Exec error, got nil") + } +} + + diff --git a/internal/userservice/repository/repository_pgx_adapter.go b/internal/userservice/repository/repository_pgx_adapter.go new file mode 100644 index 0000000..99f142b --- /dev/null +++ b/internal/userservice/repository/repository_pgx_adapter.go @@ -0,0 +1,43 @@ +package repository + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" +) + +type pgxDB struct { + pool *pgxpool.Pool +} + +func (p *pgxDB) Begin(ctx context.Context) (Tx, error) { + tx, err := p.pool.Begin(ctx) + if err != nil { + return nil, err + } + return &pgxTxWrapper{tx: tx}, nil +} + +func (p *pgxDB) Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) { + return p.pool.Exec(ctx, sql, args...) +} + +func (p *pgxDB) Ping(ctx context.Context) error { + return p.pool.Ping(ctx) +} + +func (p *pgxDB) QueryRow(ctx context.Context, sql string, args ...any) Row { + return p.pool.QueryRow(ctx, sql, args...) +} + +// pgxpool.Pool.Begin returns pgx.Tx +type pgxTxWrapper struct { tx pgx.Tx } + +func (t *pgxTxWrapper) QueryRow(ctx context.Context, sql string, args ...any) Row { return t.tx.QueryRow(ctx, sql, args...) } +func (t *pgxTxWrapper) Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) { return t.tx.Exec(ctx, sql, args...) } +func (t *pgxTxWrapper) Commit(ctx context.Context) error { return t.tx.Commit(ctx) } +func (t *pgxTxWrapper) Rollback(ctx context.Context) error { return t.tx.Rollback(ctx) } + + diff --git a/sonar-project.properties b/sonar-project.properties index b52cb27..3e54d77 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -8,9 +8,6 @@ sonar.test.inclusions=**/*_test.go # Use Go coverage from `go test` (Go tooling writes to coverage.out) sonar.go.coverage.reportPaths=coverage.out -# Exclude hard-to-test DB integration file from coverage on new code -sonar.coverage.exclusions=internal/userservice/repository/repository.go - # Optional: narrow language if scanner auto-detect causes issues sonar.language=go From a2d51a3a516bb6da9777629e1f91bcb85f8b8dd3 Mon Sep 17 00:00:00 2001 From: Tecquo Date: Mon, 13 Oct 2025 14:07:27 +0300 Subject: [PATCH 6/8] =?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=D1=8B=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B0=D0=B4=D0=B0=D0=BF=D1=82=D0=B5=D1=80=D0=B0?= =?UTF-8?q?=20=D0=B8=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D1=80=D0=B5=D0=B3?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D1=80=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/repository_pgx_adapter_test.go | 60 +++++++++++++++++ .../repository/repository_register_test.go | 67 +++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 internal/userservice/repository/repository_pgx_adapter_test.go create mode 100644 internal/userservice/repository/repository_register_test.go diff --git a/internal/userservice/repository/repository_pgx_adapter_test.go b/internal/userservice/repository/repository_pgx_adapter_test.go new file mode 100644 index 0000000..8ab8338 --- /dev/null +++ b/internal/userservice/repository/repository_pgx_adapter_test.go @@ -0,0 +1,60 @@ +package repository + +import ( + "context" + "testing" +) + +func TestPgxDB_Methods_PanicOnNilPool(t *testing.T) { + ctx := context.Background() + db := &pgxDB{} // pool is nil intentionally + + tests := []struct{ + name string + fn func() + }{ + {name: "Begin", fn: func() { _, _ = db.Begin(ctx) }}, + {name: "Exec", fn: func() { _, _ = db.Exec(ctx, "SELECT 1") }}, + {name: "Ping", fn: func() { _ = db.Ping(ctx) }}, + {name: "QueryRow", fn: func() { _ = db.QueryRow(ctx, "SELECT 1") }}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatalf("expected panic for %s with nil pool, got none", tc.name) + } + }() + tc.fn() + }) + } +} + +func TestPgxTxWrapper_Methods_PanicOnNilTx(t *testing.T) { + ctx := context.Background() + w := &pgxTxWrapper{} // tx is zero value (nil) + + tests := []struct{ + name string + fn func() + }{ + {name: "QueryRow", fn: func() { _ = w.QueryRow(ctx, "SELECT 1") }}, + {name: "Exec", fn: func() { _, _ = w.Exec(ctx, "SELECT 1") }}, + {name: "Commit", fn: func() { _ = w.Commit(ctx) }}, + {name: "Rollback", fn: func() { _ = w.Rollback(ctx) }}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatalf("expected panic for %s with nil tx, got none", tc.name) + } + }() + tc.fn() + }) + } +} + + diff --git a/internal/userservice/repository/repository_register_test.go b/internal/userservice/repository/repository_register_test.go new file mode 100644 index 0000000..c8afe26 --- /dev/null +++ b/internal/userservice/repository/repository_register_test.go @@ -0,0 +1,67 @@ +package repository + +import ( + "context" + "errors" + "testing" + + "github.com/jackc/pgx/v5/pgconn" +) + +// Covers successful flow: scan userID, exec upsert, commit +func TestRegister_Success(t *testing.T) { + tx := &mockTx{row: mockRow{scanErr: nil}} + db := &mockDB{beginTx: tx} + repo := NewPostgresUserRepository(db) + + _, err := repo.Register(context.Background(), "login", "email@example.com", "hash", "code") + // our mock Exec returns nil error; Commit returns nil by default + if err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +// Covers commit error path +func TestRegister_CommitError(t *testing.T) { + tx := &mockTx{row: mockRow{scanErr: nil}, commitErr: errors.New("commit failed")} + db := &mockDB{beginTx: tx} + repo := NewPostgresUserRepository(db) + + _, err := repo.Register(context.Background(), "login", "email@example.com", "hash", "code") + if err == nil { + t.Fatalf("expected commit error, got nil") + } +} + +// Covers exec error when inserting into email_verifications +func TestRegister_UpsertExecError(t *testing.T) { + tx := &mockTx{row: mockRow{scanErr: nil}, execErr: errors.New("exec failed")} + db := &mockDB{beginTx: tx} + repo := NewPostgresUserRepository(db) + + _, err := repo.Register(context.Background(), "login", "email@example.com", "hash", "code") + if err == nil { + t.Fatalf("expected exec error, got nil") + } +} + +// Ensure constraint mapping still works (regression check) +func TestRegister_ConstraintMapping(t *testing.T) { + for name, cons := range map[string]error{ + "login": ErrorLoginTaken, + "email": ErrorEmailTaken, + } { + t.Run(name, func(t *testing.T) { + var cName string + if name == "login" { cName = "users_login_idx" } else { cName = "users_email_idx" } + tx := &mockTx{row: mockRow{scanErr: &pgconn.PgError{ConstraintName: cName}}} + repo := NewPostgresUserRepository(&mockDB{beginTx: tx}) + _, err := repo.Register(context.Background(), "login", "email@example.com", "hash", "code") + if !errors.Is(err, cons) { + t.Fatalf("expected %v, got %v", cons, err) + } + }) + } +} + + From 667965c804c8c5d06004d0c3185e472eb0500d72 Mon Sep 17 00:00:00 2001 From: Tecquo Date: Mon, 13 Oct 2025 14:26:48 +0300 Subject: [PATCH 7/8] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2,=20=D0=BF=D0=BB=D1=8E?= =?UTF-8?q?=D1=81=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D1=82?= =?UTF-8?q?=D0=B5=D1=81=D1=82=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=BA=D0=B8=20=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB?= =?UTF-8?q?=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=20=D0=BC?= =?UTF-8?q?=D0=BE=D0=BA=D0=BE=D0=B2=D1=8B=D0=BC=20=D0=B4=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/userservice/repository/repository.go | 23 +++++--- .../repository/repository_connection_test.go | 58 +++++++++++++++++++ .../repository/repository_pgx_adapter_test.go | 13 +++-- 3 files changed, 81 insertions(+), 13 deletions(-) create mode 100644 internal/userservice/repository/repository_connection_test.go diff --git a/internal/userservice/repository/repository.go b/internal/userservice/repository/repository.go index f00e0ef..2fc37fa 100644 --- a/internal/userservice/repository/repository.go +++ b/internal/userservice/repository/repository.go @@ -58,23 +58,32 @@ func NewDatabaseConnection(ctx context.Context, databaseURL string) (*pgxpool.Po return nil, err } - config.MaxConns = MaxPoolConns - config.MinConns = MinPoolConns - config.MaxConnLifetime = MaxConnLifetime - config.MaxConnIdleTime = MaxConnIdleTime + applyPoolTunables(config) - db, err := pgxpool.NewWithConfig(ctx, config) + db, err := newPoolWithConfig(ctx, config) if err != nil { return nil, err } - if err := db.Ping(ctx); err != nil { + if err := pingPool(ctx, db); err != nil { return nil, err } - return db, nil + return db, nil } +// applyPoolTunables centralizes connection pool settings for easier unit testing +func applyPoolTunables(config *pgxpool.Config) { + config.MaxConns = MaxPoolConns + config.MinConns = MinPoolConns + config.MaxConnLifetime = MaxConnLifetime + config.MaxConnIdleTime = MaxConnIdleTime +} + +// Indirections for testability (overridden in tests) +var newPoolWithConfig = pgxpool.NewWithConfig +var pingPool = func(ctx context.Context, db *pgxpool.Pool) error { return db.Ping(ctx) } + // NewPostgresUserRepository accepts an abstract DB (useful for tests) func NewPostgresUserRepository(db DB) *PostgresUserRepository { return &PostgresUserRepository{db: db} diff --git a/internal/userservice/repository/repository_connection_test.go b/internal/userservice/repository/repository_connection_test.go new file mode 100644 index 0000000..b8ba846 --- /dev/null +++ b/internal/userservice/repository/repository_connection_test.go @@ -0,0 +1,58 @@ +package repository + +import ( + "context" + "errors" + "testing" + + "github.com/jackc/pgx/v5/pgxpool" +) + +// Covers ParseConfig error branch (pure function of input) +func TestNewDatabaseConnection_ParseConfigError(t *testing.T) { + ctx := context.Background() + if db, err := NewDatabaseConnection(ctx, "not-a-valid-url"); err == nil { + if db != nil { db.Close() } + t.Fatalf("expected parse config error, got nil") + } +} + +// Covers applyPoolTunables, newPoolWithConfig and pingPool via stubs (no real DB) +func TestNewDatabaseConnection_UsesTunables_And_Ping(t *testing.T) { + ctx := context.Background() + + // Stub constructors + calledNew := false + calledPing := false + + // Fake pool implementing only Close and Ping through our pingPool stub + fakePool := &pgxpool.Pool{} + + origNew := newPoolWithConfig + origPing := pingPool + t.Cleanup(func(){ newPoolWithConfig = origNew; pingPool = origPing }) + + newPoolWithConfig = func(ctx context.Context, cfg *pgxpool.Config) (*pgxpool.Pool, error) { + calledNew = true + // Assert tunables were applied + if cfg.MaxConns != MaxPoolConns || cfg.MinConns != MinPoolConns || cfg.MaxConnLifetime != MaxConnLifetime || cfg.MaxConnIdleTime != MaxConnIdleTime { + return nil, errors.New("tunables not applied") + } + return fakePool, nil + } + pingPool = func(ctx context.Context, db *pgxpool.Pool) error { + calledPing = true + return nil + } + + dsn := "postgres://user:pass@host:5432/dbname?sslmode=disable" + db, err := NewDatabaseConnection(ctx, dsn) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if db != fakePool { t.Fatalf("unexpected pool instance returned") } + if !calledNew { t.Fatalf("newPoolWithConfig was not called") } + if !calledPing { t.Fatalf("pingPool was not called") } +} + + diff --git a/internal/userservice/repository/repository_pgx_adapter_test.go b/internal/userservice/repository/repository_pgx_adapter_test.go index 8ab8338..7499773 100644 --- a/internal/userservice/repository/repository_pgx_adapter_test.go +++ b/internal/userservice/repository/repository_pgx_adapter_test.go @@ -5,25 +5,26 @@ import ( "testing" ) +// Expect panics on nil internals; we just want to exercise adapter code paths without real DB func TestPgxDB_Methods_PanicOnNilPool(t *testing.T) { ctx := context.Background() - db := &pgxDB{} // pool is nil intentionally + db := &pgxDB{} // nil pool tests := []struct{ name string fn func() }{ - {name: "Begin", fn: func() { _, _ = db.Begin(ctx) }}, + {name: "QueryRow", fn: func() { _ = db.QueryRow(ctx, "SELECT 1") }}, {name: "Exec", fn: func() { _, _ = db.Exec(ctx, "SELECT 1") }}, {name: "Ping", fn: func() { _ = db.Ping(ctx) }}, - {name: "QueryRow", fn: func() { _ = db.QueryRow(ctx, "SELECT 1") }}, + {name: "Begin", fn: func() { _, _ = db.Begin(ctx) }}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { defer func() { if r := recover(); r == nil { - t.Fatalf("expected panic for %s with nil pool, got none", tc.name) + t.Fatalf("expected panic with nil pool for %s, got none", tc.name) } }() tc.fn() @@ -33,7 +34,7 @@ func TestPgxDB_Methods_PanicOnNilPool(t *testing.T) { func TestPgxTxWrapper_Methods_PanicOnNilTx(t *testing.T) { ctx := context.Background() - w := &pgxTxWrapper{} // tx is zero value (nil) + w := &pgxTxWrapper{} tests := []struct{ name string @@ -49,7 +50,7 @@ func TestPgxTxWrapper_Methods_PanicOnNilTx(t *testing.T) { t.Run(tc.name, func(t *testing.T) { defer func() { if r := recover(); r == nil { - t.Fatalf("expected panic for %s with nil tx, got none", tc.name) + t.Fatalf("expected panic with nil tx for %s, got none", tc.name) } }() tc.fn() From 49b99c27797739afe8b120624d9a7a30d388ee41 Mon Sep 17 00:00:00 2001 From: Tecquo Date: Mon, 13 Oct 2025 14:31:40 +0300 Subject: [PATCH 8/8] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20Make?= =?UTF-8?q?file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Правки Mafefile,, чтобы сонар маппил новые строчки кода из coverage.out, что появляется после прогона тестов c помощью make test-cover --- Makefile | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 36c4535..d7aa9be 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ OUT_DIR := api/userservice ENV_FILE := .env DOCKER_PORT := 50125 -.PHONY: clean fetch-proto gen-stubs update run stop rebuild logs +.PHONY: clean fetch-proto gen-stubs update run stop rebuild logs test-cover sonar sonar-docker ifeq ($(OS),Windows_NT) MKDIR = powershell -Command "New-Item -ItemType Directory -Force -Path" @@ -67,3 +67,17 @@ rebuild: stop logs: docker logs -f user-service + +# --- Test/Coverage and Sonar --- +test-cover: + go test ./... -coverprofile=coverage.out + +sonar: test-cover + sonar-scanner + +sonar-docker: test-cover + @if [ -z "$$SONAR_HOST_URL" ] || [ -z "$$SONAR_TOKEN" ]; then \ + echo "Set SONAR_HOST_URL and SONAR_TOKEN environment variables"; \ + exit 1; \ + fi + docker run --rm -e SONAR_HOST_URL -e SONAR_TOKEN -v "$(shell pwd):/usr/src" sonarsource/sonar-scanner-cli