Files
smart-search-back/TESTING.md
vallyenfail d959dcca96 add service
2026-01-17 17:39:33 +03:00

310 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Руководство по тестированию
## Архитектура для тестирования
Проект спроектирован с учетом тестируемости:
### ✅ Интерфейсы для всех слоев
**Repository интерфейсы** (`internal/repository/interfaces.go`):
```go
type UserRepository interface {
FindByEmailHash(ctx context.Context, emailHash string) (*model.User, error)
FindByID(ctx context.Context, userID int) (*model.User, error)
Create(ctx context.Context, user *model.User) error
// ...
}
```
**Service интерфейсы** (`internal/service/interfaces.go`):
```go
type AuthService interface {
Login(ctx context.Context, email, password, ip, userAgent string) (string, string, error)
Refresh(ctx context.Context, refreshToken string) (string, error)
// ...
}
```
### ✅ Context пробрасывается через все слои
```
main.go (ctx) → Workers (ctx) → gRPC Handler (ctx) → Service (ctx) → Repository (ctx) → pgx
```
**Правило**: `context.Background()` создается **только один раз** в `main.go` и прокидывается через все компоненты.
Это позволяет:
- **Graceful shutdown**: при отмене context все workers и операции останавливаются
- Отменять долгие операции
- Передавать метаданные (trace ID, user ID)
- Контролировать таймауты
- Избежать потерянных goroutines
## Автоматическая генерация моков
Проект использует [minimock](https://github.com/gojuno/minimock) для автоматической генерации типизированных моков из интерфейсов.
### Генерация моков
Все моки генерируются в `internal/mocks/` одной командой:
```bash
make generate-mock
```
Эта команда генерирует моки для всех интерфейсов:
- **Repository интерфейсы**: `UserRepository`, `SessionRepository`, `InviteRepository`, `RequestRepository`, `SupplierRepository`, `TokenUsageRepository`
- **Service интерфейсы**: `AuthService`, `UserService`, `InviteService`, `RequestService`, `SupplierService`
### Использование сгенерированных моков
```go
import (
"testing"
"context"
"smart-search-back/internal/mocks"
"smart-search-back/internal/service"
"smart-search-back/internal/model"
"github.com/stretchr/testify/assert"
)
func TestAuthService_Login_Success(t *testing.T) {
// Создаем моки
mockUserRepo := mocks.NewUserRepositoryMock(t)
mockSessionRepo := mocks.NewSessionRepositoryMock(t)
// Настраиваем поведение мока
mockUserRepo.FindByEmailHashMock.Expect(context.Background(), "email_hash").Return(&model.User{
ID: 1,
PasswordHash: "b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86",
}, nil)
mockSessionRepo.CreateMock.Expect(context.Background(), &model.Session{}).Return(nil)
// Создаем сервис с моками
authService := service.NewAuthService(mockUserRepo, mockSessionRepo)
// Выполняем тест
accessToken, refreshToken, err := authService.Login(
context.Background(),
"test@example.com",
"password",
"127.0.0.1",
"test-agent",
)
// Проверяем результат
assert.NoError(t, err)
assert.NotEmpty(t, accessToken)
assert.NotEmpty(t, refreshToken)
// Minimock автоматически проверит что все ожидания выполнены
}
```
### Преимущества minimock
**Типобезопасность** - моки генерируются из интерфейсов, ошибки компиляции при изменении сигнатур
**Автоматическая проверка** - проверяет что все ожидания выполнены
**Счетчики вызовов** - можно проверить сколько раз был вызван метод
**Inspection** - можно проверить аргументы вызовов
**Minimal boilerplate** - не нужно писать моки вручную
### Пример с проверкой вызовов
```go
func TestUserService_GetBalance(t *testing.T) {
mockUserRepo := mocks.NewUserRepositoryMock(t)
// Ожидаем вызов GetBalance с userID=123
mockUserRepo.GetBalanceMock.Expect(context.Background(), 123).Return(100.50, nil)
userService := service.NewUserService(mockUserRepo, nil)
balance, err := userService.GetBalance(context.Background(), 123)
assert.NoError(t, err)
assert.Equal(t, 100.50, balance)
// Minimock автоматически проверит:
// - что GetBalance был вызван ровно 1 раз
// - с правильными аргументами
// - и вернул правильное значение
}
```
### Ручные моки (legacy пример)
Для сравнения, старый пример с ручными моками (все еще работает, но не рекомендуется):
```go
// Mock репозитория (legacy - используйте minimock!)
type mockUserRepo struct {
findByEmailHashFunc func(ctx context.Context, emailHash string) (*model.User, error)
}
func (m *mockUserRepo) FindByEmailHash(ctx context.Context, emailHash string) (*model.User, error) {
if m.findByEmailHashFunc != nil {
return m.findByEmailHashFunc(ctx, emailHash)
}
return nil, nil
}
```
## Запуск тестов
```bash
# Все тесты
go test ./...
# С покрытием
go test ./... -cover
# Verbose режим
go test ./... -v
# Конкретный пакет
go test ./internal/service/tests/...
# С race detector
go test ./... -race
```
## Использование minimock
Проект использует [minimock](https://github.com/gojuno/minimock) для автоматической генерации типизированных моков.
### Генерация моков
Все моки генерируются в `internal/mocks/`:
```bash
make generate-mock
```
Генерируются моки для всех интерфейсов:
- **Repository**: `UserRepository`, `SessionRepository`, `InviteRepository`, `RequestRepository`, `SupplierRepository`, `TokenUsageRepository`
- **Service**: `AuthService`, `UserService`, `InviteService`, `RequestService`, `SupplierService`
### Использование сгенерированных моков
```go
import "smart-search-back/internal/mocks"
func TestAuthService_Login(t *testing.T) {
mockUserRepo := mocks.NewUserRepositoryMock(t)
mockSessionRepo := mocks.NewSessionRepositoryMock(t)
mockUserRepo.FindByEmailHashMock.Expect(ctx, "hash").Return(&model.User{...}, nil)
authService := service.NewAuthService(mockUserRepo, mockSessionRepo)
// ... тест
}
```
Подробнее см. раздел "Автоматическая генерация моков" выше.
## Структура тестов
```
internal/
├── service/
│ ├── auth.go
│ ├── interfaces.go # Интерфейсы сервисов
│ └── tests/
│ └── auth_test.go # Тесты с моками
├── repository/
│ ├── user.go
│ ├── interfaces.go # Интерфейсы репозиториев
│ └── tests/
│ └── user_test.go
```
## Best Practices
### 1. Используйте context.Background() в тестах
```go
ctx := context.Background()
result, err := service.SomeMethod(ctx, params)
```
### 2. Мокайте только то, что нужно
```go
mockRepo := &mockUserRepo{
findByIDFunc: func(ctx context.Context, id int) (*model.User, error) {
return &model.User{ID: id}, nil
},
// Остальные методы можно не реализовывать если не используются
}
```
### 3. Проверяйте вызовы
```go
var called bool
mockRepo := &mockUserRepo{
createFunc: func(ctx context.Context, user *model.User) error {
called = true
assert.Equal(t, "expected@email.com", user.Email)
return nil
},
}
// ... вызов сервиса
assert.True(t, called, "Create should have been called")
```
### 4. Тестируйте ошибки
```go
mockRepo := &mockUserRepo{
findByIDFunc: func(ctx context.Context, id int) (*model.User, error) {
return nil, errors.NewBusinessError(errors.UserNotFound, "user not found")
},
}
result, err := service.GetUser(ctx, 123)
assert.Error(t, err)
assert.Nil(t, result)
```
## Integration тесты
Для integration тестов с реальной БД можно использовать testcontainers:
```go
func TestUserRepository_Integration(t *testing.T) {
// Создать testcontainer с PostgreSQL
// Применить миграции
// Запустить тесты
}
```
## CI/CD
```yaml
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: 1.21
- run: go test ./... -race -cover
```
## Хэши для тестов
При тестировании аутентификации используйте правильные хэши:
- Пароль: `"password"`
- SHA512 хэш: `"b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86"`
Или используйте `crypto.PasswordHash("password")` прямо в тестах.