Signed-off-by: Nikolai Rodionov <allanger@badhouseplants.net>
This commit is contained in:
@@ -15,6 +15,9 @@ tasks:
|
||||
|
||||
unit:
|
||||
desc: Run unit tests
|
||||
env:
|
||||
SOFTPLAYER_DB_CONNECTION_STRING: postgres://softplayer:qwertyu9@localhost:30432/softplayer?sslmode=disable
|
||||
SOFTPLAYER_REDIS_HOST: localhost:30379
|
||||
cmd: go test ./...
|
||||
silent: true
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
)
|
||||
|
||||
// Migrate the database to the latest state
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/selector"
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/reflection"
|
||||
|
||||
3
e2e/go.mod
Normal file
3
e2e/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module gitea.badhouseplants.net/softplayer/softplayer-backend/e2e
|
||||
|
||||
go 1.26.3
|
||||
11
go.mod
11
go.mod
@@ -12,6 +12,9 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3
|
||||
github.com/jackc/pgconn v1.14.3
|
||||
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa
|
||||
github.com/jackc/pgx/v5 v5.5.4
|
||||
github.com/mattn/go-colorable v0.1.14
|
||||
github.com/redis/go-redis/v9 v9.18.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
@@ -24,6 +27,12 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/geozelot/intree v1.0.0 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
@@ -34,7 +43,7 @@ require (
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
|
||||
29
go.sum
29
go.sum
@@ -88,17 +88,35 @@ github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajR
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
|
||||
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
|
||||
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw=
|
||||
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
|
||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
|
||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
|
||||
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/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
|
||||
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8=
|
||||
github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
@@ -135,7 +153,9 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -196,6 +216,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -242,6 +264,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -2,7 +2,6 @@ package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
@@ -11,11 +10,17 @@ import (
|
||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/logger"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgconn"
|
||||
"github.com/jackc/pgerrcode"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
var ErrEmailUsed = errors.New("email is already used")
|
||||
|
||||
type AccountController struct {
|
||||
DB *sql.DB
|
||||
DB *pgxpool.Pool
|
||||
Redis *redis.Client
|
||||
DevMode bool
|
||||
HashCost int16
|
||||
@@ -37,6 +42,7 @@ type AccountData struct {
|
||||
UUID string
|
||||
}
|
||||
|
||||
// Create a new account
|
||||
func (c *AccountController) Create(ctx context.Context, data *AccountData) (string, error) {
|
||||
log := logger.FromContext(ctx)
|
||||
data.UUID = uuid.New().String()
|
||||
@@ -48,14 +54,27 @@ func (c *AccountController) Create(ctx context.Context, data *AccountData) (stri
|
||||
}
|
||||
|
||||
query := "INSERT INTO accounts (uuid, email, password_hash) VALUES ($1, $2, $3)"
|
||||
if _, err := c.DB.Query(query, data.UUID, data.Email, passwordHash); err != nil {
|
||||
log.Error(err, "Couldn't create a user in the database")
|
||||
return "", err
|
||||
|
||||
if _, err := c.DB.ExecContext(ctx, query, data.UUID, data.Email, passwordHash); err != nil {
|
||||
fmt.Printf("ERR TYPE: %T\n", err)
|
||||
fmt.Printf("ERR VALUE: %#v\n", err)
|
||||
var pgErr *pgconn.PgError
|
||||
if errors.As(err, &pgErr) {
|
||||
log.Error(nil, "error", "err", pgErr)
|
||||
if pgErr.Code == pgerrcode.UniqueViolation {
|
||||
return "", ErrEmailUsed
|
||||
}
|
||||
log.Error(err, "Couldn't create a user")
|
||||
return "", ErrServerError
|
||||
}
|
||||
log.Error(err, "Couldn't create a user, wtf")
|
||||
return "", ErrServerError
|
||||
}
|
||||
|
||||
return data.UUID, nil
|
||||
}
|
||||
|
||||
// Login into an existing account (check password and email)
|
||||
func (c *AccountController) Login(ctx context.Context, email, password string) (string, error) {
|
||||
log := logger.FromContext(ctx)
|
||||
query := "SELECT uuid, password_hash FROM accounts WHERE email = $1;"
|
||||
|
||||
91
internal/controllers/accounts_test.go
Normal file
91
internal/controllers/accounts_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package controllers_test
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/controllers"
|
||||
"gitea.badhouseplants.net/softplayer/softplayer-backend/internal/helpers/postgres"
|
||||
"github.com/google/uuid"
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func newTestDbConnection() *sql.DB {
|
||||
connStr, ok := os.LookupEnv("SOFTPLAYER_DB_CONNECTION_STRING")
|
||||
if !ok {
|
||||
panic("set the db connection string env var")
|
||||
}
|
||||
db, err := postgres.Open(connStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func newTestRedisConnection() *redis.Client {
|
||||
connStr, ok := os.LookupEnv("SOFTPLAYER_REDIS_HOST")
|
||||
if !ok {
|
||||
panic("set the redis connection string env var")
|
||||
}
|
||||
return redis.NewClient(&redis.Options{
|
||||
Addr: connStr,
|
||||
})
|
||||
}
|
||||
|
||||
func newTestAccountController() *controllers.AccountController {
|
||||
return &controllers.AccountController{
|
||||
DB: newTestDbConnection(),
|
||||
Redis: newTestRedisConnection(),
|
||||
DevMode: true,
|
||||
HashCost: 3,
|
||||
AccessTokenTTL: time.Second * 10,
|
||||
RefreshTokenTTL: time.Second * 15,
|
||||
JWTSecret: []byte("test-secret"),
|
||||
}
|
||||
}
|
||||
|
||||
func newTestUniqueEmail(prefix string) string {
|
||||
if prefix == "" {
|
||||
prefix = "test"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
"%s-%d-%s@example.com",
|
||||
prefix,
|
||||
time.Now().UnixMilli(),
|
||||
uuid.NewString(),
|
||||
)
|
||||
}
|
||||
|
||||
func TestIntegrationAccountCreate_Success(t *testing.T) {
|
||||
ctrl := newTestAccountController()
|
||||
accountData := &controllers.AccountData{
|
||||
Password: "qwertyu9",
|
||||
Email: newTestUniqueEmail("accounts"),
|
||||
}
|
||||
id, err := ctrl.Create(t.Context(), accountData)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, id)
|
||||
}
|
||||
|
||||
func TestIntegrationAccountCreate_ExistingAccountErr(t *testing.T) {
|
||||
ctrl := newTestAccountController()
|
||||
email := newTestUniqueEmail("accounts")
|
||||
accountData := &controllers.AccountData{
|
||||
Password: "qwertyu9",
|
||||
Email: email,
|
||||
}
|
||||
id, err := ctrl.Create(t.Context(), accountData)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, id)
|
||||
|
||||
id, err = ctrl.Create(t.Context(), accountData)
|
||||
assert.Empty(t, id)
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, controllers.ErrEmailUsed)
|
||||
}
|
||||
21
internal/helpers/postgres/postgres.go
Normal file
21
internal/helpers/postgres/postgres.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
)
|
||||
|
||||
func Open(ctx context.Context, dsn string) (*pgxpool.Pool, error) {
|
||||
dbpool, err := pgxpool.New(context.Background(), dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := dbpool.Ping(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dbpool, nil
|
||||
}
|
||||
Reference in New Issue
Block a user