feat(tester): migrate from gRPC to REST

This commit is contained in:
Vyacheslav1557 2025-02-25 18:40:05 +05:00
parent 6613b03b6c
commit a560715ae8
40 changed files with 403 additions and 961 deletions

View file

@ -1,17 +0,0 @@
package contests
import (
"context"
contestv1 "git.sch9.ru/new_gate/ms-tester/pkg/go/gen/proto/contest/v1"
"google.golang.org/protobuf/types/known/emptypb"
)
type ContestHandlers interface {
CreateContest(ctx context.Context, req *contestv1.CreateContestRequest) (*contestv1.CreateContestResponse, error)
ReadContest(ctx context.Context, req *contestv1.ReadContestRequest) (*contestv1.ReadContestResponse, error)
DeleteContest(ctx context.Context, req *contestv1.DeleteContestRequest) (*emptypb.Empty, error)
AddTask(ctx context.Context, req *contestv1.AddTaskRequest) (*contestv1.AddTaskResponse, error)
DeleteTask(ctx context.Context, req *contestv1.DeleteTaskRequest) (*emptypb.Empty, error)
AddParticipant(ctx context.Context, req *contestv1.AddParticipantRequest) (*contestv1.AddParticipantResponse, error)
DeleteParticipant(ctx context.Context, req *contestv1.DeleteParticipantRequest) (*emptypb.Empty, error)
}

View file

@ -1,83 +0,0 @@
package grpc
import (
"context"
"git.sch9.ru/new_gate/ms-tester/internal/contests"
contestv1 "git.sch9.ru/new_gate/ms-tester/pkg/go/gen/proto/contest/v1"
"git.sch9.ru/new_gate/ms-tester/pkg/utils"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/emptypb"
)
type ContestHandlers struct {
contestv1.UnimplementedContestServiceServer
contestUC contests.ContestUseCase
}
func NewContestHandlers(gserver *grpc.Server, contestUC contests.ContestUseCase) {
handlers := &ContestHandlers{contestUC: contestUC}
contestv1.RegisterContestServiceServer(gserver, handlers)
}
func (h *ContestHandlers) CreateContest(ctx context.Context, req *contestv1.CreateContestRequest) (*contestv1.CreateContestResponse, error) {
id, err := h.contestUC.CreateContest(ctx, req.GetTitle())
if err != nil {
return nil, err
}
return &contestv1.CreateContestResponse{Id: id}, nil
}
func (h *ContestHandlers) ReadContest(ctx context.Context, req *contestv1.ReadContestRequest) (*contestv1.ReadContestResponse, error) {
contest, err := h.contestUC.ReadContestById(ctx, req.GetId())
if err != nil {
return nil, err
}
return &contestv1.ReadContestResponse{Contest: &contestv1.ReadContestResponse_Contest{
Id: *contest.Id,
Title: *contest.Title,
CreatedAt: utils.TimestampP(contest.CreatedAt),
UpdatedAt: utils.TimestampP(contest.UpdatedAt),
}}, nil
}
func (h *ContestHandlers) DeleteContest(ctx context.Context, req *contestv1.DeleteContestRequest) (*emptypb.Empty, error) {
err := h.contestUC.DeleteContest(ctx, req.GetId())
if err != nil {
return nil, err
}
return &emptypb.Empty{}, nil
}
func (h *ContestHandlers) AddTask(ctx context.Context, req *contestv1.AddTaskRequest) (*contestv1.AddTaskResponse, error) {
id, err := h.contestUC.AddTask(ctx, req.GetContestId(), req.GetProblemId())
if err != nil {
return nil, err
}
return &contestv1.AddTaskResponse{Id: id}, nil
}
func (h *ContestHandlers) DeleteTask(ctx context.Context, req *contestv1.DeleteTaskRequest) (*emptypb.Empty, error) {
err := h.contestUC.DeleteTask(ctx, req.GetTaskId())
if err != nil {
return nil, err
}
return &emptypb.Empty{}, nil
}
func (h *ContestHandlers) AddParticipant(ctx context.Context, req *contestv1.AddParticipantRequest) (*contestv1.AddParticipantResponse, error) {
id, err := h.contestUC.AddParticipant(ctx, req.GetContestId(), req.GetUserId())
if err != nil {
return nil, err
}
return &contestv1.AddParticipantResponse{Id: id}, nil
}
func (h *ContestHandlers) DeleteParticipant(ctx context.Context, req *contestv1.DeleteParticipantRequest) (*emptypb.Empty, error) {
err := h.contestUC.DeleteParticipant(ctx, req.GetParticipantId())
if err != nil {
return nil, err
}
return &emptypb.Empty{}, nil
}

View file

@ -1,16 +0,0 @@
package contests
import (
"context"
"git.sch9.ru/new_gate/ms-tester/internal/models"
)
type ContestRepository interface {
CreateContest(ctx context.Context, title string) (int32, error)
ReadContestById(ctx context.Context, id int32) (*models.Contest, error)
DeleteContest(ctx context.Context, id int32) error
AddTask(ctx context.Context, contestId int32, taskId int32) (int32, error)
DeleteTask(ctx context.Context, taskId int32) error
AddParticipant(ctx context.Context, contestId int32, userId int32) (int32, error)
DeleteParticipant(ctx context.Context, participantId int32) error
}

View file

@ -1,143 +0,0 @@
package repository
import (
"context"
"errors"
"git.sch9.ru/new_gate/ms-tester/internal/models"
"git.sch9.ru/new_gate/ms-tester/pkg/utils"
"github.com/jackc/pgerrcode"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
type ContestRepository struct {
db *sqlx.DB
logger *zap.Logger
}
func NewContestRepository(db *sqlx.DB, logger *zap.Logger) *ContestRepository {
return &ContestRepository{
db: db,
logger: logger,
}
}
const createContestQuery = "INSERT INTO contest (title) VALUES (?) RETURNING id"
func (r *ContestRepository) CreateContest(ctx context.Context, title string) (int32, error) {
query := r.db.Rebind(createContestQuery)
rows, err := r.db.QueryxContext(ctx, query, title)
if err != nil {
return 0, handlePgErr(err)
}
defer rows.Close()
var id int32
rows.Next()
err = rows.Scan(&id)
if err != nil {
return 0, handlePgErr(err)
}
return id, nil
}
const readContestByIdQuery = "SELECT * from contest WHERE id=? LIMIT 1"
func (r *ContestRepository) ReadContestById(ctx context.Context, id int32) (*models.Contest, error) {
var contest models.Contest
query := r.db.Rebind(readContestByIdQuery)
err := r.db.GetContext(ctx, &contest, query, id)
if err != nil {
return nil, handlePgErr(err)
}
return &contest, nil
}
const deleteContestQuery = "DELETE FROM contest WHERE id=?"
func (r *ContestRepository) DeleteContest(ctx context.Context, id int32) error {
query := r.db.Rebind(deleteContestQuery)
_, err := r.db.ExecContext(ctx, query, id)
if err != nil {
return handlePgErr(err)
}
return nil
}
const addTaskQuery = "INSERT INTO task (problem_id, contest_id, position) VALUES (?, ?,COALESCE(SELECT MAX(position) FROM task WHERE contest_id = ? ,0) + 1) RETURNING id"
func (r *ContestRepository) AddTask(ctx context.Context, contestId int32, problem_id int32) (int32, error) {
query := r.db.Rebind(addTaskQuery)
rows, err := r.db.QueryxContext(ctx, query, problem_id, contestId, contestId)
if err != nil {
return 0, handlePgErr(err)
}
defer rows.Close()
var id int32
rows.Next()
err = rows.Scan(&id)
if err != nil {
return 0, handlePgErr(err)
}
return id, nil
}
const deleteTaskQuery = "DELETE FROM task WHERE id=?"
func (r *ContestRepository) DeleteTask(ctx context.Context, taskId int32) error {
query := r.db.Rebind(deleteTaskQuery)
_, err := r.db.ExecContext(ctx, query, taskId)
if err != nil {
return handlePgErr(err)
}
return nil
}
const addParticipantQuery = "INSERT INTO participant (user_id ,contest_id, name) VALUES (?, ?, ?) RETURNING id"
func (r *ContestRepository) AddParticipant(ctx context.Context, contestId int32, userId int32) (int32, error) {
query := r.db.Rebind(addParticipantQuery)
name := ""
rows, err := r.db.QueryxContext(ctx, query, contestId, userId, name)
if err != nil {
return 0, handlePgErr(err)
}
defer rows.Close()
var id int32
rows.Next()
err = rows.Scan(&id)
if err != nil {
return 0, err
}
return id, nil
}
const deleteParticipantQuery = "DELETE FROM participant WHERE id=?"
func (r *ContestRepository) DeleteParticipant(ctx context.Context, participantId int32) error {
query := r.db.Rebind(deleteParticipantQuery)
_, err := r.db.ExecContext(ctx, query, participantId)
if err != nil {
return handlePgErr(err)
}
return nil
}
func handlePgErr(err error) error {
var pgErr *pgconn.PgError
if !errors.As(err, &pgErr) {
return utils.StorageError(err, utils.ErrUnknown, "unexpected error from postgres")
}
if pgerrcode.IsIntegrityConstraintViolation(pgErr.Code) {
// TODO: probably should specify which constraint
return utils.StorageError(err, utils.ErrConflict, pgErr.Message)
}
if pgerrcode.IsNoData(pgErr.Code) {
return utils.StorageError(err, utils.ErrNotFound, pgErr.Message)
}
return utils.StorageError(err, utils.ErrUnimplemented, "unimplemented error")
}

View file

@ -1,154 +0,0 @@
package repository
import (
"context"
"github.com/DATA-DOG/go-sqlmock"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"testing"
)
func TestContestRepository_CreateContest(t *testing.T) {
t.Parallel()
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
require.NoError(t, err)
defer db.Close()
sqlxDB := sqlx.NewDb(db, "sqlmock")
defer sqlxDB.Close()
contestRepo := NewContestRepository(sqlxDB, zap.NewNop())
t.Run("valid contest creation", func(t *testing.T) {
title := "Contest title"
rows := sqlmock.NewRows([]string{"id"}).AddRow(1)
mock.ExpectQuery(sqlxDB.Rebind(createContestQuery)).WithArgs(title).WillReturnRows(rows)
id, err := contestRepo.CreateContest(context.Background(), title)
require.NoError(t, err)
require.Equal(t, int32(1), id)
})
}
func TestContestRepository_DeleteContest(t *testing.T) {
t.Parallel()
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
require.NoError(t, err)
defer db.Close()
sqlxDB := sqlx.NewDb(db, "sqlmock")
defer sqlxDB.Close()
contestRepo := NewContestRepository(sqlxDB, zap.NewNop())
t.Run("valid contest deletion", func(t *testing.T) {
id := int32(1)
rows := sqlmock.NewResult(1, 1)
mock.ExpectExec(sqlxDB.Rebind(deleteContestQuery)).WithArgs(id).WillReturnResult(rows)
err = contestRepo.DeleteContest(context.Background(), id)
require.NoError(t, err)
})
}
func TestContestRepository_AddTask(t *testing.T) {
t.Parallel()
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
require.NoError(t, err)
defer db.Close()
sqlxDB := sqlx.NewDb(db, "sqlmock")
defer sqlxDB.Close()
contestRepo := NewContestRepository(sqlxDB, zap.NewNop())
t.Run("valid task additional", func(t *testing.T) {
taskId := int32(1)
contestId := int32(1)
rows := sqlmock.NewRows([]string{"id"}).AddRow(1)
mock.ExpectQuery(sqlxDB.Rebind(addTaskQuery)).WithArgs(taskId, contestId, contestId).WillReturnRows(rows)
id, err := contestRepo.AddTask(context.Background(), contestId, taskId)
require.NoError(t, err)
require.Equal(t, int32(1), id)
})
}
func TestContestRepository_DeleteTask(t *testing.T) {
t.Parallel()
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
require.NoError(t, err)
defer db.Close()
sqlxDB := sqlx.NewDb(db, "sqlmock")
defer sqlxDB.Close()
contestRepo := NewContestRepository(sqlxDB, zap.NewNop())
t.Run("valid task deletion", func(t *testing.T) {
id := int32(1)
rows := sqlmock.NewResult(1, 1)
mock.ExpectExec(sqlxDB.Rebind(deleteTaskQuery)).WithArgs(id).WillReturnResult(rows)
err = contestRepo.DeleteTask(context.Background(), id)
require.NoError(t, err)
})
}
func TestContestRepository_AddParticipant(t *testing.T) {
t.Parallel()
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
require.NoError(t, err)
defer db.Close()
sqlxDB := sqlx.NewDb(db, "sqlmock")
defer sqlxDB.Close()
contestRepo := NewContestRepository(sqlxDB, zap.NewNop())
t.Run("valid participant addition", func(t *testing.T) {
contestId := int32(1)
userId := int32(1)
name := ""
rows := sqlmock.NewRows([]string{"id"}).AddRow(1)
mock.ExpectQuery(sqlxDB.Rebind(addParticipantQuery)).WithArgs(contestId, userId, name).WillReturnRows(rows)
id, err := contestRepo.AddParticipant(context.Background(), contestId, userId)
require.NoError(t, err)
require.Equal(t, int32(1), id)
})
}
func TestContestRepository_DeleteParticipant(t *testing.T) {
t.Parallel()
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
require.NoError(t, err)
defer db.Close()
sqlxDB := sqlx.NewDb(db, "sqlmock")
defer sqlxDB.Close()
contestRepo := NewContestRepository(sqlxDB, zap.NewNop())
t.Run("valid participant deletion", func(t *testing.T) {
id := int32(1)
rows := sqlmock.NewResult(1, 1)
mock.ExpectExec(sqlxDB.Rebind(deleteParticipantQuery)).WithArgs(id).WillReturnResult(rows)
err = contestRepo.DeleteParticipant(context.Background(), id)
require.NoError(t, err)
})
}

View file

@ -1,16 +0,0 @@
package contests
import (
"context"
"git.sch9.ru/new_gate/ms-tester/internal/models"
)
type ContestUseCase interface {
CreateContest(ctx context.Context, title string) (int32, error)
ReadContestById(ctx context.Context, id int32) (*models.Contest, error)
DeleteContest(ctx context.Context, id int32) error
AddTask(ctx context.Context, contestId int32, taskId int32) (int32, error)
DeleteTask(ctx context.Context, taskId int32) error
AddParticipant(ctx context.Context, contestId int32, userId int32) (int32, error)
DeleteParticipant(ctx context.Context, participantId int32) error
}

View file

@ -1,47 +0,0 @@
package usecase
import (
"context"
"git.sch9.ru/new_gate/ms-tester/internal/contests"
"git.sch9.ru/new_gate/ms-tester/internal/models"
)
type ContestUseCase struct {
contestRepo contests.ContestRepository
}
func NewContestUseCase(
contestRepo contests.ContestRepository,
) *ContestUseCase {
return &ContestUseCase{
contestRepo: contestRepo,
}
}
func (uc *ContestUseCase) CreateContest(ctx context.Context, title string) (int32, error) {
return uc.contestRepo.CreateContest(ctx, title)
}
func (uc *ContestUseCase) ReadContestById(ctx context.Context, id int32) (*models.Contest, error) {
return uc.contestRepo.ReadContestById(ctx, id)
}
func (uc *ContestUseCase) DeleteContest(ctx context.Context, id int32) error {
return uc.contestRepo.DeleteContest(ctx, id)
}
func (uc *ContestUseCase) AddTask(ctx context.Context, contestId int32, taskId int32) (id int32, err error) {
return uc.contestRepo.AddTask(ctx, contestId, taskId)
}
func (uc *ContestUseCase) DeleteTask(ctx context.Context, taskId int32) error {
return uc.contestRepo.DeleteTask(ctx, taskId)
}
func (uc *ContestUseCase) AddParticipant(ctx context.Context, contestId int32, userId int32) (id int32, err error) {
return uc.contestRepo.AddParticipant(ctx, contestId, userId)
}
func (uc *ContestUseCase) DeleteParticipant(ctx context.Context, participantId int32) error {
return uc.contestRepo.DeleteParticipant(ctx, participantId)
}