Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix metrics test by rekby #48

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
test server for first iteration
rekby committed Apr 9, 2023
commit ec5da7437d30c711248208ba6a3a4cd1635fe7d8
21 changes: 21 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "test server",
"type":"go",
"request": "launch",
"mode": "test",
"program": "/Users/rekby/projects/praktikum/go-autotests/cmd/metricstest/iteration1_test.go",
"args": [
// "-test.v",
"-test.run=^TestIteration1$",
"--binary-path=/Users/rekby/projects/rekby/mymetrics/cmd/server/server",
"--server-port=8080"
]
}
]
}
70 changes: 56 additions & 14 deletions cmd/metricstest/fixtures_test.go
Original file line number Diff line number Diff line change
@@ -4,26 +4,30 @@ import (
"fmt"
"net"
"os"
"path/filepath"
"strconv"
"syscall"
"testing"
"time"

"github.com/Yandex-Practicum/go-autotests/internal/fork"
"github.com/go-resty/resty/v2"
"github.com/rekby/fixenv"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)

const (
startProcessTimeout = time.Second * 10
checkPortInterval = time.Millisecond * 100
checkPortInterval = time.Millisecond * 10
)

type Env struct {
fixenv.EnvT
assert.Assertions
Ctx context.Context
Ctx context.Context
Require require.Assertions

t testing.TB
}
@@ -35,28 +39,34 @@ func New(t testing.TB) *Env {
res := Env{
EnvT: *fixenv.NewEnv(t),
Assertions: *assert.New(t),
Require: *require.New(t),
t: t,
Ctx: ctx,
}
return &res
}

func (e *Env) Fatalf(format string, args ...any) {
e.t.Helper()
e.T().Fatalf(format, args...)
}

func (e *Env) Logf(format string, args ...any) {
e.t.Helper()
e.t.Logf(format, args...)
}

func

func ExistPath(e *Env, filePath string) string {
return fixenv.Cache(&e.EnvT, filePath, &fixenv.FixtureOptions{
Scope: fixenv.ScopePackage,
}, func() (string, error) {
e.Logf("Проверяю наличие файла: %q", filePath)
_, err := os.Stat(filePath)
absFilePath, err := filepath.Abs(filePath)
if err != nil {
return "", err
}
e.Logf("Проверяю наличие файла: %q (%q)", absFilePath, filePath)

_, err = os.Stat(filePath)
if err != nil {
return "", err
}
@@ -81,7 +91,9 @@ func ServerHost(e *Env) string {
}

func ServerPort(e *Env) int {
return flagServerPort
return fixenv.Cache(e, nil, nil, func() (int, error) {
return strconv.Atoi(flagServerPort)
})
}

func StartProcess(e *Env, name string, command string, args ...string) *fork.BackgroundProcess {
@@ -97,11 +109,18 @@ func StartProcess(e *Env, name string, command string, args ...string) *fork.Bac

cleanup := func() {
e.Logf("Останавливаю %q: %q %#v", name, command, args)
exitCode, err := res.Stop()
if err != nil {
e.Fatalf("Не получилось остановить процесс: %+v", err)
exitCode, stopErr := res.Stop(syscall.SIGINT, syscall.SIGKILL)

stdOut := string(res.Stdout(context.Background()))
stdErr := string(res.Stderr(context.Background()))

e.Logf("stdout:\n%v", stdOut)
e.Logf("stderr:\n%v", stdErr)

if stopErr != nil {
e.Fatalf("Не получилось остановить процесс: %+v", stopErr)
}
if exitCode != 0 {
if exitCode > 0 {
e.Logf("Ненулевой код возврата: %v", exitCode)
}
}
@@ -118,24 +137,47 @@ func StartProcessWhichListenPort(e *Env, host string, port int, name string, com

address := fmt.Sprintf("%v:%v", host, port)
dialer := net.Dialer{}
e.Logf("Пробую подключиться на %q...", address)

for {
time.Sleep(checkPortInterval)
e.Logf("Пробую подключиться на %q...", address)
conn, err := dialer.DialContext(ctx, "tcp", address)
if err == nil {
e.Logf("Закрываю успешное подключение")
err = conn.Close()
return process, err
}
if ctx.Err() != nil {
e.Fatalf("Ошибка подлючения: %+v", err)
return nil, err
}
}
})
}

func RestyClient(e *Env, host string) *resty.Client {
return fixenv.Cache[*resty.Client](e, host, nil, func() (*resty.Client, error) {
return resty.New().SetHostURL(host).SetRedirectPolicy(resty.NoRedirectPolicy()), nil
return fixenv.Cache(e, host, nil, func() (*resty.Client, error) {
return resty.
New().
SetDebug(true).
SetBaseURL(host).
SetRedirectPolicy(resty.NoRedirectPolicy()).
SetLogger(restyLogger{e}), nil
})
}

type restyLogger struct {
e *Env
}

func (l restyLogger) Errorf(format string, v ...interface{}) {
l.e.Logf("RESTY ERROR: "+format, v...)
}

func (l restyLogger) Warnf(format string, v ...interface{}) {
l.e.Logf("resty warn: "+format, v...)
}

func (l restyLogger) Debugf(format string, v ...interface{}) {
l.e.Logf("resty: "+format, v...)
}
14 changes: 10 additions & 4 deletions cmd/metricstest/flags.go
Original file line number Diff line number Diff line change
@@ -2,14 +2,20 @@ package main

import (
"flag"
"strconv"
)

const (
serverDefaultHost = "localhost"
serverDefaultPort = 8080
)

var (
flagAgentBinaryPath string
flagServerBinaryPath string
flagTargetSourcePath string
flagServerHost string
flagServerPort int
flagServerPort string
flagServerBaseURL string
flagFileStoragePath string
flagDatabaseDSN string
@@ -18,10 +24,10 @@ var (

func init() {
flag.StringVar(&flagAgentBinaryPath, "agent-binary-path", "", "path to target agent binary")
flag.StringVar(&flagServerBinaryPath, "binary-path", "", "path to target server binary")
flag.StringVar(&flagServerBinaryPath, "binary-path", "cmd/server/server", "path to target server binary")
flag.StringVar(&flagTargetSourcePath, "source-path", "", "path to target server source")
flag.StringVar(&flagServerHost, "server-host", "localhost", "host of target address")
flag.IntVar(&flagServerPort, "server-port", 8080, "port of target address")
flag.StringVar(&flagServerHost, "server-host", serverDefaultHost, "host of target address")
flag.StringVar(&flagServerPort, "server-port", strconv.Itoa(serverDefaultPort), "port of target address")
flag.StringVar(&flagServerBaseURL, "server-base-url", "", "base URL of target address")
flag.StringVar(&flagFileStoragePath, "file-storage-path", "", "path to persistent file storage")
flag.StringVar(&flagDatabaseDSN, "database-dsn", "", "connection string to database")
3 changes: 3 additions & 0 deletions cmd/metricstest/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package main

type StartedServerHost string
314 changes: 80 additions & 234 deletions cmd/metricstest/iteration1_test.go
Original file line number Diff line number Diff line change
@@ -1,266 +1,112 @@
package main

import (
"context"
"errors"
"net/http"
"os"
"syscall"
"testing"
"time"

"github.com/go-resty/resty/v2"
"github.com/stretchr/testify/suite"

"github.com/Yandex-Practicum/go-autotests/internal/fork"
)

func TestIteration1(t *testing.T) {
t.Run("Server", func(t *testing.T) {
t.Run("TestCounterHandlers", func(t *testing.T) {
t.Run("update", func(t *testing.T) {
e := New(t)
c := Client1(e)
req := c.R()
resp, err := req.Post("update/gauge/testGauge/100")
e.NoError(err, "Ошибка при выполнении запроса")

})
t.Run("TestCounterHandlers", func(t *testing.T) {
t.Run("ok", func(t *testing.T) {
e := New(t)
c := DefaultServer(e)
req := c.R()
resp, err := req.Post("update/counter/testGauge/100")
e.Require.NoError(err, "Ошибка при выполнении запроса")
e.Equal(http.StatusOK, resp.StatusCode(), "При добавлении нового значения сервер должен возвращать код 200 (http.StatusOK)")

req = c.R()
resp, err = req.Post("update/counter/testGauge/101")
e.Require.NoError(err, "Ошибка при выполнении запроса")
e.Equal(http.StatusOK, resp.StatusCode(), "При обновлении значения сервер должен возвращать код 200 (http.StatusOK)")
})
})
}

func Client1(e *Env) *resty.Client {
StartProcessWhichListenPort(e, ServerHost(e), ServerPort(e), "metric server", ServerFilePath(e))
return RestyClient(e, ServerAddress(e))
}

type Iteration1Suite struct {
suite.Suite

serverAddress string
serverProcess *fork.BackgroundProcess
}

func (suite *Iteration1Suite) SetupSuite() {
// check required flags
suite.Require().NotEmpty(flagServerBinaryPath, "-binary-path non-empty flag required")

suite.serverAddress = "http://localhost:8080"

envs := append(os.Environ(), []string{
"RESTORE=false",
}...)
p := fork.NewBackgroundProcess(context.Background(), flagServerBinaryPath,
fork.WithEnv(envs...),
)

ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()

err := p.Start(ctx)
if err != nil {
suite.T().Errorf("Невозможно запустить процесс командой %s: %s. Переменные окружения: %+v", p, err, envs)
return
}

port := "8080"
err = p.WaitPort(ctx, "tcp", port)
if err != nil {
suite.T().Errorf("Не удалось дождаться пока порт %s станет доступен для запроса: %s", port, err)
return
}

suite.serverProcess = p
}

func (suite *Iteration1Suite) TearDownSuite() {
if suite.serverProcess == nil {
return
}

exitCode, err := suite.serverProcess.Stop(syscall.SIGINT, syscall.SIGKILL)
if err != nil {
if errors.Is(err, os.ErrProcessDone) {
return
}
suite.T().Logf("Не удалось остановить процесс с помощью сигнала ОС: %s", err)
return
}

if exitCode > 0 {
suite.T().Logf("Процесс завершился с не нулевым статусом %d", exitCode)
}

// try to read stdout/stderr
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()

out := suite.serverProcess.Stderr(ctx)
if len(out) > 0 {
suite.T().Logf("Получен STDERR лог процесса:\n\n%s", string(out))
}
out = suite.serverProcess.Stdout(ctx)
if len(out) > 0 {
suite.T().Logf("Получен STDOUT лог процесса:\n\n%s", string(out))
}
}

// TestHandlers проверяет
// сервер успешно стартует и открывет tcp порт 8080 на 127.0.0.1
// обработку POST запросов вида: ?id=<ID>&value=<VALUE>&type=<gauge|counter>
// а так же негативкейсы, запросы в которых отсутствуют id, value и задан не корректный type
func (suite *Iteration1Suite) TestGaugeHandlers() {
// create HTTP client without redirects support
errRedirectBlocked := errors.New("HTTP redirect blocked")
redirPolicy := resty.RedirectPolicyFunc(func(_ *http.Request, _ []*http.Request) error {
return errRedirectBlocked
})

httpc := resty.New().
SetHostURL(suite.serverAddress).
SetRedirectPolicy(redirPolicy)

suite.Run("update", func() {
req := httpc.R()
resp, err := req.Post("update/gauge/testGauge/100")

noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с обновлением gauge")

validStatus := suite.Assert().Equalf(http.StatusOK, resp.StatusCode(),
"Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL)

if !noRespErr || !validStatus {
dump := dumpRequest(req.RawRequest, true)
suite.T().Logf("Оригинальный запрос:\n\n%s", dump)
}
})

suite.Run("without id", func() {
req := httpc.R()
resp, err := req.Post("update/gauge/")

noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с обновлением gauge")
t.Run("without-id", func(t *testing.T) {
e := New(t)
c := DefaultServer(e)
req := c.R()

validStatus := suite.Assert().Equalf(http.StatusNotFound, resp.StatusCode(),
"Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL)

if !noRespErr || !validStatus {
dump := dumpRequest(req.RawRequest, true)
suite.T().Logf("Оригинальный запрос:\n\n%s", dump)
}
})

suite.Run("invalid value", func() {
req := httpc.R()
resp, err := req.Post("update/gauge/testGauge/none")

noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с обновлением gauge")
resp, err := req.Post("update/counter/testGauge/")
e.Require.NoError(err, "Ошибка при выполнении запроса")
e.Contains([]int{http.StatusBadRequest, http.StatusNotFound}, resp.StatusCode(),
"При попытке обновления значения без ID - сервер должен вернуть ошибку 400 или 404 (http.StatusBadRequest, http.StatusNotFound).")
})

validStatus := suite.Assert().Equalf(http.StatusBadRequest, resp.StatusCode(),
"Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL)
t.Run("bad value", func(t *testing.T) {
e := New(t)
c := DefaultServer(e)
req := c.R()

if !noRespErr || !validStatus {
dump := dumpRequest(req.RawRequest, true)
suite.T().Logf("Оригинальный запрос:\n\n%s", dump)
}
})
}
resp, err := req.Post("update/gauge/testGauge/bad-value")
e.Require.NoError(err, "Ошибка при выполнении запроса")
e.Equal(http.StatusBadRequest, resp.StatusCode(), "При получении неправильного значения сервер должен вернуть ошибку 400 (http.StatusBadRequest)")
})

func (suite *Iteration1Suite) TestCounterHandlers() {
// create HTTP client without redirects support
errRedirectBlocked := errors.New("HTTP redirect blocked")
redirPolicy := resty.RedirectPolicyFunc(func(_ *http.Request, _ []*http.Request) error {
return errRedirectBlocked
})

httpc := resty.New().
SetHostURL(suite.serverAddress).
SetRedirectPolicy(redirPolicy)
t.Run("TestGaugeHandlers", func(t *testing.T) {
t.Run("ok", func(t *testing.T) {
e := New(t)
c := DefaultServer(e)
req := c.R()
resp, err := req.Post("update/gauge/testGauge/100")
e.Require.NoError(err, "Ошибка при выполнении запроса")
e.Equal(http.StatusOK, resp.StatusCode(), "При добавлении нового значения сервер должен возвращать код http.StatusOK (200)")

req = c.R()
resp, err = req.Post("update/gauge/testGauge/101")
e.Require.NoError(err, "Ошибка при выполнении запроса")
e.Equal(http.StatusOK, resp.StatusCode(), "При обновлении значения сервер должен возвращать код http.StatusOK (200)")
})

suite.Run("update", func() {
req := httpc.R()
resp, err := req.Post("update/counter/testCounter/100")
t.Run("without-id", func(t *testing.T) {
e := New(t)
c := DefaultServer(e)
req := c.R()

noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с обновлением counter")
resp, err := req.Post("update/gauge/testGauge/")
e.Require.NoError(err, "Ошибка при выполнении запроса")
e.Contains([]int{http.StatusBadRequest, http.StatusNotFound}, resp.StatusCode(),
"При попытке обновления значения без ID - сервер должен вернуть ошибку http.StatusBadRequest или http.StatusNotFound (400, 404).")
})

validStatus := suite.Assert().Equalf(http.StatusOK, resp.StatusCode(),
"Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL)
t.Run("bad value", func(t *testing.T) {
e := New(t)
c := DefaultServer(e)
req := c.R()

if !noRespErr || !validStatus {
dump := dumpRequest(req.RawRequest, true)
suite.T().Logf("Оригинальный запрос:\n\n%s", dump)
}
resp, err := req.Post("update/gauge/testGauge/bad-value")
e.Require.NoError(err, "Ошибка при выполнении запроса")
e.Equal(http.StatusBadRequest, resp.StatusCode(), "При получении неправильного значения сервер должен вернуть ошибку http.StatusBadRequest (400)")
})
})

suite.Run("without id", func() {
req := httpc.R()
resp, err := req.Post("update/counter/")
t.Run("unexpected path", func(t *testing.T) {
e := New(t)
c := DefaultServer(e)
req := c.R()

noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с обновлением counter")

validStatus := suite.Assert().Equalf(http.StatusNotFound, resp.StatusCode(),
"Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL)

if !noRespErr || !validStatus {
dump := dumpRequest(req.RawRequest, true)
suite.T().Logf("Оригинальный запрос:\n\n%s", dump)
for _, path := range []string{"unknown-path", "unknown-path/gauge/testGauge/100"} {
resp, err := req.Post(path)
e.Require.NoError(err, "Ошибка при выполнении запроса")
e.Equal(http.StatusNotFound, resp.StatusCode(), "При получении запроса к неизвестному пути сервер должен возвращать ошибку http.StatusNotFound (404)")
}
})

suite.Run("invalid value", func() {
req := httpc.R()
resp, err := req.Post("update/counter/testCounter/none")

noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с обновлением counter")

validStatus := suite.Assert().Equalf(http.StatusBadRequest, resp.StatusCode(),
"Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL)

if !noRespErr || !validStatus {
dump := dumpRequest(req.RawRequest, true)
suite.T().Logf("Оригинальный запрос:\n\n%s", dump)
}
t.Run("unknown-metric-type", func(t *testing.T) {
e := New(t)
c := DefaultServer(e)
req := c.R()
resp, err := req.Post("update/unknown/testGauge/100")
e.Require.NoError(err, "Ошибка при выполнении запроса")
e.Contains([]int{http.StatusBadRequest, http.StatusNotFound}, resp.StatusCode(),
"При попытке обновления метрики неизвестного типа сервер должен вернуть ошибку http.StatusBadRequest или http.StatusNotFound (400, 404)")
})
}

func (suite *Iteration1Suite) TestUnknownHandlers() {
errRedirectBlocked := errors.New("HTTP redirect blocked")
redirPolicy := resty.RedirectPolicyFunc(func(_ *http.Request, _ []*http.Request) error {
return errRedirectBlocked
})

httpc := resty.New().
SetHostURL(suite.serverAddress).
SetRedirectPolicy(redirPolicy)

suite.Run("update invalid type", func() {
req := httpc.R()
resp, err := req.Post("update/unknown/testCounter/100")

noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с не корректным типом метрики")

validStatus := suite.Assert().Equalf(http.StatusNotImplemented, resp.StatusCode(),
"Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL)

if !noRespErr || !validStatus {
dump := dumpRequest(req.RawRequest, true)
suite.T().Logf("Оригинальный запрос:\n\n%s", dump)
}
})

suite.Run("update invalid method", func() {
req := httpc.R()
resp, err := req.Post("updater/counter/testCounter/100")

noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с не корректным типом метрики")

validStatus := suite.Assert().Equalf(http.StatusNotFound, resp.StatusCode(),
"Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL)

if !noRespErr || !validStatus {
dump := dumpRequest(req.RawRequest, true)
suite.T().Logf("Оригинальный запрос:\n\n%s", dump)
}
})
func DefaultServer(e *Env) *resty.Client {
StartProcessWhichListenPort(e, serverDefaultHost, serverDefaultPort, "metric server", ServerFilePath(e))
return RestyClient(e, ServerAddress(e))
}