Написал тесты, отрефакторил

This commit is contained in:
urec56 2025-02-28 14:11:24 +03:00
parent 7e14ca2232
commit 217a691264
56 changed files with 6128 additions and 1 deletions

0
.env_template Normal file
View file

4
.gitignore vendored
View file

@ -1,2 +1,6 @@
.idea
vendor
.env
proto
grpc
coverage.out

38
Makefile Normal file
View file

@ -0,0 +1,38 @@
.SILENT:
.DEFAULT_GOAL := run
build:
go build -o server cmd/app/main.go
run:
go run cmd/app/main.go
migrate.up:
go run cmd/migrations/up.go
migrate.down:
go run cmd/migrations/down.go
migrate.create:
migrate create -ext sql -dir migrations -seq $(name)
TEST_PKGS := $(shell go list ./... | grep -v "/cmd/" | grep -v "/mocks")
test:
go test -v -short -count=1 -race -coverprofile=coverage.out $(TEST_PKGS)
test.many:
go test -v -short -count=100 -race -coverprofile=coverage.out $(TEST_PKGS)
test.full:
go test -v $(TEST_PKGS)
test.race:
go test -v -race -count=1 $(TEST_PKGS)
test.coverage:
go tool cover -func=coverage.out | grep "total"
go tool cover -html=coverage.out
test.mock:
go generate ./...

View file

@ -1,5 +1,58 @@
package main
func main() {
import (
"context"
"errors"
"net/http"
"os"
"os/signal"
"syscall"
"git.urec56.ru/urec/chat_back_go/config"
"git.urec56.ru/urec/chat_back_go/internal/database"
"git.urec56.ru/urec/chat_back_go/internal/domain"
"git.urec56.ru/urec/chat_back_go/internal/logger"
"git.urec56.ru/urec/chat_back_go/internal/repository"
"git.urec56.ru/urec/chat_back_go/internal/service"
"git.urec56.ru/urec/chat_back_go/internal/transport/rest"
)
func main() {
cfg := config.GetConfig()
log := logger.NewLogger(cfg)
db, err := database.InitPostgres(cfg.Psql)
if err != nil {
log.Fatalf("error occurred while database initialization: %s", err.Error())
}
defer func() {
if err = db.Close(); err != nil {
log.Error(err)
}
}()
domain.Init(log)
repo := repository.NewRepository(db, log)
serv := service.NewService(repo, cfg, log)
srv := rest.NewServer(serv, log, cfg)
go func() {
if err = srv.Run(cfg.Srv.Port); !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("error occurred while running rest server: %s", err.Error())
}
}()
log.Infof("starting server on port %s\n", cfg.Srv.Port)
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Info("shutting down the server")
if err = srv.Shutdown(context.Background()); err != nil {
log.Fatalf("error occurred while shutting down rest server: %s", err.Error())
}
}

41
cmd/migrations/down.go Normal file
View file

@ -0,0 +1,41 @@
package main
import (
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file"
"git.urec56.ru/urec/chat_back_go/config"
"git.urec56.ru/urec/chat_back_go/internal/database"
"git.urec56.ru/urec/chat_back_go/internal/logger"
)
func main() {
cfg := config.GetConfig()
log := logger.NewLogger(cfg)
db, err := database.InitPostgresSql(cfg.Psql)
if err != nil {
log.Fatalf("error occurred while database initialization: %s", err.Error())
}
driver, err := postgres.WithInstance(db, &postgres.Config{})
if err != nil {
log.Fatalf("error occurred while driver initialization: %s", err.Error())
}
m, err := migrate.NewWithDatabaseInstance(
"file://migrations",
"postgres",
driver,
)
if err != nil {
log.Fatalf("error occurred while migration initialization: %s", err.Error())
}
if err = m.Down(); err != nil {
log.Fatal(err)
}
}

41
cmd/migrations/up.go Normal file
View file

@ -0,0 +1,41 @@
package main
import (
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file"
"git.urec56.ru/urec/chat_back_go/config"
"git.urec56.ru/urec/chat_back_go/internal/database"
"git.urec56.ru/urec/chat_back_go/internal/logger"
)
func main() {
cfg := config.GetConfig()
log := logger.NewLogger(cfg)
db, err := database.InitPostgresSql(cfg.Psql)
if err != nil {
log.Fatalf("error occurred while database initialization: %s", err.Error())
}
driver, err := postgres.WithInstance(db, &postgres.Config{})
if err != nil {
log.Fatalf("error occurred while driver initialization: %s", err.Error())
}
m, err := migrate.NewWithDatabaseInstance(
"file://migrations",
"postgres",
driver,
)
if err != nil {
log.Fatalf("error occurred while migration initialization: %s", err.Error())
}
if err = m.Up(); err != nil {
log.Fatal(err)
}
}

6
config.yml Normal file
View file

@ -0,0 +1,6 @@
migrations:
folder: "migrations"
server:
port: 8000
request_id_header: "X-Request-Id"

49
config/config.go Normal file
View file

@ -0,0 +1,49 @@
package config
import (
"github.com/ilyakaznacheev/cleanenv"
log "github.com/sirupsen/logrus"
)
type Config struct {
Psql Postgres
JWT JWT
Srv Server `yaml:"server"`
Mig Migrations `yaml:"migrations"`
Mode string `env:"MODE"`
}
type Postgres struct {
Host string `env:"DB_HOST"`
Port int `env:"DB_PORT"`
User string `env:"DB_USER"`
Password string `env:"DB_PASS"`
Dbname string `env:"DB_NAME"`
Sslmode string `env:"DB_SSLMODE"`
}
type JWT struct {
SecretKey []byte `env:"JWT_SECRET_KEY"`
}
type Server struct {
Port string `yaml:"port"`
RequestIDHeader string `yaml:"request_id_header"`
}
type Migrations struct {
Folder string `yaml:"folder"`
}
func GetConfig() Config {
var cfg Config
if err := cleanenv.ReadConfig(".env", &cfg); err != nil {
log.Fatalf("error occurred while reading config: %s", err)
}
if err := cleanenv.ReadConfig("config.yml", &cfg); err != nil {
log.Fatalf("error occurred while reading config: %s", err)
}
return cfg
}

42
go.mod
View file

@ -1,3 +1,45 @@
module git.urec56.ru/urec/chat_back_go
go 1.22.6
require (
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/go-playground/validator/v10 v10.24.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/golang-migrate/migrate/v4 v4.18.2
github.com/ilyakaznacheev/cleanenv v1.5.0
github.com/jackc/pgx/v5 v5.7.2
github.com/jmoiron/sqlx v1.4.0
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.10.0
github.com/urec56/pathparams v0.0.6
go.uber.org/mock v0.5.0
golang.org/x/crypto v0.33.0
)
require (
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
go.opentelemetry.io/otel/metric v1.32.0 // indirect
go.opentelemetry.io/otel/trace v1.32.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
)

138
go.sum Normal file
View file

@ -0,0 +1,138 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dhui/dktest v0.4.4 h1:+I4s6JRE1yGuqflzwqG+aIaMdgXIorCf5P98JnaAWa8=
github.com/dhui/dktest v0.4.4/go.mod h1:4+22R4lgsdAXrDyaH4Nqx2JEz2hLp49MqQmm9HLCQhM=
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 v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4=
github.com/docker/docker v27.2.0+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/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.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-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=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8=
github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4=
github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk=
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=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
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/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.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
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/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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urec56/pathparams v0.0.6 h1:OuCbamKVdfVtprL+arL5QUuy84R909haBHLburAAp1c=
github.com/urec56/pathparams v0.0.6/go.mod h1:EymabShlKrvvNckqTZ6zVki6wEYTQmcg41nxFtd7G8Y=
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.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
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/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
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=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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=
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=
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ=
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw=

18
internal/database/mock.go Normal file
View file

@ -0,0 +1,18 @@
package database
import (
"github.com/DATA-DOG/go-sqlmock"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert"
"testing"
)
func GetMockDBx(t *testing.T) (*sqlx.DB, sqlmock.Sqlmock, func()) {
db, mock, err := sqlmock.New()
assert.NoError(t, err)
dbx := sqlx.NewDb(db, "sqlmock")
sqlx.BindDriver("sqlmock", sqlx.DOLLAR)
return dbx, mock, func() { _ = dbx.Close() }
}

52
internal/database/psql.go Normal file
View file

@ -0,0 +1,52 @@
package database
import (
"database/sql"
"fmt"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jmoiron/sqlx"
_ "github.com/jackc/pgx/v5/stdlib"
"git.urec56.ru/urec/chat_back_go/config"
)
const IntegrityErrorCode = "23505"
var IntegrityError = &pgconn.PgError{Code: IntegrityErrorCode}
func InitPostgres(cfg config.Postgres) (*sqlx.DB, error) {
db, err := sqlx.Connect(
"pgx", fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.Dbname, cfg.Sslmode,
),
)
if err != nil {
return nil, err
}
err = db.Ping()
if err != nil {
return nil, err
}
return db, nil
}
func InitPostgresSql(cfg config.Postgres) (*sql.DB, error) {
db, err := sql.Open(
"pgx", fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.Dbname, cfg.Sslmode,
),
)
if err != nil {
return nil, err
}
err = db.Ping()
if err != nil {
return nil, err
}
return db, nil
}

15
internal/domain/errors.go Normal file
View file

@ -0,0 +1,15 @@
package domain
import (
"errors"
)
var (
UnverifiedUserError = errors.New("user is not verified")
UserNotFoundError = errors.New("user not found")
InternalServerError = errors.New("internal server error")
UserAlreadyExistsError = errors.New("user already exists")
TokenError = errors.New("invalid auth token")
HashingError = errors.New("error during password hashing")
AnyError = errors.New("any error") // for tests
)

11
internal/domain/log.go Normal file
View file

@ -0,0 +1,11 @@
package domain
import (
"git.urec56.ru/urec/chat_back_go/internal/logger"
)
var log logger.Log
func Init(l logger.Log) {
log = l
}

View file

@ -0,0 +1,19 @@
package domain
import (
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"testing"
mock_logger "git.urec56.ru/urec/chat_back_go/internal/logger/mocks"
)
func Test_Init(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
l := mock_logger.NewMockLog(c)
Init(l)
assert.Equal(t, l, log)
}

100
internal/domain/user.go Normal file
View file

@ -0,0 +1,100 @@
package domain
import (
"database/sql/driver"
"encoding/json"
"fmt"
"time"
)
const (
UnverifiedUser = 0
VerificatedUser = 1
AdminUser = 100
)
type User struct {
ID int `json:"id" db:"id"`
Role int `json:"-" db:"role"`
Username string `json:"username" db:"username"`
Email string `json:"email" db:"email"`
HashedPassword string `json:"-" db:"hashed_password"`
AvatarImage string `json:"avatar_image" db:"avatar_image"`
BlackPhoenix bool `json:"black_phoenix" db:"black_phoenix"`
DateOfBirth CustomDate `json:"date_of_birth" db:"date_of_birth"`
DateOfRegistration CustomDate `json:"date_of_registration" db:"date_of_registration"`
}
func (u User) MarshalJSON() ([]byte, error) {
uMap := make(map[string]any)
uMap["id"] = u.ID
uMap["username"] = u.Username
uMap["email"] = u.Email
uMap["avatar_image"] = u.AvatarImage
uMap["black_phoenix"] = u.BlackPhoenix
uMap["date_of_birth"] = u.DateOfBirth.Format(time.DateOnly)
uMap["date_of_registration"] = u.DateOfRegistration.Format(time.DateOnly)
return json.Marshal(uMap)
}
type UserFilter struct {
Username string `json:"username" validate:"omitempty,min=2,max=30"`
Email string `json:"email" validate:"omitempty,email"`
}
type UserPassword struct {
UserPassword string `json:"user_password" validate:"min=8"`
}
type UserRegister struct {
Email string `json:"email" validate:"email"`
Username string `json:"username" validate:"min=2,max=30"`
Password string `json:"password" validate:"min=8"`
Password2 string `json:"password2" validate:"eqfield=Password"`
DateOfBirth CustomDate `json:"date_of_birth" validate:"date_of_birth"`
}
type CustomDate struct {
time.Time
}
func (ct *CustomDate) UnmarshalJSON(data []byte) error {
str := string(data[1 : len(data)-1])
t, err := time.Parse(time.DateOnly, str)
if err != nil {
return err
}
ct.Time = t
return nil
}
func (ct CustomDate) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("\"%s\"", ct.Format(time.DateOnly))), nil
}
func (ct CustomDate) String() string {
return ct.Format(time.DateOnly)
}
func (ct *CustomDate) Scan(value any) error {
switch v := value.(type) {
case string:
t, err := time.Parse(time.DateOnly, v)
if err != nil {
return err
}
*ct = CustomDate{Time: t}
case time.Time:
*ct = CustomDate{Time: v}
default:
panic("incorrect time type received")
}
return nil
}
func (ct CustomDate) Value() (driver.Value, error) {
return ct.Time.Format(time.DateOnly), nil
}

View file

@ -0,0 +1,290 @@
package domain
import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
// assertJSONEqual сравнивает JSON, игнорируя пробелы и порядок ключей
func assertJSONEqual(t *testing.T, expected string, actual []byte) {
var expectedMap, actualMap map[string]any
if err := json.Unmarshal([]byte(expected), &expectedMap); err != nil {
t.Fatalf("Ошибка десериализации ожидаемого JSON: %v", err)
}
if err := json.Unmarshal(actual, &actualMap); err != nil {
t.Fatalf("Ошибка десериализации полученного JSON: %v", err)
}
if len(expectedMap) != len(actualMap) {
t.Errorf("Разное количество ключей в JSON: ожидалось %d, получено %d", len(expectedMap), len(actualMap))
}
for key, expectedValue := range expectedMap {
actualValue, exists := actualMap[key]
if !exists {
t.Errorf("Ключ %q отсутствует в JSON", key)
} else if actualValue != expectedValue {
t.Errorf("Для ключа %q ожидалось %v, получено %v", key, expectedValue, actualValue)
}
}
}
func TestUserMarshalJSON(t *testing.T) {
validDate := CustomDate{Time: time.Date(1990, time.July, 10, 0, 0, 0, 0, time.UTC)}
regDate := CustomDate{Time: time.Date(2023, time.March, 15, 0, 0, 0, 0, time.UTC)}
testTable := []struct {
name string
user User
expected string
}{
{
name: "ok",
user: User{
ID: 42,
Username: "alice",
Email: "alice@example.com",
AvatarImage: "https://example.com/avatar.png",
BlackPhoenix: true,
DateOfBirth: validDate,
DateOfRegistration: regDate,
},
expected: `{
"id": 42,
"username": "alice",
"email": "alice@example.com",
"avatar_image": "https://example.com/avatar.png",
"black_phoenix": true,
"date_of_birth": "1990-07-10",
"date_of_registration": "2023-03-15"
}`,
},
{
name: "zero_values",
user: User{},
expected: `{
"id": 0,
"username": "",
"email": "",
"avatar_image": "",
"black_phoenix": false,
"date_of_birth": "0001-01-01",
"date_of_registration": "0001-01-01"
}`,
},
{
name: "special_characters",
user: User{
ID: 1,
Username: `test"user`,
Email: "test@example.com\nnewline",
AvatarImage: `https://example.com/avatar.png`,
},
expected: `{
"id": 1,
"username": "test\"user",
"email": "test@example.com\nnewline",
"avatar_image": "https://example.com/avatar.png",
"black_phoenix": false,
"date_of_birth": "0001-01-01",
"date_of_registration": "0001-01-01"
}`,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
result, err := json.Marshal(tc.user)
if err != nil {
t.Fatalf("Ошибка сериализации: %v", err)
}
assertJSONEqual(t, tc.expected, result)
})
}
}
func TestCustomDate_UnmarshalJSON(t *testing.T) {
testTable := []struct {
name string
input string
expectErr bool
expected time.Time
}{
{
name: "ok",
input: `"2024-02-25"`,
expected: time.Date(2024, 2, 25, 0, 0, 0, 0, time.UTC),
},
{
name: "invalid_format",
input: `"25-02-2024"`,
expectErr: true,
},
{
name: "invalid_characters",
input: `"invalid-date"`,
expectErr: true,
},
{
name: "empty_string",
input: `""`,
expectErr: true,
},
{
name: "whitespace",
input: `" "`,
expectErr: true,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
var cd CustomDate
err := json.Unmarshal([]byte(tc.input), &cd)
assert.Equal(t, tc.expectErr, err != nil)
assert.True(t, cd.Time.Equal(tc.expected) || tc.expectErr)
})
}
}
func TestCustomDate_MarshalJSON(t *testing.T) {
testTable := []struct {
name string
date CustomDate
expected string
}{
{
name: "ok_1",
date: CustomDate{Time: time.Date(2024, 2, 25, 0, 0, 0, 0, time.UTC)},
expected: `"2024-02-25"`,
},
{
name: "ok_2",
date: CustomDate{Time: time.Date(2023, 8, 10, 0, 0, 0, 0, time.UTC)},
expected: `"2023-08-10"`,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
result, err := json.Marshal(tc.date)
assert.NoError(t, err)
assert.Equal(t, tc.expected, string(result))
})
}
}
func TestCustomDate_String(t *testing.T) {
testTable := []struct {
name string
date CustomDate
expected string
}{
{
name: "ok_1",
date: CustomDate{Time: time.Date(2024, 2, 25, 0, 0, 0, 0, time.UTC)},
expected: "2024-02-25",
},
{
name: "ok_2",
date: CustomDate{Time: time.Date(2023, 8, 10, 0, 0, 0, 0, time.UTC)},
expected: "2023-08-10",
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
result := tc.date.String()
assert.Equal(t, tc.expected, result)
})
}
}
func TestCustomDate_Scan(t *testing.T) {
testTable := []struct {
name string
dbValue any
expected CustomDate
expectedErr error
panics bool
}{
{
name: "ok_1",
dbValue: "2023-08-10",
expected: CustomDate{Time: time.Date(2023, 8, 10, 0, 0, 0, 0, time.UTC)},
},
{
name: "ok_2",
dbValue: time.Date(2023, 8, 10, 0, 0, 0, 0, time.UTC),
expected: CustomDate{Time: time.Date(2023, 8, 10, 0, 0, 0, 0, time.UTC)},
},
{
name: "invalid_date",
dbValue: "",
expectedErr: &time.ParseError{},
},
{
name: "invalid_type",
panics: true,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
if tc.panics {
defer func() {
if r := recover(); r != nil {
assert.Equal(t, "incorrect time type received", r)
}
}()
}
date := &CustomDate{}
err := date.Scan(tc.dbValue)
if tc.expectedErr != nil {
assert.ErrorAs(t, err, &tc.expectedErr)
} else {
assert.NoError(t, err)
}
assert.Equal(t, tc.expected, *date)
})
}
}
func TestCustomDate_Value(t *testing.T) {
testTable := []struct {
name string
date CustomDate
expected string
}{
{
name: "ok_1",
date: CustomDate{Time: time.Date(2024, 2, 25, 0, 0, 0, 0, time.UTC)},
expected: "2024-02-25",
},
{
name: "ok_2",
date: CustomDate{Time: time.Date(2023, 8, 10, 0, 0, 0, 0, time.UTC)},
expected: "2023-08-10",
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
result, err := tc.date.Value()
assert.NoError(t, err)
date, ok := result.(string)
assert.True(t, ok)
assert.Equal(t, tc.expected, date)
})
}
}

View file

@ -0,0 +1,22 @@
package domain
import (
"github.com/go-playground/validator/v10"
"time"
)
var V = validator.New(validator.WithRequiredStructEnabled())
func init() {
_ = V.RegisterValidation("date_of_birth", dateOfBirthValidation)
}
func dateOfBirthValidation(fl validator.FieldLevel) bool {
cd, _ := fl.Field().Interface().(CustomDate)
if cd.Time.Before(time.Date(1924, time.January, 1, 0, 0, 0, 0, time.UTC)) || cd.Time.After(time.Now().AddDate(-16, 0, 0)) {
return false // дата рождения после 01.01.1924 и юзеру больше 16 лет
}
return true
}

View file

@ -0,0 +1,59 @@
package domain
import (
"github.com/go-playground/validator/v10"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
func Test_dateOfBirthValidation(t *testing.T) {
testCases := []struct {
name string
date struct {
Date CustomDate `validate:"date_of_birth"`
}
expectedErrText string
}{
{
name: "ok",
date: struct {
Date CustomDate `validate:"date_of_birth"`
}{Date: CustomDate{Time: time.Now().AddDate(-18, 0, 0)}},
},
{
name: "exactly_on_boundary",
date: struct {
Date CustomDate `validate:"date_of_birth"`
}{Date: CustomDate{Time: time.Date(1924, time.January, 1, 0, 0, 0, 0, time.UTC)}},
},
{
name: "too_young",
date: struct {
Date CustomDate `validate:"date_of_birth"`
}{Date: CustomDate{Time: time.Now().AddDate(-15, 11, 30)}},
expectedErrText: "Key: 'Date' Error:Field validation for 'Date' failed on the 'date_of_birth' tag",
},
{
name: "too_old",
date: struct {
Date CustomDate `validate:"date_of_birth"`
}{Date: CustomDate{Time: time.Date(1923, time.December, 31, 23, 59, 59, 0, time.UTC)}},
expectedErrText: "Key: 'Date' Error:Field validation for 'Date' failed on the 'date_of_birth' tag",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
validate := validator.New()
_ = validate.RegisterValidation("date_of_birth", dateOfBirthValidation)
err := validate.Struct(tc.date)
if tc.expectedErrText != "" {
assert.EqualError(t, err, tc.expectedErrText)
} else {
assert.NoError(t, err)
}
})
}
}

47
internal/logger/logger.go Normal file
View file

@ -0,0 +1,47 @@
package logger
import (
log "github.com/sirupsen/logrus"
"os"
"git.urec56.ru/urec/chat_back_go/config"
)
//go:generate mockgen -source=logger.go -destination=mocks/mock.go
type Log interface {
log.FieldLogger
}
type Logger struct {
*log.Logger
}
func NewLogger(cfg config.Config) *Logger {
logger := &Logger{Logger: log.New()}
configureLogger(cfg, logger)
return logger
}
func configureLogger(cfg config.Config, logger *Logger) {
switch cfg.Mode {
case "PROD":
logger.SetFormatter(&log.JSONFormatter{})
logger.SetOutput(os.Stdout)
logger.SetLevel(log.WarnLevel)
case "STAGE":
logger.SetFormatter(&log.TextFormatter{})
logger.SetOutput(os.Stdout)
logger.SetLevel(log.InfoLevel)
case "DEV":
logger.SetFormatter(&log.TextFormatter{})
logger.SetOutput(os.Stdout)
logger.SetLevel(log.DebugLevel)
case "TEST":
logger.SetFormatter(&log.TextFormatter{})
logger.SetOutput(os.Stdout)
logger.SetLevel(log.DebugLevel)
default:
logger.Fatal("incorrect MODE was specified")
}
}

View file

@ -0,0 +1,125 @@
package logger
import (
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"io"
"os"
"testing"
"git.urec56.ru/urec/chat_back_go/config"
)
func Test_NewLogger(t *testing.T) {
testTable := []struct {
name string
mode string
expectedFormatter log.Formatter
expectedOut io.Writer
expectedLevel log.Level
}{
{
name: "prod",
mode: "PROD",
expectedFormatter: &log.JSONFormatter{},
expectedOut: os.Stdout,
expectedLevel: log.WarnLevel,
},
{
name: "stage",
mode: "STAGE",
expectedFormatter: &log.TextFormatter{},
expectedOut: os.Stdout,
expectedLevel: log.InfoLevel,
},
{
name: "dev",
mode: "DEV",
expectedFormatter: &log.TextFormatter{},
expectedOut: os.Stdout,
expectedLevel: log.DebugLevel,
},
{
name: "test",
mode: "TEST",
expectedFormatter: &log.TextFormatter{},
expectedOut: os.Stdout,
expectedLevel: log.DebugLevel,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
logger := NewLogger(config.Config{Mode: tc.mode})
assert.Equal(t, tc.expectedFormatter, logger.Formatter)
assert.Equal(t, tc.expectedOut, logger.Out)
assert.Equal(t, tc.expectedLevel, logger.Level)
})
}
}
func Test_configureLogger(t *testing.T) {
testTable := []struct {
name string
mode string
expectedFormatter log.Formatter
expectedOut io.Writer
expectedLevel log.Level
exitCalled bool
}{
{
name: "prod",
mode: "PROD",
expectedFormatter: &log.JSONFormatter{},
expectedOut: os.Stdout,
expectedLevel: log.WarnLevel,
},
{
name: "stage",
mode: "STAGE",
expectedFormatter: &log.TextFormatter{},
expectedOut: os.Stdout,
expectedLevel: log.InfoLevel,
},
{
name: "dev",
mode: "DEV",
expectedFormatter: &log.TextFormatter{},
expectedOut: os.Stdout,
expectedLevel: log.DebugLevel,
},
{
name: "test",
mode: "TEST",
expectedFormatter: &log.TextFormatter{},
expectedOut: os.Stdout,
expectedLevel: log.DebugLevel,
},
{
name: "incorrect_mode",
expectedFormatter: &log.TextFormatter{},
expectedOut: os.Stderr,
expectedLevel: log.InfoLevel,
exitCalled: true,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
logger := &Logger{Logger: log.New()}
exitCalled := false
logger.ExitFunc = func(i int) {
exitCalled = true
}
configureLogger(config.Config{Mode: tc.mode}, logger)
assert.EqualExportedValues(t, tc.expectedFormatter, logger.Formatter)
assert.EqualExportedValues(t, tc.expectedOut, logger.Out)
assert.EqualExportedValues(t, tc.expectedLevel, logger.Level)
assert.Equal(t, tc.exitCalled, exitCalled)
})
}
}

View file

@ -0,0 +1,475 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: logger.go
//
// Generated by this command:
//
// mockgen -source=logger.go -destination=mocks/mock.go
//
// Package mock_logger is a generated GoMock package.
package mock_logger
import (
reflect "reflect"
logrus "github.com/sirupsen/logrus"
gomock "go.uber.org/mock/gomock"
)
// MockLog is a mock of Log interface.
type MockLog struct {
ctrl *gomock.Controller
recorder *MockLogMockRecorder
isgomock struct{}
}
// MockLogMockRecorder is the mock recorder for MockLog.
type MockLogMockRecorder struct {
mock *MockLog
}
// NewMockLog creates a new mock instance.
func NewMockLog(ctrl *gomock.Controller) *MockLog {
mock := &MockLog{ctrl: ctrl}
mock.recorder = &MockLogMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockLog) EXPECT() *MockLogMockRecorder {
return m.recorder
}
// Debug mocks base method.
func (m *MockLog) Debug(args ...any) {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Debug", varargs...)
}
// Debug indicates an expected call of Debug.
func (mr *MockLogMockRecorder) Debug(args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debug", reflect.TypeOf((*MockLog)(nil).Debug), args...)
}
// Debugf mocks base method.
func (m *MockLog) Debugf(format string, args ...any) {
m.ctrl.T.Helper()
varargs := []any{format}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Debugf", varargs...)
}
// Debugf indicates an expected call of Debugf.
func (mr *MockLogMockRecorder) Debugf(format any, args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{format}, args...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debugf", reflect.TypeOf((*MockLog)(nil).Debugf), varargs...)
}
// Debugln mocks base method.
func (m *MockLog) Debugln(args ...any) {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Debugln", varargs...)
}
// Debugln indicates an expected call of Debugln.
func (mr *MockLogMockRecorder) Debugln(args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Debugln", reflect.TypeOf((*MockLog)(nil).Debugln), args...)
}
// Error mocks base method.
func (m *MockLog) Error(args ...any) {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Error", varargs...)
}
// Error indicates an expected call of Error.
func (mr *MockLogMockRecorder) Error(args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Error", reflect.TypeOf((*MockLog)(nil).Error), args...)
}
// Errorf mocks base method.
func (m *MockLog) Errorf(format string, args ...any) {
m.ctrl.T.Helper()
varargs := []any{format}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Errorf", varargs...)
}
// Errorf indicates an expected call of Errorf.
func (mr *MockLogMockRecorder) Errorf(format any, args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{format}, args...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Errorf", reflect.TypeOf((*MockLog)(nil).Errorf), varargs...)
}
// Errorln mocks base method.
func (m *MockLog) Errorln(args ...any) {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Errorln", varargs...)
}
// Errorln indicates an expected call of Errorln.
func (mr *MockLogMockRecorder) Errorln(args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Errorln", reflect.TypeOf((*MockLog)(nil).Errorln), args...)
}
// Fatal mocks base method.
func (m *MockLog) Fatal(args ...any) {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Fatal", varargs...)
}
// Fatal indicates an expected call of Fatal.
func (mr *MockLogMockRecorder) Fatal(args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fatal", reflect.TypeOf((*MockLog)(nil).Fatal), args...)
}
// Fatalf mocks base method.
func (m *MockLog) Fatalf(format string, args ...any) {
m.ctrl.T.Helper()
varargs := []any{format}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Fatalf", varargs...)
}
// Fatalf indicates an expected call of Fatalf.
func (mr *MockLogMockRecorder) Fatalf(format any, args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{format}, args...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fatalf", reflect.TypeOf((*MockLog)(nil).Fatalf), varargs...)
}
// Fatalln mocks base method.
func (m *MockLog) Fatalln(args ...any) {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Fatalln", varargs...)
}
// Fatalln indicates an expected call of Fatalln.
func (mr *MockLogMockRecorder) Fatalln(args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fatalln", reflect.TypeOf((*MockLog)(nil).Fatalln), args...)
}
// Info mocks base method.
func (m *MockLog) Info(args ...any) {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Info", varargs...)
}
// Info indicates an expected call of Info.
func (mr *MockLogMockRecorder) Info(args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Info", reflect.TypeOf((*MockLog)(nil).Info), args...)
}
// Infof mocks base method.
func (m *MockLog) Infof(format string, args ...any) {
m.ctrl.T.Helper()
varargs := []any{format}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Infof", varargs...)
}
// Infof indicates an expected call of Infof.
func (mr *MockLogMockRecorder) Infof(format any, args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{format}, args...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Infof", reflect.TypeOf((*MockLog)(nil).Infof), varargs...)
}
// Infoln mocks base method.
func (m *MockLog) Infoln(args ...any) {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Infoln", varargs...)
}
// Infoln indicates an expected call of Infoln.
func (mr *MockLogMockRecorder) Infoln(args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Infoln", reflect.TypeOf((*MockLog)(nil).Infoln), args...)
}
// Panic mocks base method.
func (m *MockLog) Panic(args ...any) {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Panic", varargs...)
}
// Panic indicates an expected call of Panic.
func (mr *MockLogMockRecorder) Panic(args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Panic", reflect.TypeOf((*MockLog)(nil).Panic), args...)
}
// Panicf mocks base method.
func (m *MockLog) Panicf(format string, args ...any) {
m.ctrl.T.Helper()
varargs := []any{format}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Panicf", varargs...)
}
// Panicf indicates an expected call of Panicf.
func (mr *MockLogMockRecorder) Panicf(format any, args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{format}, args...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Panicf", reflect.TypeOf((*MockLog)(nil).Panicf), varargs...)
}
// Panicln mocks base method.
func (m *MockLog) Panicln(args ...any) {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Panicln", varargs...)
}
// Panicln indicates an expected call of Panicln.
func (mr *MockLogMockRecorder) Panicln(args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Panicln", reflect.TypeOf((*MockLog)(nil).Panicln), args...)
}
// Print mocks base method.
func (m *MockLog) Print(args ...any) {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Print", varargs...)
}
// Print indicates an expected call of Print.
func (mr *MockLogMockRecorder) Print(args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Print", reflect.TypeOf((*MockLog)(nil).Print), args...)
}
// Printf mocks base method.
func (m *MockLog) Printf(format string, args ...any) {
m.ctrl.T.Helper()
varargs := []any{format}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Printf", varargs...)
}
// Printf indicates an expected call of Printf.
func (mr *MockLogMockRecorder) Printf(format any, args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{format}, args...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Printf", reflect.TypeOf((*MockLog)(nil).Printf), varargs...)
}
// Println mocks base method.
func (m *MockLog) Println(args ...any) {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Println", varargs...)
}
// Println indicates an expected call of Println.
func (mr *MockLogMockRecorder) Println(args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Println", reflect.TypeOf((*MockLog)(nil).Println), args...)
}
// Warn mocks base method.
func (m *MockLog) Warn(args ...any) {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Warn", varargs...)
}
// Warn indicates an expected call of Warn.
func (mr *MockLogMockRecorder) Warn(args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warn", reflect.TypeOf((*MockLog)(nil).Warn), args...)
}
// Warnf mocks base method.
func (m *MockLog) Warnf(format string, args ...any) {
m.ctrl.T.Helper()
varargs := []any{format}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Warnf", varargs...)
}
// Warnf indicates an expected call of Warnf.
func (mr *MockLogMockRecorder) Warnf(format any, args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{format}, args...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warnf", reflect.TypeOf((*MockLog)(nil).Warnf), varargs...)
}
// Warning mocks base method.
func (m *MockLog) Warning(args ...any) {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Warning", varargs...)
}
// Warning indicates an expected call of Warning.
func (mr *MockLogMockRecorder) Warning(args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warning", reflect.TypeOf((*MockLog)(nil).Warning), args...)
}
// Warningf mocks base method.
func (m *MockLog) Warningf(format string, args ...any) {
m.ctrl.T.Helper()
varargs := []any{format}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Warningf", varargs...)
}
// Warningf indicates an expected call of Warningf.
func (mr *MockLogMockRecorder) Warningf(format any, args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{format}, args...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warningf", reflect.TypeOf((*MockLog)(nil).Warningf), varargs...)
}
// Warningln mocks base method.
func (m *MockLog) Warningln(args ...any) {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Warningln", varargs...)
}
// Warningln indicates an expected call of Warningln.
func (mr *MockLogMockRecorder) Warningln(args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warningln", reflect.TypeOf((*MockLog)(nil).Warningln), args...)
}
// Warnln mocks base method.
func (m *MockLog) Warnln(args ...any) {
m.ctrl.T.Helper()
varargs := []any{}
for _, a := range args {
varargs = append(varargs, a)
}
m.ctrl.Call(m, "Warnln", varargs...)
}
// Warnln indicates an expected call of Warnln.
func (mr *MockLogMockRecorder) Warnln(args ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Warnln", reflect.TypeOf((*MockLog)(nil).Warnln), args...)
}
// WithError mocks base method.
func (m *MockLog) WithError(err error) *logrus.Entry {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "WithError", err)
ret0, _ := ret[0].(*logrus.Entry)
return ret0
}
// WithError indicates an expected call of WithError.
func (mr *MockLogMockRecorder) WithError(err any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithError", reflect.TypeOf((*MockLog)(nil).WithError), err)
}
// WithField mocks base method.
func (m *MockLog) WithField(key string, value any) *logrus.Entry {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "WithField", key, value)
ret0, _ := ret[0].(*logrus.Entry)
return ret0
}
// WithField indicates an expected call of WithField.
func (mr *MockLogMockRecorder) WithField(key, value any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithField", reflect.TypeOf((*MockLog)(nil).WithField), key, value)
}
// WithFields mocks base method.
func (m *MockLog) WithFields(fields logrus.Fields) *logrus.Entry {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "WithFields", fields)
ret0, _ := ret[0].(*logrus.Entry)
return ret0
}
// WithFields indicates an expected call of WithFields.
func (mr *MockLogMockRecorder) WithFields(fields any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithFields", reflect.TypeOf((*MockLog)(nil).WithFields), fields)
}

View file

@ -0,0 +1,210 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: repository.go
//
// Generated by this command:
//
// mockgen -source=repository.go -destination=mocks/mock.go
//
// Package mock_repository is a generated GoMock package.
package mock_repository
import (
reflect "reflect"
time "time"
domain "git.urec56.ru/urec/chat_back_go/internal/domain"
gomock "go.uber.org/mock/gomock"
)
// MockUser is a mock of User interface.
type MockUser struct {
ctrl *gomock.Controller
recorder *MockUserMockRecorder
isgomock struct{}
}
// MockUserMockRecorder is the mock recorder for MockUser.
type MockUserMockRecorder struct {
mock *MockUser
}
// NewMockUser creates a new mock instance.
func NewMockUser(ctrl *gomock.Controller) *MockUser {
mock := &MockUser{ctrl: ctrl}
mock.recorder = &MockUserMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockUser) EXPECT() *MockUserMockRecorder {
return m.recorder
}
// FindOne mocks base method.
func (m *MockUser) FindOne(username, email string) (domain.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOne", username, email)
ret0, _ := ret[0].(domain.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOne indicates an expected call of FindOne.
func (mr *MockUserMockRecorder) FindOne(username, email any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockUser)(nil).FindOne), username, email)
}
// GetAll mocks base method.
func (m *MockUser) GetAll(username string) ([]domain.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAll", username)
ret0, _ := ret[0].([]domain.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAll indicates an expected call of GetAll.
func (mr *MockUserMockRecorder) GetAll(username any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAll", reflect.TypeOf((*MockUser)(nil).GetAll), username)
}
// GetByID mocks base method.
func (m *MockUser) GetByID(userID int) (domain.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetByID", userID)
ret0, _ := ret[0].(domain.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetByID indicates an expected call of GetByID.
func (mr *MockUserMockRecorder) GetByID(userID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByID", reflect.TypeOf((*MockUser)(nil).GetByID), userID)
}
// Register mocks base method.
func (m *MockUser) Register(email, hashedPassword, username string, dateOfBirth time.Time) (domain.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Register", email, hashedPassword, username, dateOfBirth)
ret0, _ := ret[0].(domain.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Register indicates an expected call of Register.
func (mr *MockUserMockRecorder) Register(email, hashedPassword, username, dateOfBirth any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockUser)(nil).Register), email, hashedPassword, username, dateOfBirth)
}
// MockChat is a mock of Chat interface.
type MockChat struct {
ctrl *gomock.Controller
recorder *MockChatMockRecorder
isgomock struct{}
}
// MockChatMockRecorder is the mock recorder for MockChat.
type MockChatMockRecorder struct {
mock *MockChat
}
// NewMockChat creates a new mock instance.
func NewMockChat(ctrl *gomock.Controller) *MockChat {
mock := &MockChat{ctrl: ctrl}
mock.recorder = &MockChatMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockChat) EXPECT() *MockChatMockRecorder {
return m.recorder
}
// MockRepo is a mock of Repo interface.
type MockRepo struct {
ctrl *gomock.Controller
recorder *MockRepoMockRecorder
isgomock struct{}
}
// MockRepoMockRecorder is the mock recorder for MockRepo.
type MockRepoMockRecorder struct {
mock *MockRepo
}
// NewMockRepo creates a new mock instance.
func NewMockRepo(ctrl *gomock.Controller) *MockRepo {
mock := &MockRepo{ctrl: ctrl}
mock.recorder = &MockRepoMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockRepo) EXPECT() *MockRepoMockRecorder {
return m.recorder
}
// FindOne mocks base method.
func (m *MockRepo) FindOne(username, email string) (domain.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOne", username, email)
ret0, _ := ret[0].(domain.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOne indicates an expected call of FindOne.
func (mr *MockRepoMockRecorder) FindOne(username, email any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockRepo)(nil).FindOne), username, email)
}
// GetAll mocks base method.
func (m *MockRepo) GetAll(username string) ([]domain.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAll", username)
ret0, _ := ret[0].([]domain.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAll indicates an expected call of GetAll.
func (mr *MockRepoMockRecorder) GetAll(username any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAll", reflect.TypeOf((*MockRepo)(nil).GetAll), username)
}
// GetByID mocks base method.
func (m *MockRepo) GetByID(userID int) (domain.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetByID", userID)
ret0, _ := ret[0].(domain.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetByID indicates an expected call of GetByID.
func (mr *MockRepoMockRecorder) GetByID(userID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByID", reflect.TypeOf((*MockRepo)(nil).GetByID), userID)
}
// Register mocks base method.
func (m *MockRepo) Register(email, hashedPassword, username string, dateOfBirth time.Time) (domain.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Register", email, hashedPassword, username, dateOfBirth)
ret0, _ := ret[0].(domain.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Register indicates an expected call of Register.
func (mr *MockRepoMockRecorder) Register(email, hashedPassword, username, dateOfBirth any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockRepo)(nil).Register), email, hashedPassword, username, dateOfBirth)
}

View file

@ -0,0 +1,36 @@
package repository
import (
"github.com/jmoiron/sqlx"
"time"
"git.urec56.ru/urec/chat_back_go/internal/domain"
"git.urec56.ru/urec/chat_back_go/internal/logger"
)
//go:generate mockgen -source=repository.go -destination=mocks/mock.go
type User interface {
GetByID(userID int) (domain.User, error)
GetAll(username string) ([]domain.User, error)
FindOne(username, email string) (domain.User, error)
Register(email, hashedPassword, username string, dateOfBirth time.Time) (domain.User, error)
}
type Chat interface{}
type Repo interface {
User
Chat
}
type Repository struct {
User
Chat
}
func NewRepository(db *sqlx.DB, l logger.Log) *Repository {
return &Repository{
User: newUser(db, l),
}
}

View file

@ -0,0 +1,23 @@
package repository
import (
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"testing"
"git.urec56.ru/urec/chat_back_go/internal/database"
mock_logger "git.urec56.ru/urec/chat_back_go/internal/logger/mocks"
)
func Test_NewRepository(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
db, _, dbClose := database.GetMockDBx(t)
defer dbClose()
log := mock_logger.NewMockLog(c)
repo := NewRepository(db, log)
assert.Equal(t, &Repository{User: &userRepository{db: db, l: log}}, repo)
}

View file

@ -0,0 +1,120 @@
package repository
import (
"database/sql"
"errors"
"fmt"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jmoiron/sqlx"
"strings"
"time"
"git.urec56.ru/urec/chat_back_go/internal/database"
"git.urec56.ru/urec/chat_back_go/internal/domain"
"git.urec56.ru/urec/chat_back_go/internal/logger"
)
type userRepository struct {
db *sqlx.DB
l logger.Log
}
func newUser(db *sqlx.DB, l logger.Log) *userRepository {
return &userRepository{db: db, l: l}
}
func (r *userRepository) GetByID(userID int) (domain.User, error) {
var user domain.User
query := `SELECT * FROM users WHERE id = $1`
if err := r.db.Get(&user, query, userID); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return domain.User{}, domain.UserNotFoundError
}
return domain.User{}, domain.InternalServerError
}
return user, nil
}
func (r *userRepository) GetAll(username string) ([]domain.User, error) {
var users []domain.User
username = fmt.Sprint("%", username, "%")
query := fmt.Sprintf(`SELECT * FROM users WHERE username ILIKE $1 AND role != %d`, domain.AdminUser)
err := r.db.Select(&users, query, username)
if err != nil {
r.l.Errorf("getting users: %s", err.Error())
return nil, domain.InternalServerError
}
return users, nil
}
func (r *userRepository) FindOne(username, email string) (domain.User, error) {
var conditions []string
var args []interface{}
var user domain.User
query := `SELECT * FROM users`
if username != "" {
conditions = append(conditions, `username = ?`)
args = append(args, username)
}
if email != "" {
conditions = append(conditions, `email = ?`)
args = append(args, email)
}
if len(conditions) > 0 {
query += ` WHERE ` + strings.Join(conditions, ` AND `)
}
query += ` LIMIT 1`
query = r.db.Rebind(query)
err := r.db.Get(&user, query, args...)
if err != nil {
return domain.User{}, domain.InternalServerError
}
return user, nil
}
func (r *userRepository) Register(email, hashedPassword, username string, dateOfBirth time.Time) (domain.User, error) {
var user domain.User
uQuery := `INSERT INTO users (email, hashed_password, username, date_of_birth) VALUES ($1, $2, $3, $4) RETURNING *`
aQuery := `INSERT INTO user_avatar (user_id, avatar_image) VALUES ($1, $2)`
tx, err := r.db.Beginx()
if err != nil {
r.l.Errorf("user registration: tx begin: %s", err.Error())
return domain.User{}, domain.InternalServerError
}
defer func() {
if err != nil {
var pgError *pgconn.PgError
if !errors.As(err, &pgError) {
r.l.Errorf("user registration: %s", err.Error())
}
if err = tx.Rollback(); err != nil && !errors.Is(err, sql.ErrTxDone) {
r.l.Errorf("user registration: tx rollback: %s", err.Error())
}
}
}()
if err = tx.Get(&user, uQuery, email, hashedPassword, username, dateOfBirth); err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.SQLState() == database.IntegrityErrorCode {
return domain.User{}, domain.UserAlreadyExistsError
}
return domain.User{}, domain.InternalServerError
}
if _, err = tx.Exec(aQuery, user.ID, user.AvatarImage); err != nil {
return domain.User{}, domain.InternalServerError
}
if err = tx.Commit(); err != nil {
return domain.User{}, domain.InternalServerError
}
return user, nil
}

View file

@ -0,0 +1,695 @@
package repository
import (
"database/sql"
"errors"
"fmt"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"testing"
"time"
"git.urec56.ru/urec/chat_back_go/internal/database"
"git.urec56.ru/urec/chat_back_go/internal/domain"
mock_logger "git.urec56.ru/urec/chat_back_go/internal/logger/mocks"
)
func Test_newUser(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
db, _, dbClose := database.GetMockDBx(t)
defer dbClose()
log := mock_logger.NewMockLog(c)
repo := newUser(db, log)
assert.Equal(t, &userRepository{db: db, l: log}, repo)
}
func TestUserRepository_GetByID(t *testing.T) {
type mockBehavior func(mock sqlmock.Sqlmock, userID int, rows *sqlmock.Rows, err error)
testTable := []struct {
name string
userID int
mockBehavior mockBehavior
dbRows *sqlmock.Rows
dbErr error
ud domain.User
expectedUser domain.User
expectedErr error
}{
{
name: "ok",
userID: 1,
mockBehavior: func(mock sqlmock.Sqlmock, userID int, rows *sqlmock.Rows, err error) {
mock.ExpectQuery(`SELECT \* FROM users WHERE id = \$1`).WithArgs(userID).WillReturnRows(rows)
},
dbRows: sqlmock.NewRows(
[]string{
"id",
"role",
"username",
"email",
"hashed_password",
"avatar_image",
"black_phoenix",
"date_of_birth",
"date_of_registration",
},
),
ud: domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "",
BlackPhoenix: false,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
expectedUser: domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "",
BlackPhoenix: false,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
},
{
name: "user_not_found",
mockBehavior: func(mock sqlmock.Sqlmock, userID int, rows *sqlmock.Rows, err error) {
mock.ExpectQuery(`SELECT \* FROM users WHERE id = \$1`).WithArgs(userID).WillReturnError(err)
},
dbRows: sqlmock.NewRows(
[]string{
"id",
"role",
"username",
"email",
"hashed_password",
"avatar_image",
"black_phoenix",
"date_of_birth",
"date_of_registration",
},
),
dbErr: sql.ErrNoRows,
expectedErr: domain.UserNotFoundError,
},
{
name: "internal_error",
mockBehavior: func(mock sqlmock.Sqlmock, userID int, rows *sqlmock.Rows, err error) {
mock.ExpectQuery(`SELECT \* FROM users WHERE id = \$1`).WithArgs(userID).WillReturnError(err)
},
dbRows: sqlmock.NewRows(
[]string{
"id",
"role",
"username",
"email",
"hashed_password",
"avatar_image",
"black_phoenix",
"date_of_birth",
"date_of_registration",
},
),
dbErr: errors.New("some error"),
expectedErr: domain.InternalServerError,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
db, mock, dbClose := database.GetMockDBx(t)
defer dbClose()
tc.mockBehavior(mock, tc.userID, tc.dbRows.AddRow(
tc.ud.ID,
tc.ud.Role,
tc.ud.Username,
tc.ud.Email,
tc.ud.HashedPassword,
tc.ud.AvatarImage,
tc.ud.BlackPhoenix,
tc.ud.DateOfBirth,
tc.ud.DateOfRegistration,
), tc.dbErr)
repo := &userRepository{db: db}
u, err := repo.GetByID(tc.userID)
assert.Equal(t, tc.expectedUser, u)
assert.ErrorIs(t, err, tc.expectedErr)
assert.NoError(t, mock.ExpectationsWereMet())
})
}
}
func TestUserRepository_GetAll(t *testing.T) {
type mockBehavior func(mock sqlmock.Sqlmock, username string, rows *sqlmock.Rows, err error)
type logBehavior func(l *mock_logger.MockLog, err error)
testTable := []struct {
name string
username string
mockBehavior mockBehavior
logBehavior logBehavior
dbRows *sqlmock.Rows
dbErr error
ud domain.User
expectedUsers []domain.User
expectedErr error
}{
{
name: "ok",
mockBehavior: func(mock sqlmock.Sqlmock, username string, rows *sqlmock.Rows, err error) {
mock.ExpectQuery(fmt.Sprintf(`SELECT \* FROM users WHERE username ILIKE \$1 AND role != %d`, domain.AdminUser)).WithArgs(username).WillReturnRows(rows)
},
logBehavior: func(l *mock_logger.MockLog, err error) {},
dbRows: sqlmock.NewRows(
[]string{
"id",
"role",
"username",
"email",
"hashed_password",
"avatar_image",
"black_phoenix",
"date_of_birth",
"date_of_registration",
},
),
ud: domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "",
BlackPhoenix: false,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
expectedUsers: []domain.User{
{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "",
BlackPhoenix: false,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
},
},
{
name: "any_error",
mockBehavior: func(mock sqlmock.Sqlmock, username string, rows *sqlmock.Rows, err error) {
mock.ExpectQuery(fmt.Sprintf(`SELECT \* FROM users WHERE username ILIKE \$1 AND role != %d`, domain.AdminUser)).WithArgs(username).WillReturnError(err)
},
logBehavior: func(l *mock_logger.MockLog, err error) {
l.EXPECT().Errorf("getting users: %s", err.Error())
},
dbRows: sqlmock.NewRows(
[]string{
"id",
"role",
"username",
"email",
"hashed_password",
"avatar_image",
"black_phoenix",
"date_of_birth",
"date_of_registration",
},
),
dbErr: errors.New("some error"),
expectedErr: domain.InternalServerError,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
db, mock, dbClose := database.GetMockDBx(t)
defer dbClose()
log := mock_logger.NewMockLog(c)
username := fmt.Sprint("%", tc.username, "%")
tc.mockBehavior(mock, username, tc.dbRows.AddRow(
tc.ud.ID,
tc.ud.Role,
tc.ud.Username,
tc.ud.Email,
tc.ud.HashedPassword,
tc.ud.AvatarImage,
tc.ud.BlackPhoenix,
tc.ud.DateOfBirth,
tc.ud.DateOfRegistration,
), tc.dbErr)
tc.logBehavior(log, tc.dbErr)
repo := &userRepository{db: db, l: log}
users, err := repo.GetAll(tc.username)
assert.Equal(t, tc.expectedUsers, users)
assert.ErrorIs(t, err, tc.expectedErr)
assert.NoError(t, mock.ExpectationsWereMet())
})
}
}
func TestUserRepository_FindOne(t *testing.T) {
type mockBehavior func(mock sqlmock.Sqlmock, username, email string, rows *sqlmock.Rows, err error)
dbRows := sqlmock.NewRows([]string{
"id",
"role",
"username",
"email",
"hashed_password",
"avatar_image",
"black_phoenix",
"date_of_birth",
"date_of_registration",
})
okUd := domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "",
BlackPhoenix: false,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
}
okExpectedUser := domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "",
BlackPhoenix: false,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
}
testTable := []struct {
name string
username string
email string
mockBehavior mockBehavior
dbRows *sqlmock.Rows
dbErr error
ud domain.User
expectedUser domain.User
expectedErr error
}{
{
name: "ok_1",
username: "urec",
email: "mail@mail.ru",
mockBehavior: func(mock sqlmock.Sqlmock, username, email string, rows *sqlmock.Rows, err error) {
mock.ExpectQuery(`SELECT \* FROM users WHERE username = \$1 AND email = \$2 LIMIT 1`).WithArgs(username, email).WillReturnRows(rows)
},
dbRows: dbRows,
ud: okUd,
expectedUser: okExpectedUser,
},
{
name: "ok_2",
username: "urec",
mockBehavior: func(mock sqlmock.Sqlmock, username, email string, rows *sqlmock.Rows, err error) {
mock.ExpectQuery(`SELECT \* FROM users WHERE username = \$1 LIMIT 1`).WithArgs(username).WillReturnRows(rows)
},
dbRows: dbRows,
ud: okUd,
expectedUser: okExpectedUser,
},
{
name: "ok_3",
email: "mail@mail.ru",
mockBehavior: func(mock sqlmock.Sqlmock, username, email string, rows *sqlmock.Rows, err error) {
mock.ExpectQuery(`SELECT \* FROM users WHERE email = \$1 LIMIT 1`).WithArgs(email).WillReturnRows(rows)
},
dbRows: dbRows,
ud: okUd,
expectedUser: okExpectedUser,
},
{
name: "ok_4",
mockBehavior: func(mock sqlmock.Sqlmock, username, email string, rows *sqlmock.Rows, err error) {
mock.ExpectQuery(`SELECT \* FROM users LIMIT 1`).WillReturnRows(rows)
},
dbRows: dbRows,
ud: okUd,
expectedUser: okExpectedUser,
},
{
name: "any_error",
mockBehavior: func(mock sqlmock.Sqlmock, username, email string, rows *sqlmock.Rows, err error) {
mock.ExpectQuery(`SELECT \* FROM users LIMIT 1`).WillReturnError(err)
},
dbRows: dbRows,
dbErr: errors.New("some error"),
expectedErr: domain.InternalServerError,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
db, mock, dbClose := database.GetMockDBx(t)
defer dbClose()
tc.mockBehavior(mock, tc.username, tc.email, tc.dbRows.AddRow(
tc.ud.ID,
tc.ud.Role,
tc.ud.Username,
tc.ud.Email,
tc.ud.HashedPassword,
tc.ud.AvatarImage,
tc.ud.BlackPhoenix,
tc.ud.DateOfBirth,
tc.ud.DateOfRegistration,
), tc.dbErr)
repo := &userRepository{db: db}
u, err := repo.FindOne(tc.username, tc.email)
assert.Equal(t, tc.expectedUser, u)
assert.ErrorIs(t, err, tc.expectedErr)
assert.NoError(t, mock.ExpectationsWereMet())
})
}
}
func TestUserRepository_Register(t *testing.T) {
type mockParams struct {
mock sqlmock.Sqlmock
email, hashedPassword, username, avatarImage string
dateOfBirth time.Time
userID int
rows *sqlmock.Rows
err error
}
type mockBehavior func(p mockParams)
type logBehavior func(log *mock_logger.MockLog, err error)
dbRows := sqlmock.NewRows([]string{
"id",
"role",
"username",
"email",
"hashed_password",
"avatar_image",
"black_phoenix",
"date_of_birth",
"date_of_registration",
})
testTable := []struct {
name string
mockParams mockParams
mockBehavior mockBehavior
logBehavior logBehavior
ud domain.User
expectedUser domain.User
expectedErr error
}{
{
name: "ok",
mockParams: mockParams{
rows: dbRows,
},
mockBehavior: func(p mockParams) {
p.mock.ExpectBegin()
p.mock.ExpectQuery(
`INSERT INTO users \(email, hashed_password, username, date_of_birth\) VALUES \(\$1, \$2, \$3, \$4\) RETURNING \*`,
).WithArgs(p.email, p.hashedPassword, p.username, p.dateOfBirth).WillReturnRows(p.rows)
p.mock.ExpectExec(
`INSERT INTO user_avatar \(user_id, avatar_image\) VALUES \(\$1, \$2\)`,
).WithArgs(p.userID, p.avatarImage).WillReturnResult(sqlmock.NewResult(1, 1))
p.mock.ExpectCommit()
},
logBehavior: func(log *mock_logger.MockLog, err error) {},
ud: domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "image",
BlackPhoenix: false,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
expectedUser: domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "image",
BlackPhoenix: false,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
},
{
name: "user_already_exists",
mockParams: mockParams{
rows: dbRows,
err: database.IntegrityError,
},
mockBehavior: func(p mockParams) {
p.mock.ExpectBegin()
p.mock.ExpectQuery(
`INSERT INTO users \(email, hashed_password, username, date_of_birth\) VALUES \(\$1, \$2, \$3, \$4\) RETURNING \*`,
).WithArgs(p.email, p.hashedPassword, p.username, p.dateOfBirth).WillReturnError(p.err)
p.mock.ExpectRollback()
},
logBehavior: func(log *mock_logger.MockLog, err error) {},
ud: domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "image",
BlackPhoenix: false,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
expectedErr: domain.UserAlreadyExistsError,
},
{
name: "begin_error",
mockParams: mockParams{
rows: dbRows,
err: errors.New("some error"),
},
mockBehavior: func(p mockParams) {
p.mock.ExpectBegin().WillReturnError(p.err)
},
logBehavior: func(log *mock_logger.MockLog, err error) {
log.EXPECT().Errorf("user registration: tx begin: %s", err.Error())
},
ud: domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "image",
BlackPhoenix: false,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
expectedErr: domain.InternalServerError,
},
{
name: "get_error",
mockParams: mockParams{
rows: dbRows,
err: errors.New("some error"),
},
mockBehavior: func(p mockParams) {
p.mock.ExpectBegin()
p.mock.ExpectQuery(
`INSERT INTO users \(email, hashed_password, username, date_of_birth\) VALUES \(\$1, \$2, \$3, \$4\) RETURNING \*`,
).WithArgs(p.email, p.hashedPassword, p.username, p.dateOfBirth).WillReturnError(p.err)
p.mock.ExpectRollback()
},
logBehavior: func(log *mock_logger.MockLog, err error) {
log.EXPECT().Errorf("user registration: %s", err.Error())
},
ud: domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "image",
BlackPhoenix: false,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
expectedErr: domain.InternalServerError,
},
{
name: "exec_error",
mockParams: mockParams{
rows: dbRows,
err: errors.New("some error"),
},
mockBehavior: func(p mockParams) {
p.mock.ExpectBegin()
p.mock.ExpectQuery(
`INSERT INTO users \(email, hashed_password, username, date_of_birth\) VALUES \(\$1, \$2, \$3, \$4\) RETURNING \*`,
).WithArgs(p.email, p.hashedPassword, p.username, p.dateOfBirth).WillReturnRows(p.rows)
p.mock.ExpectExec(
`INSERT INTO user_avatar \(user_id, avatar_image\) VALUES \(\$1, \$2\)`,
).WithArgs(p.userID, p.avatarImage).WillReturnError(p.err)
p.mock.ExpectRollback()
},
logBehavior: func(log *mock_logger.MockLog, err error) {
log.EXPECT().Errorf("user registration: %s", err.Error())
},
ud: domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "image",
BlackPhoenix: false,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
expectedErr: domain.InternalServerError,
},
{
name: "commit_error",
mockParams: mockParams{
rows: dbRows,
err: errors.New("some error"),
},
mockBehavior: func(p mockParams) {
p.mock.ExpectBegin()
p.mock.ExpectQuery(
`INSERT INTO users \(email, hashed_password, username, date_of_birth\) VALUES \(\$1, \$2, \$3, \$4\) RETURNING \*`,
).WithArgs(p.email, p.hashedPassword, p.username, p.dateOfBirth).WillReturnRows(p.rows)
p.mock.ExpectExec(
`INSERT INTO user_avatar \(user_id, avatar_image\) VALUES \(\$1, \$2\)`,
).WithArgs(p.userID, p.avatarImage).WillReturnResult(sqlmock.NewResult(1, 1))
p.mock.ExpectCommit().WillReturnError(p.err)
// p.mock.ExpectRollback().WillReturnError(sql.ErrTxDone) // с ним падает тест тк вызов роллбека не доходит
// до драйвера и он его не регистрирует
},
logBehavior: func(log *mock_logger.MockLog, err error) {
log.EXPECT().Errorf("user registration: %s", err.Error())
},
ud: domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "image",
BlackPhoenix: false,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
expectedErr: domain.InternalServerError,
},
{
name: "rollback_error",
mockParams: mockParams{
rows: dbRows,
err: database.IntegrityError,
},
mockBehavior: func(p mockParams) {
p.mock.ExpectBegin()
p.mock.ExpectQuery(
`INSERT INTO users \(email, hashed_password, username, date_of_birth\) VALUES \(\$1, \$2, \$3, \$4\) RETURNING \*`,
).WithArgs(p.email, p.hashedPassword, p.username, p.dateOfBirth).WillReturnError(p.err)
p.mock.ExpectRollback().WillReturnError(p.err)
},
logBehavior: func(log *mock_logger.MockLog, err error) {
log.EXPECT().Errorf("user registration: tx rollback: %s", err.Error())
},
ud: domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "image",
BlackPhoenix: false,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
expectedErr: domain.UserAlreadyExistsError,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
db, mock, dbClose := database.GetMockDBx(t)
defer dbClose()
log := mock_logger.NewMockLog(c)
tc.mockParams.mock = mock
tc.mockParams.rows.AddRow(
tc.ud.ID,
tc.ud.Role,
tc.ud.Username,
tc.ud.Email,
tc.ud.HashedPassword,
tc.ud.AvatarImage,
tc.ud.BlackPhoenix,
tc.ud.DateOfBirth,
tc.ud.DateOfRegistration,
)
tc.mockParams.email = tc.ud.Email
tc.mockParams.hashedPassword = tc.ud.HashedPassword
tc.mockParams.username = tc.ud.Username
tc.mockParams.avatarImage = tc.ud.AvatarImage
tc.mockParams.dateOfBirth = tc.ud.DateOfBirth.Time
tc.mockParams.userID = tc.ud.ID
tc.mockBehavior(tc.mockParams)
tc.logBehavior(log, tc.mockParams.err)
repo := &userRepository{db: db, l: log}
u, err := repo.Register(tc.mockParams.email, tc.mockParams.hashedPassword, tc.mockParams.username, tc.mockParams.dateOfBirth)
assert.Equal(t, tc.expectedUser, u)
assert.ErrorIs(t, err, tc.expectedErr)
assert.NoError(t, tc.mockParams.mock.ExpectationsWereMet())
})
}
}

95
internal/service/auth.go Normal file
View file

@ -0,0 +1,95 @@
package service
import (
"fmt"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt"
"net/http"
"strconv"
"strings"
"time"
"git.urec56.ru/urec/chat_back_go/config"
"git.urec56.ru/urec/chat_back_go/internal/domain"
"git.urec56.ru/urec/chat_back_go/internal/logger"
)
const maxBcryptPasswordLen = 72
type authService struct {
cfg config.JWT
parser Parser
l logger.Log
}
func newAuthService(cfg config.JWT, l logger.Log) *authService {
return &authService{cfg: cfg, parser: jwt.NewParser(), l: l}
}
func (s *authService) keyFunc(*jwt.Token) (interface{}, error) {
return s.cfg.SecretKey, nil
}
func (s *authService) ExtractAuthToken(r *http.Request) (string, error) {
token := r.Header.Get("Authorization")
tokenData := strings.Split(token, " ")
if len(tokenData) != 2 || strings.ToLower(tokenData[0]) != "bearer" {
return "", domain.TokenError
}
return tokenData[1], nil
}
func (s *authService) DecodeAuthToken(token string) (int, error) {
val, err := s.parser.Parse(token, s.keyFunc)
if err != nil {
return 0, domain.TokenError
}
sub, err := val.Claims.GetSubject()
if err != nil {
return 0, domain.TokenError
}
userID, err := strconv.Atoi(sub)
if err != nil {
return 0, domain.TokenError
}
return userID, nil
}
func (s *authService) EncodeAuthToken(userID int) (string, error) {
claims := jwt.MapClaims{
"sub": fmt.Sprintf("%d", userID),
"iat": time.Now().Unix(),
"exp": time.Now().Add(time.Hour * 24 * 30).Unix(), // срок действия 30 дней
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(s.cfg.SecretKey)
if err != nil {
s.l.Errorf("singing jwt: %v", err)
return "", domain.InternalServerError
}
return tokenString, nil
}
func (s *authService) HashPassword(p string) (string, error) {
hash, err := bcrypt.GenerateFromPassword([]byte(p)[:maxBcryptPasswordLen], bcrypt.DefaultCost)
if err != nil {
s.l.Errorf("error during password hashing: %s", err.Error())
return "", domain.HashingError
}
return string(hash), nil
}
func (s *authService) VerifyHashedPassword(p, hp string) bool {
if err := bcrypt.CompareHashAndPassword([]byte(hp), []byte(p)); err != nil {
return false
}
return true
}

View file

@ -0,0 +1,227 @@
package service
import (
"github.com/golang-jwt/jwt/v5"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"net/http"
"testing"
"git.urec56.ru/urec/chat_back_go/config"
"git.urec56.ru/urec/chat_back_go/internal/domain"
"git.urec56.ru/urec/chat_back_go/internal/logger"
mock_logger "git.urec56.ru/urec/chat_back_go/internal/logger/mocks"
mock_service "git.urec56.ru/urec/chat_back_go/internal/service/mocks"
)
func Test_newAuthService(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
cfg := config.JWT{}
log := mock_logger.NewMockLog(c)
serv := newAuthService(cfg, log)
assert.Equal(t, &authService{cfg: cfg, parser: jwt.NewParser(), l: log}, serv)
}
func TestAuthService_keyFunc(t *testing.T) {
testTable := []struct {
name string
token *jwt.Token
cfgSecretKey []byte
expectedKey any
expectedErr error
}{
{
name: "ok_1",
cfgSecretKey: []byte("secretKey"),
expectedKey: []byte("secretKey"),
},
{
name: "ok_2",
cfgSecretKey: []byte("another_secretKey"),
expectedKey: []byte("another_secretKey"),
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
serv := &authService{cfg: config.JWT{SecretKey: tc.cfgSecretKey}}
key, err := serv.keyFunc(tc.token)
assert.Equal(t, tc.expectedKey, key)
assert.Equal(t, tc.expectedErr, err)
})
}
}
func TestAuthService_ExtractAuthToken(t *testing.T) {
testTable := []struct {
name string
req *http.Request
reqHeader string
reqToken string
isError bool
expectedToken string
}{
{
name: "ok_1",
req: &http.Request{Header: http.Header{}},
reqHeader: "Authorization",
reqToken: "Bearer 123",
expectedToken: "123",
},
{
name: "ok_2",
req: &http.Request{Header: http.Header{}},
reqHeader: "authorization",
reqToken: "bearer 123",
expectedToken: "123",
},
{
name: "incorrect_header",
req: &http.Request{Header: http.Header{}},
reqToken: "Bearer 123",
isError: true,
},
{
name: "incorrect_token_prefix",
req: &http.Request{Header: http.Header{}},
reqHeader: "authorization",
reqToken: "Beaer 123",
isError: true,
},
{
name: "incorrect_token",
req: &http.Request{Header: http.Header{}},
reqHeader: "authorization",
reqToken: "Bearer",
isError: true,
},
{
name: "incorrect_token_length",
req: &http.Request{Header: http.Header{}},
reqHeader: "authorization",
reqToken: "123",
isError: true,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
tc.req.Header.Set(tc.reqHeader, tc.reqToken)
auth := newAuthService(config.JWT{}, &logger.Logger{})
token, err := auth.ExtractAuthToken(tc.req)
assert.Equal(t, tc.expectedToken, token)
assert.Equal(t, tc.isError, err != nil)
})
}
}
func TestAuthService_DecodeAuthToken(t *testing.T) {
type parserBehavior func(p *mock_service.MockParser, token string, keyFunc jwt.Keyfunc, jwtToken *jwt.Token, err error)
type jwtTokenConstructor func(claims Claims) *jwt.Token
type claimsBehavior func(c *mock_service.MockClaims, subject string, err error)
testTable := []struct {
name string
parserBehavior parserBehavior
claimsBehavior claimsBehavior
jwtTokenConstructor jwtTokenConstructor
parserToken string
parserErr error
claimsUserID string
claimsErr error
secretKey []byte
keyFuncErr error
expectedUserID int
expectedErr error
}{
{
name: "ok",
parserBehavior: func(p *mock_service.MockParser, token string, keyFunc jwt.Keyfunc, jwtToken *jwt.Token, err error) {
p.EXPECT().Parse(token, gomock.Any()).Return(jwtToken, err)
},
claimsBehavior: func(c *mock_service.MockClaims, subject string, err error) {
c.EXPECT().GetSubject().Return(subject, err)
},
jwtTokenConstructor: func(claims Claims) *jwt.Token {
return &jwt.Token{Claims: claims}
},
parserToken: "token",
claimsUserID: "1",
secretKey: []byte("secret_key"),
expectedUserID: 1,
},
{
name: "parser_error",
parserBehavior: func(p *mock_service.MockParser, token string, keyFunc jwt.Keyfunc, jwtToken *jwt.Token, err error) {
p.EXPECT().Parse(token, gomock.Any()).Return(jwtToken, err)
},
claimsBehavior: func(c *mock_service.MockClaims, subject string, err error) {},
jwtTokenConstructor: func(claims Claims) *jwt.Token { return &jwt.Token{} },
parserToken: "token",
parserErr: domain.AnyError,
expectedErr: domain.TokenError,
},
{
name: "claims_error",
parserBehavior: func(p *mock_service.MockParser, token string, keyFunc jwt.Keyfunc, jwtToken *jwt.Token, err error) {
p.EXPECT().Parse(token, gomock.Any()).Return(jwtToken, err)
},
claimsBehavior: func(c *mock_service.MockClaims, subject string, err error) {
c.EXPECT().GetSubject().Return(subject, err)
},
jwtTokenConstructor: func(claims Claims) *jwt.Token {
return &jwt.Token{Claims: claims}
},
parserToken: "token",
secretKey: []byte("secret_key"),
claimsErr: domain.AnyError,
expectedErr: domain.TokenError,
},
{
name: "incorrect_id",
parserBehavior: func(p *mock_service.MockParser, token string, keyFunc jwt.Keyfunc, jwtToken *jwt.Token, err error) {
p.EXPECT().Parse(token, gomock.Any()).Return(jwtToken, err)
},
claimsBehavior: func(c *mock_service.MockClaims, subject string, err error) {
c.EXPECT().GetSubject().Return(subject, err)
},
jwtTokenConstructor: func(claims Claims) *jwt.Token {
return &jwt.Token{Claims: claims}
},
parserToken: "token",
claimsUserID: "not_number",
secretKey: []byte("secret_key"),
expectedErr: domain.TokenError,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
parser := mock_service.NewMockParser(c)
claims := mock_service.NewMockClaims(c)
jwtToken := tc.jwtTokenConstructor(claims)
tc.claimsBehavior(claims, tc.claimsUserID, tc.claimsErr)
serv := &authService{cfg: config.JWT{SecretKey: tc.secretKey}, parser: parser}
tc.parserBehavior(parser, tc.parserToken, serv.keyFunc, jwtToken, tc.parserErr)
userID, err := serv.DecodeAuthToken(tc.parserToken)
assert.Equal(t, tc.expectedUserID, userID)
assert.Equal(t, tc.expectedErr, err)
})
}
}

15
internal/service/chat.go Normal file
View file

@ -0,0 +1,15 @@
package service
import (
"git.urec56.ru/urec/chat_back_go/internal/logger"
"git.urec56.ru/urec/chat_back_go/internal/repository"
)
type chatService struct {
repo repository.Chat
l logger.Log
}
func newChatService(repo repository.Chat, l logger.Log) *chatService {
return &chatService{repo: repo, l: l}
}

View file

@ -0,0 +1,22 @@
package service
import (
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"testing"
mock_logger "git.urec56.ru/urec/chat_back_go/internal/logger/mocks"
mock_repository "git.urec56.ru/urec/chat_back_go/internal/repository/mocks"
)
func Test_newChatService(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
repo := mock_repository.NewMockChat(c)
log := mock_logger.NewMockLog(c)
serv := newChatService(repo, log)
assert.Equal(t, &chatService{repo: repo, l: log}, serv)
}

View file

@ -0,0 +1,612 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: service.go
//
// Generated by this command:
//
// mockgen -source=service.go -destination=mocks/mock.go
//
// Package mock_service is a generated GoMock package.
package mock_service
import (
http "net/http"
reflect "reflect"
domain "git.urec56.ru/urec/chat_back_go/internal/domain"
jwt "github.com/golang-jwt/jwt/v5"
gomock "go.uber.org/mock/gomock"
)
// MockUser is a mock of User interface.
type MockUser struct {
ctrl *gomock.Controller
recorder *MockUserMockRecorder
isgomock struct{}
}
// MockUserMockRecorder is the mock recorder for MockUser.
type MockUserMockRecorder struct {
mock *MockUser
}
// NewMockUser creates a new mock instance.
func NewMockUser(ctrl *gomock.Controller) *MockUser {
mock := &MockUser{ctrl: ctrl}
mock.recorder = &MockUserMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockUser) EXPECT() *MockUserMockRecorder {
return m.recorder
}
// FindOne mocks base method.
func (m *MockUser) FindOne(username, email string) (domain.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOne", username, email)
ret0, _ := ret[0].(domain.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOne indicates an expected call of FindOne.
func (mr *MockUserMockRecorder) FindOne(username, email any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockUser)(nil).FindOne), username, email)
}
// Get mocks base method.
func (m *MockUser) Get(userID int) (domain.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", userID)
ret0, _ := ret[0].(domain.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Get indicates an expected call of Get.
func (mr *MockUserMockRecorder) Get(userID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockUser)(nil).Get), userID)
}
// GetAll mocks base method.
func (m *MockUser) GetAll(username string) ([]domain.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAll", username)
ret0, _ := ret[0].([]domain.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAll indicates an expected call of GetAll.
func (mr *MockUserMockRecorder) GetAll(username any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAll", reflect.TypeOf((*MockUser)(nil).GetAll), username)
}
// GetVerificated mocks base method.
func (m *MockUser) GetVerificated(userID int) (domain.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetVerificated", userID)
ret0, _ := ret[0].(domain.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetVerificated indicates an expected call of GetVerificated.
func (mr *MockUserMockRecorder) GetVerificated(userID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVerificated", reflect.TypeOf((*MockUser)(nil).GetVerificated), userID)
}
// Register mocks base method.
func (m *MockUser) Register(userData domain.UserRegister) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Register", userData)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Register indicates an expected call of Register.
func (mr *MockUserMockRecorder) Register(userData any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockUser)(nil).Register), userData)
}
// MockAuth is a mock of Auth interface.
type MockAuth struct {
ctrl *gomock.Controller
recorder *MockAuthMockRecorder
isgomock struct{}
}
// MockAuthMockRecorder is the mock recorder for MockAuth.
type MockAuthMockRecorder struct {
mock *MockAuth
}
// NewMockAuth creates a new mock instance.
func NewMockAuth(ctrl *gomock.Controller) *MockAuth {
mock := &MockAuth{ctrl: ctrl}
mock.recorder = &MockAuthMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockAuth) EXPECT() *MockAuthMockRecorder {
return m.recorder
}
// DecodeAuthToken mocks base method.
func (m *MockAuth) DecodeAuthToken(token string) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecodeAuthToken", token)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DecodeAuthToken indicates an expected call of DecodeAuthToken.
func (mr *MockAuthMockRecorder) DecodeAuthToken(token any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeAuthToken", reflect.TypeOf((*MockAuth)(nil).DecodeAuthToken), token)
}
// EncodeAuthToken mocks base method.
func (m *MockAuth) EncodeAuthToken(userID int) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EncodeAuthToken", userID)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// EncodeAuthToken indicates an expected call of EncodeAuthToken.
func (mr *MockAuthMockRecorder) EncodeAuthToken(userID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EncodeAuthToken", reflect.TypeOf((*MockAuth)(nil).EncodeAuthToken), userID)
}
// ExtractAuthToken mocks base method.
func (m *MockAuth) ExtractAuthToken(r *http.Request) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ExtractAuthToken", r)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ExtractAuthToken indicates an expected call of ExtractAuthToken.
func (mr *MockAuthMockRecorder) ExtractAuthToken(r any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtractAuthToken", reflect.TypeOf((*MockAuth)(nil).ExtractAuthToken), r)
}
// HashPassword mocks base method.
func (m *MockAuth) HashPassword(p string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HashPassword", p)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// HashPassword indicates an expected call of HashPassword.
func (mr *MockAuthMockRecorder) HashPassword(p any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HashPassword", reflect.TypeOf((*MockAuth)(nil).HashPassword), p)
}
// VerifyHashedPassword mocks base method.
func (m *MockAuth) VerifyHashedPassword(p, hp string) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "VerifyHashedPassword", p, hp)
ret0, _ := ret[0].(bool)
return ret0
}
// VerifyHashedPassword indicates an expected call of VerifyHashedPassword.
func (mr *MockAuthMockRecorder) VerifyHashedPassword(p, hp any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyHashedPassword", reflect.TypeOf((*MockAuth)(nil).VerifyHashedPassword), p, hp)
}
// MockChat is a mock of Chat interface.
type MockChat struct {
ctrl *gomock.Controller
recorder *MockChatMockRecorder
isgomock struct{}
}
// MockChatMockRecorder is the mock recorder for MockChat.
type MockChatMockRecorder struct {
mock *MockChat
}
// NewMockChat creates a new mock instance.
func NewMockChat(ctrl *gomock.Controller) *MockChat {
mock := &MockChat{ctrl: ctrl}
mock.recorder = &MockChatMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockChat) EXPECT() *MockChatMockRecorder {
return m.recorder
}
// MockParser is a mock of Parser interface.
type MockParser struct {
ctrl *gomock.Controller
recorder *MockParserMockRecorder
isgomock struct{}
}
// MockParserMockRecorder is the mock recorder for MockParser.
type MockParserMockRecorder struct {
mock *MockParser
}
// NewMockParser creates a new mock instance.
func NewMockParser(ctrl *gomock.Controller) *MockParser {
mock := &MockParser{ctrl: ctrl}
mock.recorder = &MockParserMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockParser) EXPECT() *MockParserMockRecorder {
return m.recorder
}
// DecodeSegment mocks base method.
func (m *MockParser) DecodeSegment(seg string) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecodeSegment", seg)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DecodeSegment indicates an expected call of DecodeSegment.
func (mr *MockParserMockRecorder) DecodeSegment(seg any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeSegment", reflect.TypeOf((*MockParser)(nil).DecodeSegment), seg)
}
// Parse mocks base method.
func (m *MockParser) Parse(tokenString string, keyFunc jwt.Keyfunc) (*jwt.Token, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Parse", tokenString, keyFunc)
ret0, _ := ret[0].(*jwt.Token)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Parse indicates an expected call of Parse.
func (mr *MockParserMockRecorder) Parse(tokenString, keyFunc any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Parse", reflect.TypeOf((*MockParser)(nil).Parse), tokenString, keyFunc)
}
// ParseUnverified mocks base method.
func (m *MockParser) ParseUnverified(tokenString string, claims jwt.Claims) (*jwt.Token, []string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ParseUnverified", tokenString, claims)
ret0, _ := ret[0].(*jwt.Token)
ret1, _ := ret[1].([]string)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// ParseUnverified indicates an expected call of ParseUnverified.
func (mr *MockParserMockRecorder) ParseUnverified(tokenString, claims any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParseUnverified", reflect.TypeOf((*MockParser)(nil).ParseUnverified), tokenString, claims)
}
// ParseWithClaims mocks base method.
func (m *MockParser) ParseWithClaims(tokenString string, claims jwt.Claims, keyFunc jwt.Keyfunc) (*jwt.Token, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ParseWithClaims", tokenString, claims, keyFunc)
ret0, _ := ret[0].(*jwt.Token)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ParseWithClaims indicates an expected call of ParseWithClaims.
func (mr *MockParserMockRecorder) ParseWithClaims(tokenString, claims, keyFunc any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParseWithClaims", reflect.TypeOf((*MockParser)(nil).ParseWithClaims), tokenString, claims, keyFunc)
}
// MockClaims is a mock of Claims interface.
type MockClaims struct {
ctrl *gomock.Controller
recorder *MockClaimsMockRecorder
isgomock struct{}
}
// MockClaimsMockRecorder is the mock recorder for MockClaims.
type MockClaimsMockRecorder struct {
mock *MockClaims
}
// NewMockClaims creates a new mock instance.
func NewMockClaims(ctrl *gomock.Controller) *MockClaims {
mock := &MockClaims{ctrl: ctrl}
mock.recorder = &MockClaimsMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockClaims) EXPECT() *MockClaimsMockRecorder {
return m.recorder
}
// GetAudience mocks base method.
func (m *MockClaims) GetAudience() (jwt.ClaimStrings, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAudience")
ret0, _ := ret[0].(jwt.ClaimStrings)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAudience indicates an expected call of GetAudience.
func (mr *MockClaimsMockRecorder) GetAudience() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAudience", reflect.TypeOf((*MockClaims)(nil).GetAudience))
}
// GetExpirationTime mocks base method.
func (m *MockClaims) GetExpirationTime() (*jwt.NumericDate, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetExpirationTime")
ret0, _ := ret[0].(*jwt.NumericDate)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetExpirationTime indicates an expected call of GetExpirationTime.
func (mr *MockClaimsMockRecorder) GetExpirationTime() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExpirationTime", reflect.TypeOf((*MockClaims)(nil).GetExpirationTime))
}
// GetIssuedAt mocks base method.
func (m *MockClaims) GetIssuedAt() (*jwt.NumericDate, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetIssuedAt")
ret0, _ := ret[0].(*jwt.NumericDate)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetIssuedAt indicates an expected call of GetIssuedAt.
func (mr *MockClaimsMockRecorder) GetIssuedAt() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIssuedAt", reflect.TypeOf((*MockClaims)(nil).GetIssuedAt))
}
// GetIssuer mocks base method.
func (m *MockClaims) GetIssuer() (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetIssuer")
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetIssuer indicates an expected call of GetIssuer.
func (mr *MockClaimsMockRecorder) GetIssuer() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIssuer", reflect.TypeOf((*MockClaims)(nil).GetIssuer))
}
// GetNotBefore mocks base method.
func (m *MockClaims) GetNotBefore() (*jwt.NumericDate, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetNotBefore")
ret0, _ := ret[0].(*jwt.NumericDate)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetNotBefore indicates an expected call of GetNotBefore.
func (mr *MockClaimsMockRecorder) GetNotBefore() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotBefore", reflect.TypeOf((*MockClaims)(nil).GetNotBefore))
}
// GetSubject mocks base method.
func (m *MockClaims) GetSubject() (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSubject")
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetSubject indicates an expected call of GetSubject.
func (mr *MockClaimsMockRecorder) GetSubject() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSubject", reflect.TypeOf((*MockClaims)(nil).GetSubject))
}
// MockServ is a mock of Serv interface.
type MockServ struct {
ctrl *gomock.Controller
recorder *MockServMockRecorder
isgomock struct{}
}
// MockServMockRecorder is the mock recorder for MockServ.
type MockServMockRecorder struct {
mock *MockServ
}
// NewMockServ creates a new mock instance.
func NewMockServ(ctrl *gomock.Controller) *MockServ {
mock := &MockServ{ctrl: ctrl}
mock.recorder = &MockServMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockServ) EXPECT() *MockServMockRecorder {
return m.recorder
}
// DecodeAuthToken mocks base method.
func (m *MockServ) DecodeAuthToken(token string) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecodeAuthToken", token)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DecodeAuthToken indicates an expected call of DecodeAuthToken.
func (mr *MockServMockRecorder) DecodeAuthToken(token any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeAuthToken", reflect.TypeOf((*MockServ)(nil).DecodeAuthToken), token)
}
// EncodeAuthToken mocks base method.
func (m *MockServ) EncodeAuthToken(userID int) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EncodeAuthToken", userID)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// EncodeAuthToken indicates an expected call of EncodeAuthToken.
func (mr *MockServMockRecorder) EncodeAuthToken(userID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EncodeAuthToken", reflect.TypeOf((*MockServ)(nil).EncodeAuthToken), userID)
}
// ExtractAuthToken mocks base method.
func (m *MockServ) ExtractAuthToken(r *http.Request) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ExtractAuthToken", r)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ExtractAuthToken indicates an expected call of ExtractAuthToken.
func (mr *MockServMockRecorder) ExtractAuthToken(r any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtractAuthToken", reflect.TypeOf((*MockServ)(nil).ExtractAuthToken), r)
}
// FindOne mocks base method.
func (m *MockServ) FindOne(username, email string) (domain.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOne", username, email)
ret0, _ := ret[0].(domain.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOne indicates an expected call of FindOne.
func (mr *MockServMockRecorder) FindOne(username, email any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOne", reflect.TypeOf((*MockServ)(nil).FindOne), username, email)
}
// Get mocks base method.
func (m *MockServ) Get(userID int) (domain.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", userID)
ret0, _ := ret[0].(domain.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Get indicates an expected call of Get.
func (mr *MockServMockRecorder) Get(userID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockServ)(nil).Get), userID)
}
// GetAll mocks base method.
func (m *MockServ) GetAll(username string) ([]domain.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAll", username)
ret0, _ := ret[0].([]domain.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAll indicates an expected call of GetAll.
func (mr *MockServMockRecorder) GetAll(username any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAll", reflect.TypeOf((*MockServ)(nil).GetAll), username)
}
// GetVerificated mocks base method.
func (m *MockServ) GetVerificated(userID int) (domain.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetVerificated", userID)
ret0, _ := ret[0].(domain.User)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetVerificated indicates an expected call of GetVerificated.
func (mr *MockServMockRecorder) GetVerificated(userID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVerificated", reflect.TypeOf((*MockServ)(nil).GetVerificated), userID)
}
// HashPassword mocks base method.
func (m *MockServ) HashPassword(p string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HashPassword", p)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// HashPassword indicates an expected call of HashPassword.
func (mr *MockServMockRecorder) HashPassword(p any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HashPassword", reflect.TypeOf((*MockServ)(nil).HashPassword), p)
}
// Register mocks base method.
func (m *MockServ) Register(userData domain.UserRegister) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Register", userData)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Register indicates an expected call of Register.
func (mr *MockServMockRecorder) Register(userData any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockServ)(nil).Register), userData)
}
// VerifyHashedPassword mocks base method.
func (m *MockServ) VerifyHashedPassword(p, hp string) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "VerifyHashedPassword", p, hp)
ret0, _ := ret[0].(bool)
return ret0
}
// VerifyHashedPassword indicates an expected call of VerifyHashedPassword.
func (mr *MockServMockRecorder) VerifyHashedPassword(p, hp any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "VerifyHashedPassword", reflect.TypeOf((*MockServ)(nil).VerifyHashedPassword), p, hp)
}

View file

@ -0,0 +1,62 @@
package service
import (
"github.com/golang-jwt/jwt/v5"
"net/http"
"git.urec56.ru/urec/chat_back_go/config"
"git.urec56.ru/urec/chat_back_go/internal/domain"
"git.urec56.ru/urec/chat_back_go/internal/logger"
"git.urec56.ru/urec/chat_back_go/internal/repository"
)
//go:generate mockgen -source=service.go -destination=mocks/mock.go
type User interface {
Get(userID int) (domain.User, error)
GetVerificated(userID int) (domain.User, error)
GetAll(username string) ([]domain.User, error)
FindOne(username, email string) (domain.User, error)
Register(userData domain.UserRegister) (string, error)
}
type Auth interface {
ExtractAuthToken(r *http.Request) (string, error)
DecodeAuthToken(token string) (int, error)
EncodeAuthToken(userID int) (string, error)
HashPassword(p string) (string, error)
VerifyHashedPassword(p, hp string) bool
}
type Chat interface {
}
type Parser interface {
Parse(tokenString string, keyFunc jwt.Keyfunc) (*jwt.Token, error)
ParseWithClaims(tokenString string, claims jwt.Claims, keyFunc jwt.Keyfunc) (*jwt.Token, error)
ParseUnverified(tokenString string, claims jwt.Claims) (token *jwt.Token, parts []string, err error)
DecodeSegment(seg string) ([]byte, error)
}
type Claims interface{ jwt.Claims }
type Service struct {
User
Auth
Chat
}
type Serv interface {
User
Auth
Chat
}
func NewService(repo repository.Repo, cfg config.Config, l logger.Log) *Service {
auth := newAuthService(cfg.JWT, l)
return &Service{
User: newUserService(repo, auth, l),
Auth: auth,
Chat: newChatService(repo, l),
}
}

View file

@ -0,0 +1,31 @@
package service
import (
"github.com/golang-jwt/jwt/v5"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"testing"
"git.urec56.ru/urec/chat_back_go/config"
mock_logger "git.urec56.ru/urec/chat_back_go/internal/logger/mocks"
mock_repository "git.urec56.ru/urec/chat_back_go/internal/repository/mocks"
)
func Test_NewService(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
repo := mock_repository.NewMockRepo(c)
log := mock_logger.NewMockLog(c)
cfg := config.Config{}
serv := NewService(repo, cfg, log)
auth := &authService{cfg: cfg.JWT, parser: jwt.NewParser(), l: log}
expectedServ := &Service{
User: &userService{ur: repo, auth: auth, l: log},
Auth: auth,
Chat: &chatService{repo: repo, l: log},
}
assert.Equal(t, expectedServ, serv)
}

69
internal/service/users.go Normal file
View file

@ -0,0 +1,69 @@
package service
import (
"errors"
"git.urec56.ru/urec/chat_back_go/internal/domain"
"git.urec56.ru/urec/chat_back_go/internal/logger"
"git.urec56.ru/urec/chat_back_go/internal/repository"
)
type userService struct {
ur repository.User
auth Auth
l logger.Log
}
func newUserService(user repository.User, auth Auth, l logger.Log) *userService {
return &userService{ur: user, auth: auth, l: l}
}
func (s *userService) Get(userID int) (domain.User, error) {
u, err := s.ur.GetByID(userID)
return u, err
}
func (s *userService) GetVerificated(userID int) (domain.User, error) {
u, err := s.ur.GetByID(userID)
if err != nil {
return u, err
}
if u.Role < domain.VerificatedUser {
return u, domain.UnverifiedUserError
}
return u, err
}
func (s *userService) GetAll(username string) ([]domain.User, error) {
users, err := s.ur.GetAll(username)
return users, err
}
func (s *userService) FindOne(username, email string) (domain.User, error) {
user, err := s.ur.FindOne(username, email)
if err != nil {
return domain.User{}, domain.InternalServerError
}
return user, nil
}
func (s *userService) Register(userData domain.UserRegister) (string, error) {
hashedPassword, err := s.auth.HashPassword(userData.Password)
if err != nil {
return "", domain.InternalServerError
}
user, err := s.ur.Register(userData.Email, hashedPassword, userData.Username, userData.DateOfBirth.Time)
if err != nil {
if errors.Is(err, domain.UserAlreadyExistsError) {
return "", err
}
return "", domain.InternalServerError
}
authToken, err := s.auth.EncodeAuthToken(user.ID)
if err != nil {
return "", domain.InternalServerError
}
return authToken, err
}

View file

@ -0,0 +1,357 @@
package service
import (
"errors"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"testing"
"time"
"git.urec56.ru/urec/chat_back_go/internal/domain"
mock_logger "git.urec56.ru/urec/chat_back_go/internal/logger/mocks"
mock_repository "git.urec56.ru/urec/chat_back_go/internal/repository/mocks"
mock_service "git.urec56.ru/urec/chat_back_go/internal/service/mocks"
)
func Test_newUserService(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
repo := mock_repository.NewMockUser(c)
auth := mock_service.NewMockAuth(c)
log := mock_logger.NewMockLog(c)
serv := newUserService(repo, auth, log)
assert.Equal(t, &userService{ur: repo, auth: auth, l: log}, serv)
}
func TestUserService_Get(t *testing.T) {
type mockBehavior func(r *mock_repository.MockUser, userID int, user domain.User, err error)
testTable := []struct {
name string
mockBehavior mockBehavior
userID int
expectedUser domain.User
repoErr error
expectedErr error
}{
{
name: "ok",
mockBehavior: func(r *mock_repository.MockUser, userID int, user domain.User, err error) {
r.EXPECT().GetByID(userID).Return(user, err)
},
userID: 1,
expectedUser: domain.User{
ID: 1,
},
},
{
name: "user_not_found",
mockBehavior: func(r *mock_repository.MockUser, userID int, user domain.User, err error) {
r.EXPECT().GetByID(userID).Return(user, err)
},
repoErr: domain.UserNotFoundError,
expectedErr: domain.UserNotFoundError,
},
{
name: "internal_error",
mockBehavior: func(r *mock_repository.MockUser, userID int, user domain.User, err error) {
r.EXPECT().GetByID(userID).Return(user, err)
},
repoErr: domain.InternalServerError,
expectedErr: domain.InternalServerError,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
repo := mock_repository.NewMockUser(c)
serv := userService{ur: repo}
tc.mockBehavior(repo, tc.userID, tc.expectedUser, tc.repoErr)
u, err := serv.Get(tc.userID)
assert.Equal(t, tc.expectedUser, u)
assert.ErrorIs(t, err, tc.expectedErr)
})
}
}
func TestUserService_GetVerificated(t *testing.T) {
type mockBehavior func(r *mock_repository.MockUser, userID int, user domain.User, err error)
testTable := []struct {
name string
mockBehavior mockBehavior
userID int
expectedUser domain.User
repoErr error
expectedErr error
}{
{
name: "ok",
mockBehavior: func(r *mock_repository.MockUser, userID int, user domain.User, err error) {
r.EXPECT().GetByID(userID).Return(user, err)
},
userID: 1,
expectedUser: domain.User{
ID: 1,
Role: domain.VerificatedUser,
},
},
{
name: "user_not_found",
mockBehavior: func(r *mock_repository.MockUser, userID int, user domain.User, err error) {
r.EXPECT().GetByID(userID).Return(user, err)
},
userID: 1,
repoErr: domain.UserNotFoundError,
expectedErr: domain.UserNotFoundError,
},
{
name: "user_unverified",
mockBehavior: func(r *mock_repository.MockUser, userID int, user domain.User, err error) {
r.EXPECT().GetByID(userID).Return(user, err)
},
userID: 1,
expectedUser: domain.User{
ID: 1,
Role: domain.UnverifiedUser,
},
expectedErr: domain.UnverifiedUserError,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
repo := mock_repository.NewMockUser(c)
serv := userService{ur: repo}
tc.mockBehavior(repo, tc.userID, tc.expectedUser, tc.repoErr)
u, err := serv.GetVerificated(tc.userID)
assert.Equal(t, tc.expectedUser, u)
assert.ErrorIs(t, err, tc.expectedErr)
})
}
}
func TestUserService_GetAll(t *testing.T) {
type mockBehavior func(r *mock_repository.MockUser, username string, users []domain.User, err error)
testTable := []struct {
name string
mockBehavior mockBehavior
username string
expectedUsers []domain.User
repoErr error
expectedErr error
}{
{
name: "ok",
mockBehavior: func(r *mock_repository.MockUser, username string, users []domain.User, err error) {
r.EXPECT().GetAll(username).Return(users, err)
},
expectedUsers: []domain.User{
{ID: 1},
},
},
{
name: "internal_error",
mockBehavior: func(r *mock_repository.MockUser, username string, users []domain.User, err error) {
r.EXPECT().GetAll(username).Return(users, err)
},
repoErr: domain.InternalServerError,
expectedErr: domain.InternalServerError,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
repo := mock_repository.NewMockUser(c)
serv := userService{ur: repo}
tc.mockBehavior(repo, tc.username, tc.expectedUsers, tc.repoErr)
u, err := serv.GetAll(tc.username)
assert.Equal(t, tc.expectedUsers, u)
assert.ErrorIs(t, err, tc.expectedErr)
})
}
}
func TestUserService_FindOne(t *testing.T) {
type mockBehavior func(r *mock_repository.MockUser, username, email string, user domain.User, err error)
testTable := []struct {
name string
mockBehavior mockBehavior
username string
email string
expectedUser domain.User
repoErr error
expectedErr error
}{
{
name: "ok",
mockBehavior: func(r *mock_repository.MockUser, username, email string, user domain.User, err error) {
r.EXPECT().FindOne(username, email).Return(user, err)
},
expectedUser: domain.User{
ID: 1,
Role: domain.UnverifiedUser,
},
},
{
name: "any_error",
mockBehavior: func(r *mock_repository.MockUser, username, email string, user domain.User, err error) {
r.EXPECT().FindOne(username, email).Return(user, err)
},
repoErr: errors.New("some error"),
expectedErr: domain.InternalServerError,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
repo := mock_repository.NewMockUser(c)
serv := userService{ur: repo}
tc.mockBehavior(repo, tc.username, tc.email, tc.expectedUser, tc.repoErr)
u, err := serv.FindOne(tc.username, tc.email)
assert.Equal(t, tc.expectedUser, u)
assert.ErrorIs(t, err, tc.expectedErr)
})
}
}
func TestUserService_Register(t *testing.T) {
type hashMockBehavior func(s *mock_service.MockAuth, p, hp string, err error)
type repoMockBehavior func(r *mock_repository.MockUser, email, hp, username string, dateOfBirth time.Time, user domain.User, err error)
type encodeMockBehavior func(s *mock_service.MockAuth, userID int, token string, err error)
testTable := []struct {
name string
hashMockBehavior hashMockBehavior
repoMockBehavior repoMockBehavior
encodeMockBehavior encodeMockBehavior
ud domain.UserRegister
hp string
repoUser domain.User
hashErr error
repoErr error
encodeErr error
expectedToken string
expectedErr error
}{
{
name: "ok",
hashMockBehavior: func(s *mock_service.MockAuth, p, hp string, err error) {
s.EXPECT().HashPassword(p).Return(hp, err)
},
repoMockBehavior: func(r *mock_repository.MockUser, email, hp, username string, dateOfBirth time.Time, user domain.User, err error) {
r.EXPECT().Register(email, hp, username, dateOfBirth).Return(user, err)
},
encodeMockBehavior: func(s *mock_service.MockAuth, userID int, token string, err error) {
s.EXPECT().EncodeAuthToken(userID).Return(token, err)
},
ud: domain.UserRegister{
Email: "mail@mail.ru",
Username: "username",
Password: "pass",
},
hp: "hp",
repoUser: domain.User{ID: 1},
expectedToken: "token",
},
{
name: "user_already_exists",
hashMockBehavior: func(s *mock_service.MockAuth, p, hp string, err error) {
s.EXPECT().HashPassword(p).Return(hp, err)
},
repoMockBehavior: func(r *mock_repository.MockUser, email, hp, username string, dateOfBirth time.Time, user domain.User, err error) {
r.EXPECT().Register(email, hp, username, dateOfBirth).Return(user, err)
},
encodeMockBehavior: func(s *mock_service.MockAuth, userID int, token string, err error) {},
repoErr: domain.UserAlreadyExistsError,
expectedErr: domain.UserAlreadyExistsError,
},
{
name: "hash_failed",
hashMockBehavior: func(s *mock_service.MockAuth, p, hp string, err error) {
s.EXPECT().HashPassword(p).Return(hp, err)
},
repoMockBehavior: func(r *mock_repository.MockUser, email, hp, username string, dateOfBirth time.Time, user domain.User, err error) {
},
encodeMockBehavior: func(s *mock_service.MockAuth, userID int, token string, err error) {},
hashErr: errors.New("some error"),
expectedErr: domain.InternalServerError,
},
{
name: "db_error",
hashMockBehavior: func(s *mock_service.MockAuth, p, hp string, err error) {
s.EXPECT().HashPassword(p).Return(hp, err)
},
repoMockBehavior: func(r *mock_repository.MockUser, email, hp, username string, dateOfBirth time.Time, user domain.User, err error) {
r.EXPECT().Register(email, hp, username, dateOfBirth).Return(user, err)
},
encodeMockBehavior: func(s *mock_service.MockAuth, userID int, token string, err error) {},
repoErr: errors.New("some error"),
expectedErr: domain.InternalServerError,
},
{
name: "token_encoding_error",
hashMockBehavior: func(s *mock_service.MockAuth, p, hp string, err error) {
s.EXPECT().HashPassword(p).Return(hp, err)
},
repoMockBehavior: func(r *mock_repository.MockUser, email, hp, username string, dateOfBirth time.Time, user domain.User, err error) {
r.EXPECT().Register(email, hp, username, dateOfBirth).Return(user, err)
},
encodeMockBehavior: func(s *mock_service.MockAuth, userID int, token string, err error) {
s.EXPECT().EncodeAuthToken(userID).Return(token, err)
},
encodeErr: errors.New("some error"),
expectedErr: domain.InternalServerError,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
repo := mock_repository.NewMockUser(c)
auth := mock_service.NewMockAuth(c)
log := mock_logger.NewMockLog(c)
serv := userService{ur: repo, auth: auth, l: log}
tc.hashMockBehavior(auth, tc.ud.Password, tc.hp, tc.hashErr)
tc.repoMockBehavior(repo, tc.ud.Email, tc.hp, tc.ud.Username, tc.ud.DateOfBirth.Time, tc.repoUser, tc.repoErr)
tc.encodeMockBehavior(auth, tc.repoUser.ID, tc.expectedToken, tc.encodeErr)
token, err := serv.Register(tc.ud)
assert.Equal(t, tc.expectedToken, token)
assert.ErrorIs(t, err, tc.expectedErr)
})
}
}

View file

@ -0,0 +1,64 @@
package handler
import (
"fmt"
"net/http"
)
// GetChats args: userID
// Middleware stack: middleware.VerificatedAuth
func (h *Handler) GetChats(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) ChangeChatData(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) GetLastMessage(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) GetSomeMessages(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) GetMessageByID(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) CreateInvitationLink(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) InviteToChat(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) DeleteChat(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) DeleteUserFromChat(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) PinChat(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) UnpinnChat(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) GetPinnedChats(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) GetPinnedMessages(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}

View file

@ -0,0 +1,134 @@
package handler
import (
"net/http/httptest"
"testing"
)
func TestHandler_GetChats(t *testing.T) {
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{}
h.GetChats(w, req)
_ = w.Result()
}
func TestHandler_Create(t *testing.T) {
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{}
h.Create(w, req)
}
func TestHandler_ChangeChatData(t *testing.T) {
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{}
h.ChangeChatData(w, req)
}
func TestHandler_GetLastMessage(t *testing.T) {
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{}
h.GetLastMessage(w, req)
}
func TestHandler_GetSomeMessages(t *testing.T) {
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{}
h.GetSomeMessages(w, req)
}
func TestHandler_GetMessageByID(t *testing.T) {
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{}
h.GetMessageByID(w, req)
}
func TestHandler_CreateInvitationLink(t *testing.T) {
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{}
h.CreateInvitationLink(w, req)
}
func TestHandler_InviteToChat(t *testing.T) {
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{}
h.InviteToChat(w, req)
}
func TestHandler_DeleteChat(t *testing.T) {
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{}
h.DeleteChat(w, req)
}
func TestHandler_DeleteUserFromChat(t *testing.T) {
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{}
h.DeleteUserFromChat(w, req)
}
func TestHandler_PinChat(t *testing.T) {
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{}
h.PinChat(w, req)
}
func TestHandler_UnpinnChat(t *testing.T) {
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{}
h.UnpinnChat(w, req)
}
func TestHandler_GetPinnedChats(t *testing.T) {
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{}
h.GetPinnedChats(w, req)
}
func TestHandler_GetPinnedMessages(t *testing.T) {
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{}
h.GetPinnedMessages(w, req)
}

View file

@ -0,0 +1,15 @@
package handler
import (
"git.urec56.ru/urec/chat_back_go/internal/logger"
"git.urec56.ru/urec/chat_back_go/internal/service"
)
type Handler struct {
serv service.Serv
l logger.Log
}
func NewHandler(serv service.Serv, l logger.Log) *Handler {
return &Handler{serv: serv, l: l}
}

View file

@ -0,0 +1,22 @@
package handler
import (
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"testing"
mock_logger "git.urec56.ru/urec/chat_back_go/internal/logger/mocks"
mock_service "git.urec56.ru/urec/chat_back_go/internal/service/mocks"
)
func Test_NewHandler(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
serv := mock_service.NewMockServ(c)
log := mock_logger.NewMockLog(c)
h := NewHandler(serv, log)
assert.Equal(t, &Handler{serv: serv, l: log}, h)
}

View file

@ -0,0 +1,14 @@
package handler
import (
"fmt"
"net/http"
)
func (h *Handler) UploadAvatar(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) UploadImage(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}

View file

@ -0,0 +1,28 @@
package handler
import (
"net/http/httptest"
"testing"
)
func TestHandler_UploadAvatar(t *testing.T) {
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{}
h.UploadAvatar(w, req)
_ = w.Result()
}
func TestHandler_UploadImage(t *testing.T) {
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{}
h.UploadImage(w, req)
_ = w.Result()
}

View file

@ -0,0 +1,21 @@
package mock_http
import (
"net/http/httptest"
)
type Recorder struct {
*httptest.ResponseRecorder
err error
}
func NewRecorder(err error) *Recorder {
return &Recorder{ResponseRecorder: httptest.NewRecorder(), err: err}
}
func (r *Recorder) Write(buf []byte) (int, error) {
if r.err != nil {
return 0, r.err
}
return r.ResponseRecorder.Write(buf)
}

View file

@ -0,0 +1,144 @@
package handler
import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"net/http"
"git.urec56.ru/urec/chat_back_go/internal/domain"
)
// GetUsers args: query: optional(username)
func (h *Handler) GetUsers(w http.ResponseWriter, r *http.Request) {
v := r.URL.Query()
username := v.Get("username")
users, err := h.serv.GetAll(username)
if err != nil {
h.l.Infof("[%s] getting users: %s", r.URL.Path, err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
j, _ := json.Marshal(map[string][]domain.User{"users": users}) // no error because []domain.User can`t cause any error here
if _, err = w.Write(j); err != nil {
h.l.Errorf("[%s] writing response: %s", r.URL.Path, err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
}
// CheckExisting args: body: optional(username), optional(email)
func (h *Handler) CheckExisting(w http.ResponseWriter, r *http.Request) {
var userFilter domain.UserFilter
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&userFilter); err != nil {
w.WriteHeader(http.StatusUnprocessableEntity)
return
}
if err := domain.V.Struct(userFilter); err != nil {
h.l.Infof("[%s] validation: %s", r.URL.Path, err.Error())
w.WriteHeader(http.StatusUnprocessableEntity)
return
}
u, err := h.serv.FindOne(userFilter.Username, userFilter.Email)
if err != nil {
h.l.Infof("[%s] serv.FindOne error: %s", r.URL.Path, err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
if u.ID != 0 { // zero value
w.WriteHeader(http.StatusConflict)
}
}
// CheckExistingPassword args: body: password
func (h *Handler) CheckExistingPassword(w http.ResponseWriter, _ *http.Request) {
randInt := rand.Intn(10)
if randInt > 5 {
w.WriteHeader(http.StatusConflict)
}
}
func (h *Handler) Register(w http.ResponseWriter, r *http.Request) {
var userData domain.UserRegister
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&userData); err != nil {
w.WriteHeader(http.StatusUnprocessableEntity)
return
}
if err := domain.V.Struct(userData); err != nil {
h.l.Infof("[%s] validation: %s", r.URL.Path, err.Error())
w.WriteHeader(http.StatusUnprocessableEntity)
return
}
token, err := h.serv.Register(userData)
if err != nil {
if errors.Is(err, domain.UserAlreadyExistsError) {
w.WriteHeader(http.StatusConflict)
} else {
h.l.Infof("[%s] serv.Register: %s", r.URL.Path, err.Error())
w.WriteHeader(http.StatusInternalServerError)
}
return
}
resp, _ := json.Marshal(map[string]string{"authorization": fmt.Sprintf("Bearer %s", token)}) // no error here
w.WriteHeader(http.StatusCreated)
if _, err = w.Write(resp); err != nil {
h.l.Errorf("[%s] writing response: %s", r.URL.Path, err.Error())
return
}
}
func (h *Handler) ResendEmailVerification(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) EmailVerification(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
user, ok := r.Context().Value("user").(domain.User)
if !ok {
h.l.Errorf("[%s] extracting user from ctx", r.URL.Path)
w.WriteHeader(http.StatusInternalServerError)
return
}
j, _ := json.Marshal(user) // no error because domain.User can`t cause any error here
if _, err := w.Write(j); err != nil {
h.l.Errorf("[%s] writing response: %s", r.URL.Path, err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
}
func (h *Handler) GetAvatarsHistory(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) SendConfirmationCode(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}
func (h *Handler) ChangeUserData(w http.ResponseWriter, r *http.Request) {
fmt.Printf("")
}

View file

@ -0,0 +1,639 @@
package handler
import (
"bytes"
"context"
"encoding/json"
"errors"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
"git.urec56.ru/urec/chat_back_go/internal/domain"
mock_logger "git.urec56.ru/urec/chat_back_go/internal/logger/mocks"
mock_service "git.urec56.ru/urec/chat_back_go/internal/service/mocks"
mock_http "git.urec56.ru/urec/chat_back_go/internal/transport/rest/handler/mocks"
)
func TestHandler_GetUsers(t *testing.T) {
type servBehavior func(s *mock_service.MockServ, username string, users []domain.User, err error)
type logBehavior func(l *mock_logger.MockLog, path string, err error)
testTable := []struct {
name string
username string
servBehavior servBehavior
servUsers []domain.User
servErr error
logBehavior logBehavior
logErr error
writeErr error
expectedUsers map[string][]domain.User
expectedStatusCode int
}{
{
name: "ok_1",
username: "urec",
servBehavior: func(s *mock_service.MockServ, username string, users []domain.User, err error) {
s.EXPECT().GetAll(username).Return(users, err)
},
servUsers: []domain.User{
{
ID: 1,
Username: "urec",
Email: "mail@mail.ru",
AvatarImage: "image",
BlackPhoenix: true,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {},
expectedUsers: map[string][]domain.User{
"users": {
{
ID: 1,
Username: "urec",
Email: "mail@mail.ru",
AvatarImage: "image",
BlackPhoenix: true,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
},
},
expectedStatusCode: http.StatusOK,
},
{
name: "ok_2",
servBehavior: func(s *mock_service.MockServ, username string, users []domain.User, err error) {
s.EXPECT().GetAll(username).Return(users, err)
},
servUsers: []domain.User{
{
ID: 1,
Username: "urec",
Email: "mail@mail.ru",
AvatarImage: "image",
BlackPhoenix: true,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {},
expectedUsers: map[string][]domain.User{
"users": {
{
ID: 1,
Username: "urec",
Email: "mail@mail.ru",
AvatarImage: "image",
BlackPhoenix: true,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
},
},
expectedStatusCode: http.StatusOK,
},
{
name: "serv_error",
servBehavior: func(s *mock_service.MockServ, username string, users []domain.User, err error) {
s.EXPECT().GetAll(username).Return(users, err)
},
servErr: domain.AnyError,
logBehavior: func(l *mock_logger.MockLog, path string, err error) {
l.EXPECT().Infof("[%s] getting users: %s", path, err.Error())
},
logErr: domain.AnyError,
expectedStatusCode: http.StatusInternalServerError,
},
{
name: "response_writing_error",
servBehavior: func(s *mock_service.MockServ, username string, users []domain.User, err error) {
s.EXPECT().GetAll(username).Return(users, err)
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {
l.EXPECT().Errorf("[%s] writing response: %s", path, err.Error())
},
logErr: domain.AnyError,
writeErr: domain.AnyError,
expectedStatusCode: http.StatusInternalServerError,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
reqPath := "/users"
log := mock_logger.NewMockLog(c)
serv := mock_service.NewMockServ(c)
tc.logBehavior(log, reqPath, tc.logErr)
tc.servBehavior(serv, tc.username, tc.servUsers, tc.servErr)
req := httptest.NewRequest(http.MethodGet, reqPath, nil)
w := mock_http.NewRecorder(tc.writeErr)
q := req.URL.Query()
q.Add("username", tc.username)
req.URL.RawQuery = q.Encode()
h := &Handler{serv: serv, l: log}
h.GetUsers(w, req)
resp := w.Result()
b, err := io.ReadAll(resp.Body)
assert.NoError(t, err)
var u map[string][]domain.User
if tc.expectedStatusCode == http.StatusOK {
err = json.Unmarshal(b, &u)
assert.NoError(t, err)
}
assert.Equal(t, tc.expectedUsers, u)
assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
})
}
}
func TestHandler_CheckExisting(t *testing.T) {
type servBehavior func(s *mock_service.MockServ, username, email string, user domain.User, err error)
type logBehavior func(l *mock_logger.MockLog, path string, err error)
testTable := []struct {
name string
reqBody domain.UserFilter
servBehavior servBehavior
logBehavior logBehavior
servUser domain.User
servErr error
logErr error
isDecodeError bool
expectedStatusCode int
}{
{
name: "ok",
reqBody: domain.UserFilter{Username: "urecs", Email: "mail@atod.com"},
servBehavior: func(s *mock_service.MockServ, username, email string, user domain.User, err error) {
s.EXPECT().FindOne(username, email).Return(user, err)
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {},
expectedStatusCode: http.StatusOK,
},
{
name: "decoding_body_error",
servBehavior: func(s *mock_service.MockServ, username, email string, user domain.User, err error) {},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {},
isDecodeError: true,
expectedStatusCode: http.StatusUnprocessableEntity,
},
{
name: "validation_error",
reqBody: domain.UserFilter{Username: "u"},
servBehavior: func(s *mock_service.MockServ, username, email string, user domain.User, err error) {},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {
l.EXPECT().Infof("[%s] validation: %s", path, err.Error())
},
logErr: errors.New("Key: 'UserFilter.Username' Error:Field validation for 'Username' failed on the 'min' tag"),
expectedStatusCode: http.StatusUnprocessableEntity,
},
{
name: "serv_error",
reqBody: domain.UserFilter{Username: "urec"},
servBehavior: func(s *mock_service.MockServ, username, email string, user domain.User, err error) {
s.EXPECT().FindOne(username, email).Return(user, err)
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {
l.EXPECT().Infof("[%s] serv.FindOne error: %s", path, err.Error())
},
servErr: domain.AnyError,
logErr: domain.AnyError,
expectedStatusCode: http.StatusInternalServerError,
},
{
name: "user_already_exists",
reqBody: domain.UserFilter{Username: "urec"},
servUser: domain.User{ID: 1},
servBehavior: func(s *mock_service.MockServ, username, email string, user domain.User, err error) {
s.EXPECT().FindOne(username, email).Return(user, err)
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {},
expectedStatusCode: http.StatusConflict,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
reqPath := "/check_existing_user"
log := mock_logger.NewMockLog(c)
serv := mock_service.NewMockServ(c)
tc.logBehavior(log, reqPath, tc.logErr)
tc.servBehavior(serv, tc.reqBody.Username, tc.reqBody.Email, tc.servUser, tc.servErr)
var body []byte
if !tc.isDecodeError {
b, err := json.Marshal(tc.reqBody)
assert.NoError(t, err)
body = b
} else {
b := make([]byte, 0)
body = b
}
req := httptest.NewRequest(http.MethodPost, reqPath, bytes.NewBuffer(body))
w := mock_http.NewRecorder(nil)
h := &Handler{serv: serv, l: log}
h.CheckExisting(w, req)
resp := w.Result()
assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
})
}
}
func TestHandler_CheckExistingPassword(t *testing.T) {
reqPath := "/check_existing_password"
req := httptest.NewRequest(http.MethodPost, reqPath, nil)
w := httptest.NewRecorder()
h := &Handler{}
for i := 0; i < 10; i++ {
h.CheckExistingPassword(w, req)
}
}
func TestHandler_Register(t *testing.T) {
type servBehavior func(s *mock_service.MockServ, userData domain.UserRegister, token string, err error)
type logBehavior func(l *mock_logger.MockLog, path string, err error)
testTable := []struct {
name string
reqBody domain.UserRegister
servBehavior servBehavior
logBehavior logBehavior
servToken string
servErr error
logErr error
isDecodeError bool
writeErr error
expectedStatusCode int
expectedBody map[string]string
}{
{
name: "ok",
reqBody: domain.UserRegister{
Username: "urecs",
Email: "mail@atod.com",
Password: "password",
Password2: "password",
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
servBehavior: func(s *mock_service.MockServ, userData domain.UserRegister, token string, err error) {
s.EXPECT().Register(userData).Return(token, err)
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {},
servToken: "token",
expectedStatusCode: http.StatusCreated,
expectedBody: map[string]string{"authorization": "Bearer token"},
},
{
name: "user_already_exists",
reqBody: domain.UserRegister{
Username: "urec",
Email: "mail@atod.com",
Password: "password",
Password2: "password",
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
servBehavior: func(s *mock_service.MockServ, userData domain.UserRegister, token string, err error) {
s.EXPECT().Register(userData).Return(token, err)
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {},
servErr: domain.UserAlreadyExistsError,
expectedStatusCode: http.StatusConflict,
},
{
name: "decoding_body_error",
servBehavior: func(s *mock_service.MockServ, userData domain.UserRegister, token string, err error) {},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {},
isDecodeError: true,
expectedStatusCode: http.StatusUnprocessableEntity,
},
{
name: "validation_error",
reqBody: domain.UserRegister{
Email: "mail@atod.com",
Password: "password",
Password2: "password",
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
servBehavior: func(s *mock_service.MockServ, userData domain.UserRegister, token string, err error) {},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {
l.EXPECT().Infof("[%s] validation: %s", path, err.Error())
},
logErr: errors.New("Key: 'UserRegister.Username' Error:Field validation for 'Username' failed on the 'min' tag"),
expectedStatusCode: http.StatusUnprocessableEntity,
},
{
name: "serv_error",
reqBody: domain.UserRegister{
Username: "urec",
Email: "mail@atod.com",
Password: "password",
Password2: "password",
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
servBehavior: func(s *mock_service.MockServ, userData domain.UserRegister, token string, err error) {
s.EXPECT().Register(userData).Return(token, err)
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {
l.EXPECT().Infof("[%s] serv.Register: %s", path, err.Error())
},
servErr: domain.AnyError,
logErr: domain.AnyError,
expectedStatusCode: http.StatusInternalServerError,
},
{
name: "response_writing_error",
reqBody: domain.UserRegister{
Username: "urec",
Email: "mail@atod.com",
Password: "password",
Password2: "password",
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
servBehavior: func(s *mock_service.MockServ, userData domain.UserRegister, token string, err error) {
s.EXPECT().Register(userData).Return(token, err)
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {
l.EXPECT().Errorf("[%s] writing response: %s", path, err.Error())
},
writeErr: domain.AnyError,
logErr: domain.AnyError,
expectedStatusCode: http.StatusCreated,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
reqPath := "/register"
log := mock_logger.NewMockLog(c)
serv := mock_service.NewMockServ(c)
tc.logBehavior(log, reqPath, tc.logErr)
tc.servBehavior(serv, tc.reqBody, tc.servToken, tc.servErr)
var body []byte
if !tc.isDecodeError {
b, err := json.Marshal(tc.reqBody)
assert.NoError(t, err)
body = b
} else {
b := make([]byte, 0)
body = b
}
req := httptest.NewRequest(http.MethodPost, reqPath, bytes.NewBuffer(body))
w := mock_http.NewRecorder(tc.writeErr)
h := &Handler{serv: serv, l: log}
h.Register(w, req)
resp := w.Result()
b, err := io.ReadAll(resp.Body)
assert.NoError(t, err)
var authToken map[string]string
if tc.expectedStatusCode == http.StatusCreated && tc.writeErr == nil {
err = json.Unmarshal(b, &authToken)
assert.NoError(t, err)
}
assert.Equal(t, tc.expectedBody, authToken)
assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
})
}
}
func TestHandler_ResendEmailVerification(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
log := mock_logger.NewMockLog(c)
serv := mock_service.NewMockServ(c)
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{serv: serv, l: log}
h.ResendEmailVerification(w, req)
_ = w.Result()
}
func TestHandler_EmailVerification(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
log := mock_logger.NewMockLog(c)
serv := mock_service.NewMockServ(c)
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{serv: serv, l: log}
h.EmailVerification(w, req)
_ = w.Result()
}
func TestHandler_Login(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
log := mock_logger.NewMockLog(c)
serv := mock_service.NewMockServ(c)
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{serv: serv, l: log}
h.Login(w, req)
_ = w.Result()
}
func TestHandler_Get(t *testing.T) {
type logBehavior func(l *mock_logger.MockLog, path string, err error)
testTable := []struct {
name string
ctxUser domain.User
addingCtxUser bool
logBehavior logBehavior
logErr error
writeErr error
expectedUser domain.User
expectedStatusCode int
}{
{
name: "ok",
ctxUser: domain.User{
ID: 1,
Username: "urec",
Email: "mail@mail.ru",
AvatarImage: "image",
BlackPhoenix: true,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
addingCtxUser: true,
logBehavior: func(l *mock_logger.MockLog, path string, err error) {},
expectedUser: domain.User{
ID: 1,
Username: "urec",
Email: "mail@mail.ru",
AvatarImage: "image",
BlackPhoenix: true,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
expectedStatusCode: http.StatusOK,
},
{
name: "incorrect_user_in_ctx",
logBehavior: func(l *mock_logger.MockLog, path string, err error) {
l.EXPECT().Errorf("[%s] extracting user from ctx", path)
},
expectedStatusCode: http.StatusInternalServerError,
},
{
name: "response_writing_error",
ctxUser: domain.User{
ID: 1,
Username: "urec",
Email: "mail@mail.ru",
AvatarImage: "image",
BlackPhoenix: true,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
addingCtxUser: true,
logBehavior: func(l *mock_logger.MockLog, path string, err error) {
l.EXPECT().Errorf("[%s] writing response: %s", path, err.Error())
},
logErr: domain.AnyError,
writeErr: domain.AnyError,
expectedStatusCode: http.StatusInternalServerError,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
reqPath := "/users/me"
log := mock_logger.NewMockLog(c)
tc.logBehavior(log, reqPath, tc.logErr)
req := httptest.NewRequest(http.MethodGet, reqPath, nil)
w := mock_http.NewRecorder(tc.writeErr)
ctx := req.Context()
if tc.addingCtxUser {
ctx = context.WithValue(ctx, "user", tc.ctxUser)
}
req = req.WithContext(ctx)
h := &Handler{l: log}
h.Get(w, req)
resp := w.Result()
b, err := io.ReadAll(resp.Body)
assert.NoError(t, err)
var u domain.User
if tc.expectedStatusCode == http.StatusOK {
err = json.Unmarshal(b, &u)
assert.NoError(t, err)
}
assert.Equal(t, tc.expectedUser, u)
assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
})
}
}
func TestHandler_GetAvatarsHistory(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
log := mock_logger.NewMockLog(c)
serv := mock_service.NewMockServ(c)
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{serv: serv, l: log}
h.GetAvatarsHistory(w, req)
_ = w.Result()
}
func TestHandler_SendConfirmationCode(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
log := mock_logger.NewMockLog(c)
serv := mock_service.NewMockServ(c)
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{serv: serv, l: log}
h.SendConfirmationCode(w, req)
_ = w.Result()
}
func TestHandler_ChangeUserData(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
log := mock_logger.NewMockLog(c)
serv := mock_service.NewMockServ(c)
req := httptest.NewRequest("", "/chats", nil)
w := httptest.NewRecorder()
h := &Handler{serv: serv, l: log}
h.ChangeUserData(w, req)
_ = w.Result()
}

View file

@ -0,0 +1,80 @@
package middleware
import (
"context"
"errors"
"net/http"
"git.urec56.ru/urec/chat_back_go/internal/domain"
)
// Auth достаёт jwt токен из хедера `Authorization` по схеме `Bearer` и кидает в контекст юзера
func (m *Middleware) Auth(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
t, err := m.serv.ExtractAuthToken(r)
if err != nil {
m.l.Infof("[%s] error extracting token: %s", r.URL.Path, err.Error())
w.WriteHeader(http.StatusUnauthorized)
return
}
userID, err := m.serv.DecodeAuthToken(t)
if err != nil {
m.l.Infof("[%s] error decoding token: %s", r.URL.Path, err.Error())
w.WriteHeader(http.StatusUnauthorized)
return
}
user, err := m.serv.Get(userID)
if err != nil {
if errors.Is(err, domain.UserNotFoundError) {
w.WriteHeader(http.StatusNotFound)
return
}
m.l.Infof("[%s] error resolving user: %s", r.URL.Path, err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
ctx := r.Context()
ctx = context.WithValue(ctx, "user", user)
r = r.WithContext(ctx)
next(w, r)
}
}
// VerificatedAuth достаёт jwt токен из хедера `Authorization` по схеме `Bearer` и кидает в контекст верифицированного юзера
func (m *Middleware) VerificatedAuth(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
t, err := m.serv.ExtractAuthToken(r)
if err != nil {
m.l.Infof("[%s] error extracting token: %s", r.URL.Path, err.Error())
w.WriteHeader(http.StatusUnauthorized)
return
}
userID, err := m.serv.DecodeAuthToken(t)
if err != nil {
m.l.Infof("[%s] error decoding token: %s", r.URL.Path, err.Error())
w.WriteHeader(http.StatusUnauthorized)
return
}
user, err := m.serv.GetVerificated(userID)
if err != nil {
if errors.Is(err, domain.UserNotFoundError) {
w.WriteHeader(http.StatusNotFound)
return
} else if errors.Is(err, domain.UnverifiedUserError) {
w.WriteHeader(http.StatusConflict)
return
}
m.l.Infof("[%s] error resolving user: %s", r.URL.Path, err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
ctx := r.Context()
ctx = context.WithValue(ctx, "user", user)
r = r.WithContext(ctx)
next(w, r)
}
}

View file

@ -0,0 +1,359 @@
package middleware
import (
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"net/http"
"net/http/httptest"
"testing"
"time"
"git.urec56.ru/urec/chat_back_go/internal/domain"
mock_logger "git.urec56.ru/urec/chat_back_go/internal/logger/mocks"
mock_service "git.urec56.ru/urec/chat_back_go/internal/service/mocks"
)
func TestMiddleware_Auth(t *testing.T) {
type extractBehavior func(s *mock_service.MockServ, r *http.Request, token string, err error)
type decodeBehavior func(s *mock_service.MockServ, token string, userID int, err error)
type getBehavior func(s *mock_service.MockServ, userID int, user domain.User, err error)
type logBehavior func(l *mock_logger.MockLog, path string, err error)
testTable := []struct {
name string
extractBehavior extractBehavior
decodeBehavior decodeBehavior
getBehavior getBehavior
logBehavior logBehavior
reqToken string
extractToken string
extractErr error
decodeUserID int
decodeErr error
getUser domain.User
getErr error
logErr error
expectedUser domain.User
expectedStatusCode int
}{
{
name: "ok",
extractBehavior: func(s *mock_service.MockServ, r *http.Request, token string, err error) {
s.EXPECT().ExtractAuthToken(r).Return(token, err)
},
decodeBehavior: func(s *mock_service.MockServ, token string, userID int, err error) {
s.EXPECT().DecodeAuthToken(token).Return(userID, err)
},
getBehavior: func(s *mock_service.MockServ, userID int, user domain.User, err error) {
s.EXPECT().Get(userID).Return(user, err)
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {},
reqToken: "Bearer token",
extractToken: "token",
decodeUserID: 1,
getUser: domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "image",
BlackPhoenix: true,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
expectedUser: domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "image",
BlackPhoenix: true,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
expectedStatusCode: http.StatusOK,
},
{
name: "user_not_found",
extractBehavior: func(s *mock_service.MockServ, r *http.Request, token string, err error) {
s.EXPECT().ExtractAuthToken(r).Return(token, err)
},
decodeBehavior: func(s *mock_service.MockServ, token string, userID int, err error) {
s.EXPECT().DecodeAuthToken(token).Return(userID, err)
},
getBehavior: func(s *mock_service.MockServ, userID int, user domain.User, err error) {
s.EXPECT().Get(userID).Return(user, err)
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {},
reqToken: "Bearer token",
extractToken: "token",
getErr: domain.UserNotFoundError,
expectedStatusCode: http.StatusNotFound,
},
{
name: "extract_error",
extractBehavior: func(s *mock_service.MockServ, r *http.Request, token string, err error) {
s.EXPECT().ExtractAuthToken(r).Return(token, err)
},
decodeBehavior: func(s *mock_service.MockServ, token string, userID int, err error) {},
getBehavior: func(s *mock_service.MockServ, userID int, user domain.User, err error) {},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {
l.EXPECT().Infof("[%s] error extracting token: %s", path, err.Error())
},
extractErr: domain.AnyError,
logErr: domain.AnyError,
expectedStatusCode: http.StatusUnauthorized,
},
{
name: "decode_error",
extractBehavior: func(s *mock_service.MockServ, r *http.Request, token string, err error) {
s.EXPECT().ExtractAuthToken(r).Return(token, err)
},
decodeBehavior: func(s *mock_service.MockServ, token string, userID int, err error) {
s.EXPECT().DecodeAuthToken(token).Return(userID, err)
},
getBehavior: func(s *mock_service.MockServ, userID int, user domain.User, err error) {},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {
l.EXPECT().Infof("[%s] error decoding token: %s", path, err.Error())
},
decodeErr: domain.AnyError,
logErr: domain.AnyError,
expectedStatusCode: http.StatusUnauthorized,
},
{
name: "get_error",
extractBehavior: func(s *mock_service.MockServ, r *http.Request, token string, err error) {
s.EXPECT().ExtractAuthToken(r).Return(token, err)
},
decodeBehavior: func(s *mock_service.MockServ, token string, userID int, err error) {
s.EXPECT().DecodeAuthToken(token).Return(userID, err)
},
getBehavior: func(s *mock_service.MockServ, userID int, user domain.User, err error) {
s.EXPECT().Get(userID).Return(user, err)
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {
l.EXPECT().Infof("[%s] error resolving user: %s", path, err.Error())
},
getErr: domain.AnyError,
logErr: domain.AnyError,
expectedStatusCode: http.StatusInternalServerError,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
log := mock_logger.NewMockLog(c)
serv := mock_service.NewMockServ(c)
reqPath := "/"
req := httptest.NewRequest(http.MethodGet, reqPath, nil)
w := httptest.NewRecorder()
req.Header.Set("Authorization", tc.reqToken)
tc.extractBehavior(serv, req, tc.extractToken, tc.extractErr)
tc.decodeBehavior(serv, tc.extractToken, tc.decodeUserID, tc.decodeErr)
tc.getBehavior(serv, tc.decodeUserID, tc.getUser, tc.getErr)
tc.logBehavior(log, reqPath, tc.logErr)
m := &Middleware{serv: serv, l: log}
server := m.Auth(func(w http.ResponseWriter, r *http.Request) {
u := r.Context().Value("user")
assert.Equal(t, tc.expectedUser, u)
})
server.ServeHTTP(w, req)
resp := w.Result()
assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
})
}
}
func TestMiddleware_VerificatedAuth(t *testing.T) {
type extractBehavior func(s *mock_service.MockServ, r *http.Request, token string, err error)
type decodeBehavior func(s *mock_service.MockServ, token string, userID int, err error)
type getBehavior func(s *mock_service.MockServ, userID int, user domain.User, err error)
type logBehavior func(l *mock_logger.MockLog, path string, err error)
testTable := []struct {
name string
extractBehavior extractBehavior
decodeBehavior decodeBehavior
getBehavior getBehavior
logBehavior logBehavior
reqToken string
extractToken string
extractErr error
decodeUserID int
decodeErr error
getUser domain.User
getErr error
logErr error
expectedUser domain.User
expectedStatusCode int
}{
{
name: "ok",
extractBehavior: func(s *mock_service.MockServ, r *http.Request, token string, err error) {
s.EXPECT().ExtractAuthToken(r).Return(token, err)
},
decodeBehavior: func(s *mock_service.MockServ, token string, userID int, err error) {
s.EXPECT().DecodeAuthToken(token).Return(userID, err)
},
getBehavior: func(s *mock_service.MockServ, userID int, user domain.User, err error) {
s.EXPECT().GetVerificated(userID).Return(user, err)
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {},
reqToken: "Bearer token",
extractToken: "token",
decodeUserID: 1,
getUser: domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "image",
BlackPhoenix: true,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
expectedUser: domain.User{
ID: 1,
Role: 1,
Username: "urec",
Email: "mail@mail.ru",
HashedPassword: "hp",
AvatarImage: "image",
BlackPhoenix: true,
DateOfBirth: domain.CustomDate{Time: time.Date(2002, time.February, 2, 0, 0, 0, 0, time.UTC)},
DateOfRegistration: domain.CustomDate{Time: time.Date(2025, time.February, 2, 0, 0, 0, 0, time.UTC)},
},
expectedStatusCode: http.StatusOK,
},
{
name: "unverified_user",
extractBehavior: func(s *mock_service.MockServ, r *http.Request, token string, err error) {
s.EXPECT().ExtractAuthToken(r).Return(token, err)
},
decodeBehavior: func(s *mock_service.MockServ, token string, userID int, err error) {
s.EXPECT().DecodeAuthToken(token).Return(userID, err)
},
getBehavior: func(s *mock_service.MockServ, userID int, user domain.User, err error) {
s.EXPECT().GetVerificated(userID).Return(user, err)
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {},
reqToken: "Bearer token",
extractToken: "token",
getErr: domain.UnverifiedUserError,
expectedStatusCode: http.StatusConflict,
},
{
name: "user_not_found",
extractBehavior: func(s *mock_service.MockServ, r *http.Request, token string, err error) {
s.EXPECT().ExtractAuthToken(r).Return(token, err)
},
decodeBehavior: func(s *mock_service.MockServ, token string, userID int, err error) {
s.EXPECT().DecodeAuthToken(token).Return(userID, err)
},
getBehavior: func(s *mock_service.MockServ, userID int, user domain.User, err error) {
s.EXPECT().GetVerificated(userID).Return(user, err)
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {},
reqToken: "Bearer token",
extractToken: "token",
getErr: domain.UserNotFoundError,
expectedStatusCode: http.StatusNotFound,
},
{
name: "extract_error",
extractBehavior: func(s *mock_service.MockServ, r *http.Request, token string, err error) {
s.EXPECT().ExtractAuthToken(r).Return(token, err)
},
decodeBehavior: func(s *mock_service.MockServ, token string, userID int, err error) {},
getBehavior: func(s *mock_service.MockServ, userID int, user domain.User, err error) {},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {
l.EXPECT().Infof("[%s] error extracting token: %s", path, err.Error())
},
extractErr: domain.AnyError,
logErr: domain.AnyError,
expectedStatusCode: http.StatusUnauthorized,
},
{
name: "decode_error",
extractBehavior: func(s *mock_service.MockServ, r *http.Request, token string, err error) {
s.EXPECT().ExtractAuthToken(r).Return(token, err)
},
decodeBehavior: func(s *mock_service.MockServ, token string, userID int, err error) {
s.EXPECT().DecodeAuthToken(token).Return(userID, err)
},
getBehavior: func(s *mock_service.MockServ, userID int, user domain.User, err error) {},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {
l.EXPECT().Infof("[%s] error decoding token: %s", path, err.Error())
},
decodeErr: domain.AnyError,
logErr: domain.AnyError,
expectedStatusCode: http.StatusUnauthorized,
},
{
name: "get_error",
extractBehavior: func(s *mock_service.MockServ, r *http.Request, token string, err error) {
s.EXPECT().ExtractAuthToken(r).Return(token, err)
},
decodeBehavior: func(s *mock_service.MockServ, token string, userID int, err error) {
s.EXPECT().DecodeAuthToken(token).Return(userID, err)
},
getBehavior: func(s *mock_service.MockServ, userID int, user domain.User, err error) {
s.EXPECT().GetVerificated(userID).Return(user, err)
},
logBehavior: func(l *mock_logger.MockLog, path string, err error) {
l.EXPECT().Infof("[%s] error resolving user: %s", path, err.Error())
},
getErr: domain.AnyError,
logErr: domain.AnyError,
expectedStatusCode: http.StatusInternalServerError,
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
log := mock_logger.NewMockLog(c)
serv := mock_service.NewMockServ(c)
reqPath := "/"
req := httptest.NewRequest(http.MethodGet, reqPath, nil)
w := httptest.NewRecorder()
req.Header.Set("Authorization", tc.reqToken)
tc.extractBehavior(serv, req, tc.extractToken, tc.extractErr)
tc.decodeBehavior(serv, tc.extractToken, tc.decodeUserID, tc.decodeErr)
tc.getBehavior(serv, tc.decodeUserID, tc.getUser, tc.getErr)
tc.logBehavior(log, reqPath, tc.logErr)
m := &Middleware{serv: serv, l: log}
server := m.VerificatedAuth(func(w http.ResponseWriter, r *http.Request) {
u := r.Context().Value("user")
assert.Equal(t, tc.expectedUser, u)
})
server.ServeHTTP(w, req)
resp := w.Result()
assert.Equal(t, tc.expectedStatusCode, resp.StatusCode)
})
}
}

View file

@ -0,0 +1,12 @@
package middleware
import (
"net/http"
)
func (m *Middleware) Log(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
m.l.Infof("[%s] \"%s %s\"", r.Context().Value(RequestIDKey), r.Method, r.URL.Path)
next(w, r)
}
}

View file

@ -0,0 +1,72 @@
package middleware
import (
"context"
"go.uber.org/mock/gomock"
"net/http"
"net/http/httptest"
"testing"
mock_logger "git.urec56.ru/urec/chat_back_go/internal/logger/mocks"
)
func TestMiddleware_Log(t *testing.T) {
type logBehavior func(l *mock_logger.MockLog, requestID any, method, url string)
testTable := []struct {
name string
requestMethod string
requestUrl string
requestID any
logBehavior logBehavior
}{
{
name: "ok_1",
requestMethod: http.MethodGet,
requestUrl: "/users",
requestID: 1,
logBehavior: func(l *mock_logger.MockLog, requestID any, method, url string) {
l.EXPECT().Infof("[%s] \"%s %s\"", requestID, method, url)
},
},
{
name: "ok_2",
requestMethod: http.MethodPost,
requestUrl: "/chat",
requestID: 10031,
logBehavior: func(l *mock_logger.MockLog, requestID any, method, url string) {
l.EXPECT().Infof("[%s] \"%s %s\"", requestID, method, url)
},
},
{
name: "ok_3",
requestMethod: http.MethodPost,
requestUrl: "/chat",
logBehavior: func(l *mock_logger.MockLog, requestID any, method, url string) {
l.EXPECT().Infof("[%s] \"%s %s\"", requestID, method, url)
},
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
log := mock_logger.NewMockLog(c)
tc.logBehavior(log, tc.requestID, tc.requestMethod, tc.requestUrl)
req := httptest.NewRequest(tc.requestMethod, tc.requestUrl, nil)
w := httptest.NewRecorder()
ctx := req.Context()
ctx = context.WithValue(ctx, RequestIDKey, tc.requestID)
req = req.WithContext(ctx)
m := &Middleware{l: log}
server := m.Log(func(w http.ResponseWriter, r *http.Request) {})
server.ServeHTTP(w, req)
})
}
}

View file

@ -0,0 +1,18 @@
package middleware
import (
"git.urec56.ru/urec/chat_back_go/config"
"git.urec56.ru/urec/chat_back_go/internal/logger"
"git.urec56.ru/urec/chat_back_go/internal/service"
)
type Middleware struct {
serv service.Serv
l logger.Log
cfg config.Server
reqID uint64 // 0
}
func NewMiddleware(serv service.Serv, l logger.Log, cfg config.Server) *Middleware {
return &Middleware{serv: serv, l: l, cfg: cfg}
}

View file

@ -0,0 +1,23 @@
package middleware
import (
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"testing"
"git.urec56.ru/urec/chat_back_go/config"
mock_logger "git.urec56.ru/urec/chat_back_go/internal/logger/mocks"
mock_service "git.urec56.ru/urec/chat_back_go/internal/service/mocks"
)
func Test_NewMiddleware(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
serv := mock_service.NewMockServ(c)
log := mock_logger.NewMockLog(c)
cfg := config.Server{}
m := NewMiddleware(serv, log, cfg)
assert.Equal(t, &Middleware{serv: serv, l: log, cfg: cfg}, m)
}

View file

@ -0,0 +1,26 @@
package middleware
import (
"context"
"fmt"
"net/http"
"sync/atomic"
)
type ctxKeyRequestID int
const RequestIDKey ctxKeyRequestID = 0
func (m *Middleware) RequestID(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
requestID := r.Header.Get(m.cfg.RequestIDHeader)
if requestID == "" {
myID := atomic.AddUint64(&m.reqID, 1)
requestID = fmt.Sprintf("%06d", myID)
}
ctx = context.WithValue(ctx, RequestIDKey, requestID)
r = r.WithContext(ctx)
next(w, r)
}
}

View file

@ -0,0 +1,57 @@
package middleware
import (
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"net/http"
"net/http/httptest"
"testing"
"git.urec56.ru/urec/chat_back_go/config"
)
func TestMiddleware_RequestID(t *testing.T) {
testTable := []struct {
name string
cfgRequestIDHeader string
requestIDHeader string
requestID string
requestIDInHeader string
expectedRequestID string
}{
{
name: "id_in_header",
cfgRequestIDHeader: "X-Request-Id",
requestIDHeader: "X-Request-Id",
requestIDInHeader: "023945",
expectedRequestID: "023945",
},
{
name: "no_id_in_request_header",
expectedRequestID: "000001",
},
}
for _, tc := range testTable {
t.Run(tc.name, func(t *testing.T) {
c := gomock.NewController(t)
defer c.Finish()
req := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
req.Header.Set(tc.requestIDHeader, tc.requestIDInHeader)
cfg := config.Server{RequestIDHeader: tc.cfgRequestIDHeader}
m := &Middleware{cfg: cfg}
server := m.RequestID(func(w http.ResponseWriter, r *http.Request) {
reqID := r.Context().Value(RequestIDKey)
assert.Equal(t, tc.expectedRequestID, reqID)
})
server.ServeHTTP(w, req)
})
}
}

View file

@ -0,0 +1,109 @@
package rest
import (
"context"
"expvar"
"github.com/urec56/pathparams"
"net/http"
"git.urec56.ru/urec/chat_back_go/config"
"git.urec56.ru/urec/chat_back_go/internal/logger"
"git.urec56.ru/urec/chat_back_go/internal/service"
"git.urec56.ru/urec/chat_back_go/internal/transport/rest/handler"
"git.urec56.ru/urec/chat_back_go/internal/transport/rest/middleware"
)
type Server struct {
httpServer *http.Server
m *middleware.Middleware
h *handler.Handler
}
func NewServer(serv service.Serv, l logger.Log, cfg config.Config) *Server {
return &Server{m: middleware.NewMiddleware(serv, l, cfg.Srv), h: handler.NewHandler(serv, l)}
}
func (s *Server) Run(port string) error {
r := s.getRouter()
s.httpServer = &http.Server{
Addr: ":" + port,
Handler: r,
}
return s.httpServer.ListenAndServe()
}
func (s *Server) Shutdown(ctx context.Context) error {
return s.httpServer.Shutdown(ctx)
}
func (s *Server) getRouter() http.Handler {
pathparams.SetConfig("", pathparams.DefaultValidationErrorStatusCode)
ro := pathparams.NewRouter()
r := ro.PathPrefix("/api")
r.Use(s.m.RequestID)
r.Use(s.m.Log)
s.initUsersRouter(r)
s.initChatRouter(r)
s.initImagesRouter(r)
s.initDebug(r)
return ro
}
func (s *Server) initUsersRouter(r *pathparams.Router) {
ur := r.PathPrefix("/users")
aur := r.PathPrefix("/users")
aur.Use(s.m.Auth)
ur.Get("", s.h.GetUsers)
ur.Post("/check_existing_user", s.h.CheckExisting)
ur.Post("/check_existing_password", s.h.CheckExistingPassword)
ur.Post("/login", s.h.Login)
ur.Post("/register", s.h.Register)
aur.Post("/resend_email_verification", s.h.ResendEmailVerification)
aur.Post("/email_verification", s.h.EmailVerification)
aur.Get("/me", s.h.Get)
aur.Get("/avatars", s.h.GetAvatarsHistory)
aur.Post("/send_confirmation_code", s.h.SendConfirmationCode)
aur.Post("/change_data", s.h.ChangeUserData)
}
func (s *Server) initChatRouter(r *pathparams.Router) {
chr := r.PathPrefix("/chat")
chr.Use(s.m.VerificatedAuth)
chr.Get("", s.h.GetChats)
chr.Post("/create_chat", s.h.Create)
chr.Post("/change_data", s.h.ChangeChatData)
chr.Get("/get_last_message/{chat_id}", s.h.GetLastMessage)
chr.Get("/get_some_messages/{chat_id}", s.h.GetSomeMessages)
chr.Get("/message/{chat_id}", s.h.GetMessageByID)
chr.Get("/create_invitation_link", s.h.CreateInvitationLink)
chr.Get("/invite_to_chat/{invitation_token}", s.h.InviteToChat)
chr.Delete("/delete_chat/{chat_id}", s.h.DeleteChat)
chr.Delete("/delete_user_from_chat/{chat_id}", s.h.DeleteUserFromChat)
chr.Post("/pin_chat", s.h.PinChat)
chr.Delete("/unpin_chat", s.h.UnpinnChat)
chr.Get("/get_pinned_chats", s.h.GetPinnedChats)
chr.Get("/pinned_messages/{chat_id}", s.h.GetPinnedMessages)
}
func (s *Server) initImagesRouter(r *pathparams.Router) {
ir := r.PathPrefix("/images")
ir.Use(s.m.VerificatedAuth)
ir.Post("/upload_avatar", s.h.UploadAvatar)
ir.Post("/upload_image", s.h.UploadImage)
}
func (s *Server) initDebug(r *pathparams.Router) {
r.Get("/debug/vars", expvar.Handler().(http.HandlerFunc))
}

View file

@ -0,0 +1,7 @@
DROP TABLE user_chat;
DROP TABLE user_avatar;
DROP TABLE pinned_message;
DROP TABLE pinned_chat;
DROP TABLE chat;
DROP TABLE users;

View file

@ -0,0 +1,64 @@
CREATE TABLE users(
id BIGSERIAL PRIMARY KEY,
email VARCHAR NOT NULL UNIQUE,
username VARCHAR NOT NULL UNIQUE,
hashed_password VARCHAR NOT NULL,
role INT NOT NULL DEFAULT 0,
black_phoenix BOOL NOT NULL DEFAULT FALSE,
avatar_image VARCHAR NOT NULL DEFAULT 'https://images.black-phoenix.ru/static/images/%D1%82%D1%8B%20%D1%83%D0%B6%D0%B5%20%D0%BF%D0%B5%D1%88%D0%BA%D0%B0%20BP.png',
date_of_birth DATE NOT NULL,
date_of_registration DATE NOT NULL DEFAULT now()
);
CREATE TABLE chat(
id BIGSERIAL PRIMARY KEY,
created_by BIGINT NOT NULL,
chat_for BIGINT NOT NULL,
chat_name VARCHAR NOT NULL,
avatar_image VARCHAR NOT NULL,
visibility BOOL NOT NULL DEFAULT TRUE,
FOREIGN KEY (created_by)
REFERENCES users (id),
FOREIGN KEY (chat_for)
REFERENCES users (id)
);
CREATE TABLE pinned_chat(
user_id BIGINT,
chat_id BIGINT,
PRIMARY KEY (user_id, chat_id),
FOREIGN KEY (user_id)
REFERENCES users (id),
FOREIGN KEY (chat_id)
REFERENCES chat (id)
);
CREATE TABLE pinned_message(
chat_id BIGINT NOT NULL,
message_id UUID,
user_id BIGINT,
PRIMARY KEY (user_id, message_id),
FOREIGN KEY (user_id)
REFERENCES users (id),
FOREIGN KEY (chat_id)
REFERENCES chat (id)
);
CREATE TABLE user_avatar(
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
avatar_image VARCHAR NOT NULL,
FOREIGN KEY (user_id)
REFERENCES users (id)
);
CREATE TABLE user_chat(
user_id BIGINT,
chat_id BIGINT,
PRIMARY KEY (user_id, chat_id),
FOREIGN KEY (user_id)
REFERENCES users (id),
FOREIGN KEY (chat_id)
REFERENCES chat (id)
);