Skip to content

Commit 695e0af

Browse files
committed
init commit
0 parents  commit 695e0af

23 files changed

+585
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.idea
2+
.vscode
3+
vendor

Makefile

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
.PHONY: build
2+
3+
export APP_NAME := gotest
4+
export VERSION := $(if $(TAG),$(TAG),$(if $(BRANCH_NAME),$(BRANCH_NAME),$(shell git symbolic-ref -q --short HEAD || git describe --tags --exact-match)))
5+
export DOCKER_REPOSITORY := rebrainme-webinars
6+
export DOCKER_BUILDKIT := 1
7+
8+
MIGRATE_DSN := "postgres://gotest:gotest@psql:5432/gotest?sslmode=disable"
9+
NOCACHE := $(if $(NOCACHE),"--no-cache")
10+
11+
build:
12+
@docker build ${NOCACHE} --pull -f ./build/helper.Dockerfile -t ${DOCKER_REPOSITORY}/${APP_NAME}-helper:${VERSION} --ssh default .
13+
14+
run-test-env:
15+
@docker-compose -f ./build/docker-compose.yml up -d psql
16+
@sleep 5
17+
18+
migration-up:
19+
@docker-compose -f ./build/docker-compose.yml run --rm -T helper migrate -verbose -path ./migrations -database ${MIGRATE_DSN} up
20+
21+
migration-down:
22+
@docker-compose -f ./build/docker-compose.yml run --rm -T helper migrate -verbose -path ./migrations -database ${MIGRATE_DSN} down
23+
24+
run-tests: run-test-env migration-up
25+
@docker-compose -f ./build/docker-compose.yml run --rm -T helper sh ./scripts/long_tests_runner.sh; $(MAKE) stop-env
26+
27+
stop-env:
28+
@docker-compose -f ./build/docker-compose.yml down

build/docker-compose.yml

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
version: "3"
2+
3+
services:
4+
psql:
5+
image: postgres
6+
container_name: psql-${VERSION:-develop}
7+
tty: true
8+
restart: always
9+
environment:
10+
POSTGRES_USER: gotest
11+
POSTGRES_PASSWORD: gotest
12+
POSTGRES_DB: gotest
13+
14+
helper:
15+
image: ${DOCKER_REPOSITORY}/${APP_NAME}-helper:${VERSION}
16+
container_name: helper-${VERSION:-develop}
17+
depends_on:
18+
- psql
19+
environment:
20+
PSQL_DSN: "host=psql port=5432 user=gotest password=gotest dbname=gotest sslmode=disable connect_timeout=5 binary_parameters=yes"
21+
PSQL_MAX_OPEN_CONN: 1
22+
volumes:
23+
- ../:/app

build/helper.Dockerfile

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# syntax=docker/dockerfile:experimental
2+
3+
FROM golang:1.16
4+
5+
RUN apt-get install openssh-client git
6+
7+
RUN mkdir -p -m 0600 ~/.ssh \
8+
&& ssh-keyscan git.myrepo.com >> ~/.ssh/known_hosts \
9+
&& git config --global url."ssh://[email protected]/".insteadOf "https://git.myrepo.com/"
10+
11+
RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.38.0 \
12+
mv ./bin/golangci-lint /bin/golangci-lint
13+
14+
ENV GOPRIVATE git.myrepo.com/*
15+
ENV GO111MODULE on
16+
17+
RUN curl -L https://github.com/golang-migrate/migrate/releases/download/v4.14.1/migrate.linux-amd64.tar.gz | tar -xvz \
18+
&& mv ./migrate.linux-amd64 /bin/migrate
19+
20+
WORKDIR /app
21+
COPY go.mod go.sum ./
22+
RUN --mount=type=ssh go mod download
23+

cmd/app.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package main
2+
3+
import "fmt"
4+
5+
func main() {
6+
fmt.Println("fake main")
7+
}

go.mod

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module rebrainme/gotest
2+
3+
go 1.16
4+
5+
require (
6+
github.com/Masterminds/squirrel v1.5.0 // indirect
7+
github.com/elgris/stom v0.0.0-20160204063428-05ccb51a70bb // indirect
8+
github.com/jmoiron/sqlx v1.3.3 // indirect
9+
github.com/lib/pq v1.10.0 // indirect
10+
github.com/stretchr/testify v1.7.0 // indirect
11+
github.com/vrischmann/envconfig v1.3.0 // indirect
12+
)

go.sum

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8=
2+
github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
3+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
4+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
6+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7+
github.com/elgris/stom v0.0.0-20160204063428-05ccb51a70bb h1:W75wf/IoE/FNOK+JkH96RKmRIEU64Zu/+2Xa9cX9/dk=
8+
github.com/elgris/stom v0.0.0-20160204063428-05ccb51a70bb/go.mod h1:MPN0gHWHBoMceZ3hh8GtkMaxbVpX6s5NeIj3cBH/IgU=
9+
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
10+
github.com/jmoiron/sqlx v1.3.3 h1:j82X0bf7oQ27XeqxicSZsTU5suPwKElg3oyxNn43iTk=
11+
github.com/jmoiron/sqlx v1.3.3/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
12+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
13+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
14+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
15+
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
16+
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
17+
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
18+
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
19+
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
20+
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
21+
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
22+
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
23+
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
24+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
25+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
26+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
27+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
28+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
29+
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
30+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
31+
github.com/vrischmann/envconfig v1.3.0 h1:4XIvQTXznxmWMnjouj0ST5lFo/WAYf5Exgl3x82crEk=
32+
github.com/vrischmann/envconfig v1.3.0/go.mod h1:bbvxFYJdRSpXrhS63mBFtKJzkDiNkyArOLXtY6q0kuI=
33+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
34+
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
35+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
36+
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
37+
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/config/config.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/vrischmann/envconfig"
7+
8+
"rebrainme/gotest/internal/system/database/psql"
9+
)
10+
11+
type Config struct {
12+
PSQL *psql.Config
13+
}
14+
15+
func Init() (*Config, error) {
16+
cfg := &Config{}
17+
if err := envconfig.Init(cfg); err != nil {
18+
return nil, fmt.Errorf("config init error: %w", err)
19+
}
20+
21+
return cfg, nil
22+
}

internal/entities/clients.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package entities
2+
3+
type Client struct {
4+
Email string
5+
FullName string
6+
City string
7+
}
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package clients
2+
3+
import (
4+
sq "github.com/Masterminds/squirrel"
5+
6+
"rebrainme/gotest/internal/system/database/stommer"
7+
)
8+
9+
const tableClients = "clients"
10+
11+
func prepareInsertOrUpdateClientQuery(model clientModel) (string, []interface{}, error) {
12+
psqlSq := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
13+
14+
st, err := stommer.New(model)
15+
if err != nil {
16+
return "", []interface{}{}, err
17+
}
18+
19+
rawRequest := psqlSq.Insert(tableClients).
20+
Columns(st.Columns...).
21+
Values(st.Values...).
22+
Suffix(`
23+
ON CONFLICT (email) DO UPDATE SET
24+
full_name = EXCLUDED.full_name,
25+
city = EXCLUDED.city,
26+
updated_at = NOW(),
27+
deleted_at = NULL
28+
`)
29+
30+
return rawRequest.ToSql()
31+
}
32+
33+
func prepareFindClientsByEmailsQuery(emails []string) (string, []interface{}, error) {
34+
psqlSq := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
35+
36+
st, err := stommer.New(clientModel{})
37+
if err != nil {
38+
return "", []interface{}{}, err
39+
}
40+
41+
rawRequest := psqlSq.Select(st.Columns...).
42+
From(tableClients).
43+
Where(sq.Eq{
44+
"email": emails,
45+
"deleted_at": nil,
46+
})
47+
48+
return rawRequest.ToSql()
49+
}
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package clients
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"rebrainme/gotest/internal/entities"
8+
)
9+
10+
func (r *repositoryDB) InsertOrUpdateClient(ctx context.Context, client entities.Client) error {
11+
query, args, err := prepareInsertOrUpdateClientQuery(buildClientModel(client))
12+
if err != nil {
13+
return fmt.Errorf("failed to prepare InsertOrUpdateClient query: %w", err)
14+
}
15+
16+
if _, err := r.conn.ExecContext(ctx, query, args...); err != nil {
17+
return fmt.Errorf("failed to execute InsertOrUpdateClient query: %w", err)
18+
}
19+
20+
return nil
21+
}
22+
23+
func (r *repositoryDB) FindClientsByEmails(ctx context.Context, emails []string) (result []entities.Client, err error) {
24+
query, args, err := prepareFindClientsByEmailsQuery(emails)
25+
if err != nil {
26+
return result, fmt.Errorf("failed to prepare FindClientsByEmails query: %w", err)
27+
}
28+
29+
rows, err := r.conn.QueryxContext(ctx, query, args...)
30+
if err != nil {
31+
return result, fmt.Errorf("failed to execute FindClientsByEmails query: %w", err)
32+
}
33+
defer func() {
34+
if closeErr := rows.Close(); closeErr != nil && err == nil {
35+
err = closeErr
36+
}
37+
}()
38+
39+
for rows.Next() {
40+
var model clientModel
41+
if err := rows.StructScan(&model); err != nil {
42+
return result, fmt.Errorf("failed to scan row to struct")
43+
}
44+
result = append(result, buildClientEntity(model))
45+
}
46+
47+
if err := rows.Err(); err != nil {
48+
return result, fmt.Errorf("unable to scan all out of FindClientsByEmails query: %w", err)
49+
}
50+
51+
return
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package clients
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
10+
"rebrainme/gotest/internal/entities"
11+
"rebrainme/gotest/test/fixtures"
12+
)
13+
14+
func TestRepositoryDB_InsertOrUpdateClient(t *testing.T) {
15+
if testing.Short() {
16+
t.Skip(skipTestMessage)
17+
}
18+
19+
var (
20+
req = require.New(t)
21+
ctx = context.Background()
22+
repo, psqlClient = getTestRepoAndClient(req)
23+
)
24+
defer func() {
25+
req.NoError(psqlClient.Close())
26+
}()
27+
28+
fixtures.ExecuteFixture(psqlClient, fixtures.CleanupFixture{})
29+
30+
test := func(client entities.Client) func(t *testing.T) {
31+
return func(t *testing.T) {
32+
err := repo.InsertOrUpdateClient(ctx, client)
33+
req.NoError(err)
34+
35+
var model clientModel
36+
row := psqlClient.QueryRowx(fmt.Sprintf("SELECT * FROM %s WHERE email = $1", tableClients), client.Email)
37+
err = row.StructScan(&model)
38+
req.NoError(err)
39+
req.Equal(client, buildClientEntity(model))
40+
}
41+
}
42+
43+
clientEmail := "[email protected]"
44+
45+
t.Run("insert new client", test(entities.Client{Email: clientEmail, FullName: "John Doe", City: "Moscow"}))
46+
t.Run("update exist client", test(entities.Client{Email: clientEmail, FullName: "John Braun", City: "NY"}))
47+
}
48+
49+
func TestRepositoryDB_FindClientsByEmails(t *testing.T) {
50+
if testing.Short() {
51+
t.Skip(skipTestMessage)
52+
}
53+
54+
var (
55+
req = require.New(t)
56+
ctx = context.Background()
57+
repo, psqlClient = getTestRepoAndClient(req)
58+
)
59+
defer func() {
60+
req.NoError(psqlClient.Close())
61+
}()
62+
63+
fixtures.ExecuteFixture(psqlClient, fixtures.CleanupFixture{})
64+
65+
clients := []entities.Client{
66+
{Email: "[email protected]", FullName: "John Doe", City: "Moscow"},
67+
{Email: "[email protected]", FullName: "Alan Walker", City: "NY"},
68+
{Email: "[email protected]", FullName: "Bob Marley", City: "London"},
69+
}
70+
for _, client := range clients {
71+
err := repo.InsertOrUpdateClient(ctx, client)
72+
req.NoError(err)
73+
}
74+
75+
_, err := psqlClient.Exec(fmt.Sprintf("UPDATE %s SET deleted_at = NOW() WHERE email = $1", tableClients), clients[2].Email)
76+
req.NoError(err)
77+
78+
test := func(emails []string, want []entities.Client) func(t *testing.T) {
79+
return func(t *testing.T) {
80+
actual, err := repo.FindClientsByEmails(ctx, emails)
81+
req.NoError(err)
82+
req.Len(actual, len(want))
83+
req.Equal(want, actual)
84+
}
85+
}
86+
87+
t.Run("get exists clients", test([]string{clients[0].Email, clients[1].Email}, []entities.Client{clients[0], clients[1]}))
88+
t.Run("get deleted client", test([]string{clients[2].Email}, []entities.Client(nil)))
89+
t.Run("get all clients", test([]string{clients[0].Email, clients[1].Email, clients[2].Email}, []entities.Client{clients[0], clients[1]}))
90+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package clients
2+
3+
import (
4+
"github.com/jmoiron/sqlx"
5+
"github.com/stretchr/testify/require"
6+
7+
"rebrainme/gotest/internal/config"
8+
"rebrainme/gotest/internal/system/database/psql"
9+
)
10+
11+
const skipTestMessage = "Skip test. please up local database for this test"
12+
13+
func getTestRepoAndClient(req *require.Assertions) (Repository, *sqlx.DB) {
14+
cfg, err := config.Init()
15+
req.NoError(err)
16+
17+
postgresClient, err := psql.New(cfg.PSQL)
18+
req.NoError(err)
19+
20+
repo := NewRepository(postgresClient)
21+
22+
return repo, postgresClient.GetConnection()
23+
}

0 commit comments

Comments
 (0)