feat: merge auth&tester

This commit is contained in:
Vyacheslav1557 2025-04-22 20:44:52 +05:00
parent 0a2dea6c23
commit 441af4c6a2
72 changed files with 4910 additions and 2378 deletions

View file

@ -0,0 +1,175 @@
package repository
import (
"context"
"git.sch9.ru/new_gate/ms-tester/internal/problems"
"git.sch9.ru/new_gate/ms-tester/pkg"
"git.sch9.ru/new_gate/ms-tester/internal/models"
"github.com/jmoiron/sqlx"
)
type Repository struct {
_db *sqlx.DB
}
func NewRepository(db *sqlx.DB) *Repository {
return &Repository{
_db: db,
}
}
func (r *Repository) BeginTx(ctx context.Context) (problems.Tx, error) {
tx, err := r._db.BeginTxx(ctx, nil)
if err != nil {
return nil, err
}
return tx, nil
}
func (r *Repository) DB() problems.Querier {
return r._db
}
const CreateProblemQuery = "INSERT INTO problems (title) VALUES ($1) RETURNING id"
func (r *Repository) CreateProblem(ctx context.Context, q problems.Querier, title string) (int32, error) {
const op = "Repository.CreateProblem"
rows, err := q.QueryxContext(ctx, CreateProblemQuery, title)
if err != nil {
return 0, pkg.HandlePgErr(err, op)
}
defer rows.Close()
var id int32
rows.Next()
err = rows.Scan(&id)
if err != nil {
return 0, pkg.HandlePgErr(err, op)
}
return id, nil
}
const GetProblemByIdQuery = "SELECT * from problems WHERE id=$1 LIMIT 1"
func (r *Repository) GetProblemById(ctx context.Context, q problems.Querier, id int32) (*models.Problem, error) {
const op = "Repository.ReadProblemById"
var problem models.Problem
err := q.GetContext(ctx, &problem, GetProblemByIdQuery, id)
if err != nil {
return nil, pkg.HandlePgErr(err, op)
}
return &problem, nil
}
const DeleteProblemQuery = "DELETE FROM problems WHERE id=$1"
func (r *Repository) DeleteProblem(ctx context.Context, q problems.Querier, id int32) error {
const op = "Repository.DeleteProblem"
_, err := q.ExecContext(ctx, DeleteProblemQuery, id)
if err != nil {
return pkg.HandlePgErr(err, op)
}
return nil
}
const (
ListProblemsQuery = `SELECT p.id,
p.title,
p.memory_limit,
p.time_limit,
p.created_at,
p.updated_at,
COALESCE(solved_count, 0) AS solved_count
FROM problems p
LEFT JOIN (SELECT t.problem_id,
COUNT(DISTINCT s.participant_id) AS solved_count
FROM solutions s
JOIN tasks t ON s.task_id = t.id
WHERE s.state = 5
GROUP BY t.problem_id) sol ON p.id = sol.problem_id
LIMIT $1 OFFSET $2`
CountProblemsQuery = "SELECT COUNT(*) FROM problems"
)
func (r *Repository) ListProblems(ctx context.Context, q problems.Querier, filter models.ProblemsFilter) (*models.ProblemsList, error) {
const op = "ContestRepository.ListProblems"
var list []*models.ProblemsListItem
err := q.SelectContext(ctx, &list, ListProblemsQuery, filter.PageSize, filter.Offset())
if err != nil {
return nil, pkg.HandlePgErr(err, op)
}
var count int32
err = q.GetContext(ctx, &count, CountProblemsQuery)
if err != nil {
return nil, pkg.HandlePgErr(err, op)
}
return &models.ProblemsList{
Problems: list,
Pagination: models.Pagination{
Total: models.Total(count, filter.PageSize),
Page: filter.Page,
},
}, nil
}
const (
UpdateProblemQuery = `UPDATE problems
SET title = COALESCE($2, title),
time_limit = COALESCE($3, time_limit),
memory_limit = COALESCE($4, memory_limit),
legend = COALESCE($5, legend),
input_format = COALESCE($6, input_format),
output_format = COALESCE($7, output_format),
notes = COALESCE($8, notes),
scoring = COALESCE($9, scoring),
legend_html = COALESCE($10, legend_html),
input_format_html = COALESCE($11, input_format_html),
output_format_html = COALESCE($12, output_format_html),
notes_html = COALESCE($13, notes_html),
scoring_html = COALESCE($14, scoring_html)
WHERE id=$1`
)
func (r *Repository) UpdateProblem(ctx context.Context, q problems.Querier, id int32, problem *models.ProblemUpdate) error {
const op = "Repository.UpdateProblem"
query := q.Rebind(UpdateProblemQuery)
_, err := q.ExecContext(ctx, query,
id,
problem.Title,
problem.TimeLimit,
problem.MemoryLimit,
problem.Legend,
problem.InputFormat,
problem.OutputFormat,
problem.Notes,
problem.Scoring,
problem.LegendHtml,
problem.InputFormatHtml,
problem.OutputFormatHtml,
problem.NotesHtml,
problem.ScoringHtml,
)
if err != nil {
return pkg.HandlePgErr(err, op)
}
return nil
}

View file

@ -0,0 +1,293 @@
package repository_test
import (
"context"
"database/sql"
"fmt"
"git.sch9.ru/new_gate/ms-tester/internal/models"
"git.sch9.ru/new_gate/ms-tester/internal/problems/repository"
"github.com/DATA-DOG/go-sqlmock"
"github.com/jmoiron/sqlx"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
// setupTestDB creates a mocked sqlx.DB and sqlmock instance for testing.
func setupTestDB(t *testing.T) (*sqlx.DB, sqlmock.Sqlmock) {
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
assert.NoError(t, err)
sqlxDB := sqlx.NewDb(db, "sqlmock")
return sqlxDB, mock
}
func TestRepository_CreateProblem(t *testing.T) {
db, mock := setupTestDB(t)
defer db.Close()
repo := repository.NewRepository(db)
t.Run("success", func(t *testing.T) {
ctx := context.Background()
problem := models.Problem{
Id: 1,
Title: "Test Problem",
}
mock.ExpectQuery(repository.CreateProblemQuery).
WithArgs(problem.Title).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(problem.Id))
id, err := repo.CreateProblem(ctx, db, problem.Title)
assert.NoError(t, err)
assert.Equal(t, problem.Id, id)
})
}
func TestRepository_GetProblemById(t *testing.T) {
db, mock := setupTestDB(t)
defer db.Close()
repo := repository.NewRepository(db)
t.Run("success", func(t *testing.T) {
ctx := context.Background()
expected := &models.Problem{
Id: 1,
Title: "Test Problem",
TimeLimit: 1000,
MemoryLimit: 1024,
Legend: "Test Legend",
InputFormat: "Test Input Format",
OutputFormat: "Test Output Format",
Notes: "Test Notes",
Scoring: "Test Scoring",
LegendHtml: "Test Legend HTML",
InputFormatHtml: "Test Input Format HTML",
OutputFormatHtml: "Test Output Format HTML",
NotesHtml: "Test Notes HTML",
ScoringHtml: "Test Scoring HTML",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
columns := []string{
"id",
"title",
"time_limit",
"memory_limit",
"legend",
"input_format",
"output_format",
"notes",
"scoring",
"legend_html",
"input_format_html",
"output_format_html",
"notes_html",
"scoring_html",
"created_at",
"updated_at",
}
rows := sqlmock.NewRows(columns).
AddRow(
expected.Id,
expected.Title,
expected.TimeLimit,
expected.MemoryLimit,
expected.Legend,
expected.InputFormat,
expected.OutputFormat,
expected.Notes,
expected.Scoring,
expected.LegendHtml,
expected.InputFormatHtml,
expected.OutputFormatHtml,
expected.NotesHtml,
expected.ScoringHtml,
expected.CreatedAt,
expected.UpdatedAt)
mock.ExpectQuery(repository.GetProblemByIdQuery).WithArgs(expected.Id).WillReturnRows(rows)
problem, err := repo.GetProblemById(ctx, db, expected.Id)
assert.NoError(t, err)
assert.EqualExportedValues(t, expected, problem)
})
t.Run("not found", func(t *testing.T) {
ctx := context.Background()
id := int32(1)
mock.ExpectQuery(repository.GetProblemByIdQuery).WithArgs(id).WillReturnError(sql.ErrNoRows)
_, err := repo.GetProblemById(ctx, db, id)
assert.Error(t, err)
})
}
func TestRepository_DeleteProblem(t *testing.T) {
db, mock := setupTestDB(t)
defer db.Close()
repo := repository.NewRepository(db)
t.Run("success", func(t *testing.T) {
ctx := context.Background()
id := int32(1)
mock.ExpectExec(repository.DeleteProblemQuery).
WithArgs(id).WillReturnResult(sqlmock.NewResult(0, 1))
err := repo.DeleteProblem(ctx, db, id)
assert.NoError(t, err)
})
t.Run("not found", func(t *testing.T) {
ctx := context.Background()
id := int32(1)
mock.ExpectExec(repository.DeleteProblemQuery).WithArgs(id).WillReturnError(sql.ErrNoRows)
err := repo.DeleteProblem(ctx, db, id)
assert.Error(t, err)
})
}
func TestRepository_ListProblems(t *testing.T) {
db, mock := setupTestDB(t)
defer db.Close()
repo := repository.NewRepository(db)
t.Run("success", func(t *testing.T) {
ctx := context.Background()
expected := make([]*models.ProblemsListItem, 0)
for i := 0; i < 10; i++ {
problem := &models.ProblemsListItem{
Id: int32(i + 1),
Title: fmt.Sprintf("Test Problem %d", i+1),
TimeLimit: 1000,
MemoryLimit: 1024,
SolvedCount: int32(123 * i),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
expected = append(expected, problem)
}
filter := models.ProblemsFilter{
Page: 1,
PageSize: 10,
}
var totalCount int32 = 10
columns := []string{
"id",
"title",
"time_limit",
"memory_limit",
"solved_count",
"created_at",
"updated_at",
}
rows := sqlmock.NewRows(columns)
for _, problem := range expected {
rows = rows.AddRow(
problem.Id,
problem.Title,
problem.TimeLimit,
problem.MemoryLimit,
problem.SolvedCount,
problem.CreatedAt,
problem.UpdatedAt,
)
}
mock.ExpectQuery(repository.ListProblemsQuery).WillReturnRows(rows)
mock.ExpectQuery(repository.CountProblemsQuery).
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(totalCount))
problems, err := repo.ListProblems(ctx, db, filter)
assert.NoError(t, err)
assert.Equal(t, expected, problems.Problems)
assert.Equal(t, models.Pagination{
Page: 1,
Total: 1,
}, problems.Pagination)
})
}
func TestRepository_UpdateProblem(t *testing.T) {
db, mock := setupTestDB(t)
defer db.Close()
repo := repository.NewRepository(db)
t.Run("success", func(t *testing.T) {
ctx := context.Background()
var id int32 = 1
update := &models.ProblemUpdate{
Title: sp("Test Problem"),
TimeLimit: ip(1000),
MemoryLimit: ip(1024),
Legend: sp("Test Legend"),
InputFormat: sp("Test Input Format"),
OutputFormat: sp("Test Output Format"),
Notes: sp("Test Notes"),
Scoring: sp("Test Scoring"),
LegendHtml: sp("Test Legend HTML"),
InputFormatHtml: sp("Test Input Format HTML"),
OutputFormatHtml: sp("Test Output Format HTML"),
NotesHtml: sp("Test Notes HTML"),
ScoringHtml: sp("Test Scoring HTML"),
}
mock.ExpectExec(repository.UpdateProblemQuery).WithArgs(
id,
update.Title,
update.TimeLimit,
update.MemoryLimit,
update.Legend,
update.InputFormat,
update.OutputFormat,
update.Notes,
update.Scoring,
update.LegendHtml,
update.InputFormatHtml,
update.OutputFormatHtml,
update.NotesHtml,
update.ScoringHtml,
).WillReturnResult(sqlmock.NewResult(1, 1))
err := repo.UpdateProblem(ctx, db, id, update)
assert.NoError(t, err)
})
}
func sp(s string) *string {
return &s
}
func ip(s int32) *int32 {
return &s
}