test(tester): fix tests

This commit is contained in:
Vyacheslav1557 2025-04-12 00:12:28 +05:00
parent 2ab7a16ddf
commit 4fb0b80f24
6 changed files with 1500 additions and 181 deletions

View file

@ -2,10 +2,9 @@ package repository
import (
"context"
"fmt"
"git.sch9.ru/new_gate/ms-tester/internal/models"
sq "github.com/Masterminds/squirrel"
"github.com/jmoiron/sqlx"
"strings"
)
type ContestRepository struct {
@ -172,7 +171,7 @@ const (
func (r *ContestRepository) ListContests(ctx context.Context, filter models.ContestsFilter) (*models.ContestsList, error) {
const op = "ContestRepository.ReadTasks"
var contests []*models.ContestsListItem
contests := make([]*models.ContestsListItem, 0)
query := r.db.Rebind(readContestsListQuery)
err := r.db.SelectContext(ctx, &contests, query, filter.PageSize, filter.Offset())
if err != nil {
@ -207,7 +206,7 @@ func (r *ContestRepository) ListParticipants(ctx context.Context, filter models.
filter.PageSize = 1
}
var participants []*models.ParticipantsListItem
participants := make([]*models.ParticipantsListItem, 0)
query := r.db.Rebind(readParticipantsListQuery)
err := r.db.SelectContext(ctx, &participants, query, filter.ContestId, filter.PageSize, filter.Offset())
if err != nil {
@ -313,93 +312,94 @@ func (r *ContestRepository) CreateSolution(ctx context.Context, creation *models
return id, nil
}
// buildListSolutionsQueries builds two SQL queries: one for selecting solutions
// and another for counting them. The first query selects all columns that are
// needed for the solutions list, including the task and contest titles, and
// the participant name. The second query counts the number of solutions that
// match the filter.
//
// The caller is responsible for executing the queries and processing the
// results.
func buildListSolutionsQueries(filter models.SolutionsFilter) (sq.SelectBuilder, sq.SelectBuilder) {
columns := []string{
"s.id",
"s.participant_id",
"p2.name AS participant_name",
"s.state",
"s.score",
"s.penalty",
"s.time_stat",
"s.memory_stat",
"s.language",
"s.task_id",
"t.position AS task_position",
"p.title AS task_title",
"t.contest_id",
"c.title",
"s.updated_at",
"s.created_at",
}
qb := sq.Select(columns...).
From("solutions s").
LeftJoin("tasks t ON s.task_id = t.id").
LeftJoin("problems p ON t.problem_id = p.id").
LeftJoin("contests c ON t.contest_id = c.id").
LeftJoin("participants p2 ON s.participant_id = p2.id")
if filter.ContestId != nil {
qb = qb.Where("s.contest_id = ?", *filter.ContestId)
}
if filter.ParticipantId != nil {
qb = qb.Where("s.participant_id = ?", *filter.ParticipantId)
}
if filter.TaskId != nil {
qb = qb.Where("s.task_id = ?", *filter.TaskId)
}
if filter.Language != nil {
qb = qb.Where("s.language = ?", *filter.Language)
}
if filter.State != nil {
qb = qb.Where("s.state = ?", *filter.State)
}
countQb := sq.Select("COUNT(*)").FromSelect(qb, "sub")
if filter.Order != nil && *filter.Order < 0 {
qb = qb.OrderBy("s.id DESC")
}
qb = qb.Limit(uint64(filter.PageSize)).Offset(uint64(filter.Offset()))
return qb, countQb
}
func (r *ContestRepository) ListSolutions(ctx context.Context, filter models.SolutionsFilter) (*models.SolutionsList, error) {
const op = "ContestRepository.ListSolutions"
baseQuery := `
SELECT s.id,
baseQb, countQb := buildListSolutionsQueries(filter)
s.participant_id,
p2.name as participant_name,
s.state,
s.score,
s.penalty,
s.time_stat,
s.memory_stat,
s.language,
s.task_id,
t.position as task_position,
p.title as task_title,
t.contest_id,
c.title,
s.updated_at,
s.created_at
FROM solutions s
LEFT JOIN tasks t ON s.task_id = t.id
LEFT JOIN problems p ON t.problem_id = p.id
LEFT JOIN contests c ON t.contest_id = c.id
LEFT JOIN participants p2 on s.participant_id = p2.id
WHERE 1=1
`
var conditions []string
var args []interface{}
if filter.ContestId != nil {
conditions = append(conditions, "s.contest_id = ?")
args = append(args, *filter.ContestId)
query, args, err := countQb.ToSql()
if err != nil {
return nil, handlePgErr(err, op)
}
if filter.ParticipantId != nil {
conditions = append(conditions, "s.participant_id = ?")
args = append(args, *filter.ParticipantId)
}
if filter.TaskId != nil {
conditions = append(conditions, "s.task_id = ?")
args = append(args, *filter.TaskId)
}
if filter.Language != nil {
conditions = append(conditions, "s.language = ?")
args = append(args, *filter.Language)
}
if filter.State != nil {
conditions = append(conditions, "s.state = ?")
args = append(args, *filter.State)
}
if len(conditions) > 0 {
baseQuery += " AND " + strings.Join(conditions, " AND ")
}
if filter.Order != nil {
orderDirection := "ASC"
if *filter.Order < 0 {
orderDirection = "DESC"
}
baseQuery += fmt.Sprintf(" ORDER BY s.id %s", orderDirection)
}
countQuery := "SELECT COUNT(*) FROM (" + baseQuery + ") as count_table"
var totalCount int32
err := r.db.QueryRowxContext(ctx, r.db.Rebind(countQuery), args...).Scan(&totalCount)
err = r.db.QueryRowxContext(ctx, r.db.Rebind(query), args...).Scan(&totalCount)
if err != nil {
return nil, handlePgErr(err, op)
}
offset := (filter.Page - 1) * filter.PageSize
baseQuery += " LIMIT ? OFFSET ?"
args = append(args, filter.PageSize, offset)
rows, err := r.db.QueryxContext(ctx, r.db.Rebind(baseQuery), args...)
query, args, err = baseQb.ToSql()
if err != nil {
return nil, handlePgErr(err, op)
}
rows, err := r.db.QueryxContext(ctx, r.db.Rebind(query), args...)
if err != nil {
return nil, handlePgErr(err, op)
}
defer rows.Close()
var solutions []*models.SolutionsListItem
solutions := make([]*models.SolutionsListItem, 0)
for rows.Next() {
var solution models.SolutionsListItem
err = rows.StructScan(&solution)
@ -551,7 +551,7 @@ WITH Attempts AS (
MIN(CASE WHEN s.state = 5 THEN s.penalty END) as success_penalty
FROM solutions s
JOIN tasks t ON t.id = s.task_id
WHERE t.contest_id = :contest_id
WHERE t.contest_id = ?
GROUP BY s.participant_id, s.task_id
)
SELECT
@ -559,11 +559,11 @@ SELECT
p.name,
COUNT(DISTINCT CASE WHEN a.success_penalty IS NOT NULL THEN a.task_id END) as solved_in_total,
COALESCE(SUM(CASE WHEN a.success_penalty IS NOT NULL
THEN a.failed_attempts * :penalty + a.success_penalty
THEN a.failed_attempts * ? + a.success_penalty
ELSE 0 END), 0) as penalty_in_total
FROM participants p
LEFT JOIN Attempts a ON a.participant_id = p.id
WHERE p.contest_id = :contest_id
WHERE p.contest_id = ?
GROUP BY p.id, p.name
`
)
@ -596,10 +596,7 @@ func (r *ContestRepository) ReadMonitor(ctx context.Context, contestId int32) (*
penalty := int32(20) // FIXME
namedQuery := r.db.Rebind(participantsQuery)
rows3, err := r.db.NamedQueryContext(ctx, namedQuery, map[string]interface{}{
"contest_id": contestId,
"penalty": penalty,
})
rows3, err := r.db.QueryxContext(ctx, namedQuery, contestId, penalty, contestId)
if err != nil {
return nil, handlePgErr(err, op)
}

File diff suppressed because it is too large Load diff

View file

@ -2,57 +2,153 @@ package repository
import (
"context"
"database/sql"
"git.sch9.ru/new_gate/ms-tester/internal/models"
"github.com/DATA-DOG/go-sqlmock"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"testing"
)
func TestProblemRepository_CreateProblem(t *testing.T) {
t.Parallel()
type problemTestFixture struct {
db *sql.DB
sqlxDB *sqlx.DB
mock sqlmock.Sqlmock
problemRepo *ProblemRepository
}
func newProblemTestFixture(t *testing.T) *problemTestFixture {
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
require.NoError(t, err)
defer db.Close()
sqlxDB := sqlx.NewDb(db, "sqlmock")
defer sqlxDB.Close()
repo := NewProblemRepository(sqlxDB)
problemRepo := NewProblemRepository(sqlxDB, zap.NewNop())
return &problemTestFixture{
db: db,
sqlxDB: sqlxDB,
mock: mock,
problemRepo: repo,
}
}
// cleanup closes database connections
func (tf *problemTestFixture) cleanup() {
tf.db.Close()
tf.sqlxDB.Close()
}
func TestProblemRepository_CreateProblem(t *testing.T) {
tf := newProblemTestFixture(t)
defer tf.cleanup()
t.Run("valid problem creation", func(t *testing.T) {
title := "Problem title"
title := "Test Problem"
rows := sqlmock.NewRows([]string{"id"}).AddRow(1)
mock.ExpectQuery(sqlxDB.Rebind(createProblemQuery)).WithArgs(title).WillReturnRows(rows)
tf.mock.ExpectQuery(tf.sqlxDB.Rebind(createProblemQuery)).
WithArgs(title).
WillReturnRows(rows)
id, err := problemRepo.CreateProblem(context.Background(), title)
id, err := tf.problemRepo.CreateProblem(context.Background(), tf.problemRepo.DB(), title)
require.NoError(t, err)
require.Equal(t, int32(1), id)
})
}
func TestProblemRepository_ReadProblemById(t *testing.T) {
tf := newProblemTestFixture(t)
defer tf.cleanup()
t.Run("valid problem read", func(t *testing.T) {
id := int32(1)
rows := sqlmock.NewRows([]string{"id", "title"}).
AddRow(1, "Test Problem")
tf.mock.ExpectQuery(tf.sqlxDB.Rebind(readProblemQuery)).
WithArgs(id).
WillReturnRows(rows)
problem, err := tf.problemRepo.ReadProblemById(context.Background(), tf.problemRepo.DB(), id)
require.NoError(t, err)
require.NotNil(t, problem)
require.Equal(t, int32(1), problem.Id)
require.Equal(t, "Test Problem", problem.Title)
})
}
func TestProblemRepository_DeleteProblem(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()
problemRepo := NewProblemRepository(sqlxDB, zap.NewNop())
tf := newProblemTestFixture(t)
defer tf.cleanup()
t.Run("valid problem deletion", func(t *testing.T) {
id := int32(1)
rows := sqlmock.NewResult(1, 1)
mock.ExpectExec(sqlxDB.Rebind(deleteProblemQuery)).WithArgs(id).WillReturnResult(rows)
tf.mock.ExpectExec(tf.sqlxDB.Rebind(deleteProblemQuery)).
WithArgs(id).
WillReturnResult(rows)
err = problemRepo.DeleteProblem(context.Background(), id)
err := tf.problemRepo.DeleteProblem(context.Background(), tf.problemRepo.DB(), id)
require.NoError(t, err)
})
}
func TestProblemRepository_ListProblems(t *testing.T) {
tf := newProblemTestFixture(t)
defer tf.cleanup()
t.Run("valid problems list", func(t *testing.T) {
filter := models.ProblemsFilter{
Page: 1,
PageSize: 10,
}
listRows := sqlmock.NewRows([]string{"id", "title", "solved_count"}).
AddRow(1, "Problem 1", 5).
AddRow(2, "Problem 2", 3)
countRows := sqlmock.NewRows([]string{"count"}).AddRow(2)
tf.mock.ExpectQuery(tf.sqlxDB.Rebind(ListProblemsQuery)).
WithArgs(filter.PageSize, filter.Offset()).
WillReturnRows(listRows)
tf.mock.ExpectQuery(tf.sqlxDB.Rebind(CountProblemsQuery)).
WillReturnRows(countRows)
result, err := tf.problemRepo.ListProblems(context.Background(), tf.problemRepo.DB(), filter)
require.NoError(t, err)
require.NotNil(t, result)
require.Len(t, result.Problems, 2)
require.Equal(t, int32(1), result.Pagination.Page)
require.Equal(t, int32(1), result.Pagination.Total)
})
}
func TestProblemRepository_UpdateProblem(t *testing.T) {
tf := newProblemTestFixture(t)
defer tf.cleanup()
t.Run("valid problem update", func(t *testing.T) {
id := int32(1)
problemUpdate := models.ProblemUpdate{
Title: sp("Updated Title"),
TimeLimit: i32p(1000),
MemoryLimit: i32p(256),
}
rows := sqlmock.NewResult(1, 1)
tf.mock.ExpectExec(tf.sqlxDB.Rebind(UpdateProblemQuery)).
WithArgs(
problemUpdate.Title,
problemUpdate.TimeLimit,
problemUpdate.MemoryLimit,
nil, nil, nil, nil, nil,
nil, nil, nil, nil, nil,
id,
).
WillReturnResult(rows)
err := tf.problemRepo.UpdateProblem(context.Background(), tf.problemRepo.DB(), id, problemUpdate)
require.NoError(t, err)
})
}