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 }