Compare commits
3 Commits
a247e6213e
...
fe0bd0dddc
| Author | SHA1 | Date | |
|---|---|---|---|
| fe0bd0dddc | |||
| 16a6b5ed94 | |||
| 51f30b516e |
11
.gitignore
vendored
11
.gitignore
vendored
@@ -30,4 +30,13 @@ go.work.sum
|
|||||||
# Editor/IDE
|
# Editor/IDE
|
||||||
# .idea/
|
# .idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
|
|
||||||
|
# Builded files
|
||||||
|
apiserver
|
||||||
|
|
||||||
|
!apiserver/
|
||||||
|
# Swagger generated files (auto-generated from source code)
|
||||||
|
internal/apiserver/docs/swagger.json
|
||||||
|
internal/apiserver/docs/swagger.yaml
|
||||||
|
internal/apiserver/docs/docs.go
|
||||||
9
LICENSE.md
Normal file
9
LICENSE.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Sergey Elpashev
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
147
Makefile
Normal file
147
Makefile
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# MyGoServer Makefile
|
||||||
|
# Удобные команды для разработки и тестирования
|
||||||
|
|
||||||
|
.PHONY: help build test test-coverage test-race fmt vet clean docker-up docker-down
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
help:
|
||||||
|
@echo "🚀 MyGoServer Development Commands:"
|
||||||
|
@echo ""
|
||||||
|
@echo "📦 Build Commands:"
|
||||||
|
@echo " build - Build the application"
|
||||||
|
@echo " build-clean - Clean build and rebuild"
|
||||||
|
@echo ""
|
||||||
|
@echo "🧪 Test Commands:"
|
||||||
|
@echo " test - Run all tests"
|
||||||
|
@echo " test-unit - Run unit tests only"
|
||||||
|
@echo " test-coverage - Run tests with coverage report"
|
||||||
|
@echo " test-race - Run tests with race detector"
|
||||||
|
@echo " test-watch - Run tests in watch mode"
|
||||||
|
@echo ""
|
||||||
|
@echo "🔧 Code Quality:"
|
||||||
|
@echo " fmt - Format all Go code"
|
||||||
|
@echo " vet - Run static analysis"
|
||||||
|
@echo " lint - Run linting (if golangci-lint installed)"
|
||||||
|
@echo ""
|
||||||
|
@echo "🐳 Docker Commands:"
|
||||||
|
@echo " docker-up - Start application with Docker"
|
||||||
|
@echo " docker-down - Stop Docker containers"
|
||||||
|
@echo " docker-build - Build Docker image"
|
||||||
|
@echo ""
|
||||||
|
@echo "🧹 Cleanup:"
|
||||||
|
@echo " clean - Clean build artifacts"
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
# Build commands
|
||||||
|
build: generate-docs
|
||||||
|
@echo "🏗️ Building application..."
|
||||||
|
go build -o apiserver ./cmd/apiserver
|
||||||
|
@echo "✅ Build completed: apiserver"
|
||||||
|
|
||||||
|
generate-docs:
|
||||||
|
@echo "📖 Generating Swagger documentation..."
|
||||||
|
swag init -g cmd/apiserver/main.go -o internal/apiserver/docs
|
||||||
|
@echo "✅ Swagger documentation generated"
|
||||||
|
|
||||||
|
build-clean: clean build
|
||||||
|
|
||||||
|
# Test commands
|
||||||
|
test:
|
||||||
|
@echo "🧪 Running all tests..."
|
||||||
|
./scripts/test.sh
|
||||||
|
|
||||||
|
test-unit:
|
||||||
|
@echo "🧪 Running unit tests..."
|
||||||
|
go test -v ./internal/apiserver/...
|
||||||
|
|
||||||
|
test-coverage:
|
||||||
|
@echo "📊 Running tests with coverage..."
|
||||||
|
go test -coverprofile=coverage.out ./...
|
||||||
|
go tool cover -html=coverage.out -o coverage.html
|
||||||
|
@echo "📊 Coverage report: coverage.html"
|
||||||
|
|
||||||
|
test-race:
|
||||||
|
@echo "🏁 Running tests with race detector..."
|
||||||
|
go test -race ./...
|
||||||
|
|
||||||
|
test-watch:
|
||||||
|
@echo "👀 Running tests in watch mode..."
|
||||||
|
@which gotest > /dev/null || (echo "Installing gotest-watcher..." && go install github.com/bitfield/gotest@latest)
|
||||||
|
gotest -w ./...
|
||||||
|
|
||||||
|
# Code quality
|
||||||
|
fmt:
|
||||||
|
@echo "🎨 Formatting code..."
|
||||||
|
go fmt ./...
|
||||||
|
@echo "✅ Code formatted"
|
||||||
|
|
||||||
|
vet:
|
||||||
|
@echo "🔍 Running static analysis..."
|
||||||
|
go vet ./...
|
||||||
|
@echo "✅ Static analysis completed"
|
||||||
|
|
||||||
|
lint:
|
||||||
|
@echo "🧹 Running linter..."
|
||||||
|
@which golangci-lint > /dev/null || (echo "Installing golangci-lint..." && go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest)
|
||||||
|
golangci-lint run
|
||||||
|
@echo "✅ Linting completed"
|
||||||
|
|
||||||
|
# Docker commands
|
||||||
|
docker-up:
|
||||||
|
@echo "🐳 Starting application with Docker..."
|
||||||
|
docker-compose -f build/docker-compose.yml up -d
|
||||||
|
@echo "🚀 Application started on http://localhost:8080"
|
||||||
|
@echo "📖 Swagger UI: http://localhost:8080/swagger/"
|
||||||
|
|
||||||
|
docker-down:
|
||||||
|
@echo "🛑 Stopping Docker containers..."
|
||||||
|
docker-compose -f build/docker-compose.yml down
|
||||||
|
|
||||||
|
docker-build:
|
||||||
|
@echo "🔨 Building Docker image..."
|
||||||
|
docker build -f build/Dockerfile -t mygoserver:latest .
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
clean:
|
||||||
|
@echo "🧹 Cleaning build artifacts..."
|
||||||
|
rm -f apiserver coverage.out coverage.html
|
||||||
|
rm -rf build/bin/
|
||||||
|
docker-compose -f build/docker-compose.yml down -v 2>/dev/null || true
|
||||||
|
@echo "✅ Cleanup completed"
|
||||||
|
|
||||||
|
# Development helpers
|
||||||
|
dev: build
|
||||||
|
@echo "🚀 Starting development server..."
|
||||||
|
./apiserver
|
||||||
|
|
||||||
|
debug: build
|
||||||
|
@echo "🐛 Starting debug server..."
|
||||||
|
dlv --listen=:2345 --headless=true --api-version=2 exec ./apiserver
|
||||||
|
|
||||||
|
# Run specific test patterns
|
||||||
|
test-pattern:
|
||||||
|
@if [ -z "$(PATTERN)" ]; then \
|
||||||
|
echo "Usage: make test-pattern PATTERN=TestName"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@echo "🧪 Running tests matching: $(PATTERN)"
|
||||||
|
go test -v ./internal/apiserver/... -run="$(PATTERN)"
|
||||||
|
|
||||||
|
# Install development tools
|
||||||
|
install-tools:
|
||||||
|
@echo "🛠️ Installing development tools..."
|
||||||
|
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||||
|
go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
|
@echo "✅ Development tools installed"
|
||||||
|
|
||||||
|
# Swagger documentation
|
||||||
|
swag-setup:
|
||||||
|
@echo "📖 Setting up Swagger documentation..."
|
||||||
|
@echo "⚠️ Installing swag tool..."
|
||||||
|
go install github.com/swaggo/swag/cmd/swag@latest
|
||||||
|
@echo "✅ swag installed"
|
||||||
|
@echo "📋 Initializing Swagger configuration..."
|
||||||
|
swag init -g cmd/apiserver/main.go -o internal/apiserver/docs
|
||||||
|
@echo "✅ Swagger documentation generated"
|
||||||
|
|
||||||
|
.PHONY: help build test test-coverage test-race fmt vet clean docker-up docker-down swag-setup
|
||||||
31
build/Dockerfile
Normal file
31
build/Dockerfile
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM golang:1.25-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy go mod files
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
|
||||||
|
# Download dependencies
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o apiserver ./cmd/apiserver
|
||||||
|
|
||||||
|
# Final stage
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
WORKDIR /root/
|
||||||
|
|
||||||
|
# Copy binary from builder stage
|
||||||
|
COPY --from=builder /app/apiserver .
|
||||||
|
COPY --from=builder /app/configs ./configs
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
CMD ["./apiserver", "--config-path=./configs/server.toml"]
|
||||||
23
build/docker-compose.yml
Normal file
23
build/docker-compose.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
apiserver:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: build/Dockerfile
|
||||||
|
container_name: mygoserver
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
volumes:
|
||||||
|
- ../configs:/root/configs:ro
|
||||||
|
environment:
|
||||||
|
- LOGGING_LEVEL=info
|
||||||
|
- LOGGING_FORMAT=json
|
||||||
|
- LOGGING_OUTPUT=stdout
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- app-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
app-network:
|
||||||
|
driver: bridge
|
||||||
78
cmd/apiserver/config/config.go
Normal file
78
cmd/apiserver/config/config.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
Address string `toml:"address"`
|
||||||
|
ReadTimeout time.Duration `toml:"read_timeout"`
|
||||||
|
WriteTimeout time.Duration `toml:"write_timeout"`
|
||||||
|
IdleTimeout time.Duration `toml:"idle_timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Logging struct {
|
||||||
|
Level string `toml:"level"`
|
||||||
|
Format string `toml:"format"`
|
||||||
|
Output string `toml:"output"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
MaxBodySize int64 `toml:"max_body_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Server Server `toml:"server"`
|
||||||
|
Logging Logging `toml:"logging"`
|
||||||
|
Request Request `toml:"request"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
configPath string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.StringVar(&configPath, "config-path", "./configs/development.toml", "path to config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load() *Config {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var config Config
|
||||||
|
|
||||||
|
if _, err := toml.DecodeFile(configPath, &config); err != nil {
|
||||||
|
log.Fatalf("Failed to load config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set defaults if not specified
|
||||||
|
if config.Server.Address == "" {
|
||||||
|
config.Server.Address = ":8080"
|
||||||
|
}
|
||||||
|
if config.Server.ReadTimeout == 0 {
|
||||||
|
config.Server.ReadTimeout = 10 * time.Second
|
||||||
|
}
|
||||||
|
if config.Server.WriteTimeout == 0 {
|
||||||
|
config.Server.WriteTimeout = 10 * time.Second
|
||||||
|
}
|
||||||
|
if config.Server.IdleTimeout == 0 {
|
||||||
|
config.Server.IdleTimeout = 60 * time.Second
|
||||||
|
}
|
||||||
|
if config.Logging.Level == "" {
|
||||||
|
config.Logging.Level = "info"
|
||||||
|
}
|
||||||
|
if config.Logging.Format == "" {
|
||||||
|
config.Logging.Format = "json"
|
||||||
|
}
|
||||||
|
if config.Logging.Output == "" {
|
||||||
|
config.Logging.Output = "stdout"
|
||||||
|
}
|
||||||
|
if config.Request.MaxBodySize == 0 {
|
||||||
|
config.Request.MaxBodySize = 10 * 1024 * 1024 // 10MB
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config
|
||||||
|
}
|
||||||
@@ -1,23 +1,31 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"git.nwaifu.su/sergey/MyGoServer/internal/app/apiserver"
|
"git.nwaifu.su/sergey/MyGoServer/cmd/apiserver/config"
|
||||||
|
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// @title Some GoLang server
|
||||||
configPath string
|
// @version 1.0
|
||||||
)
|
// @description This is some GoLang server.
|
||||||
|
// @termsOfService http://swagger.io/terms/
|
||||||
|
|
||||||
func init() {
|
// @contact.name Sergey Elpashev
|
||||||
flag.StringVar(&configPath, "config-path", "/go/bin/configs/server.toml", "path to config")
|
// @contact.url https://nwaifu.su
|
||||||
}
|
// @contact.email mrsedan@nwaifu.su
|
||||||
|
|
||||||
|
// @license.name MIT
|
||||||
|
// @license.url https://opensource.org/license/mit
|
||||||
|
|
||||||
|
// @host localhost:8080
|
||||||
|
// @BasePath /
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
cfg := config.Load()
|
||||||
if err := apiserver.Start(); err != nil {
|
|
||||||
|
srv := server.NewServer(cfg)
|
||||||
|
if err := srv.Start(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
configs/development.toml
Normal file
16
configs/development.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Development server configuration
|
||||||
|
[server]
|
||||||
|
address = ":8080"
|
||||||
|
read_timeout = "5s"
|
||||||
|
write_timeout = "5s"
|
||||||
|
idle_timeout = "30s"
|
||||||
|
|
||||||
|
# Logging configuration for development
|
||||||
|
[logging]
|
||||||
|
level = "debug"
|
||||||
|
format = "text"
|
||||||
|
output = "stdout"
|
||||||
|
|
||||||
|
# Request configuration
|
||||||
|
[request]
|
||||||
|
max_body_size = 10485760 # 10MB
|
||||||
16
configs/server.toml
Normal file
16
configs/server.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Server configuration
|
||||||
|
[server]
|
||||||
|
address = ":8080"
|
||||||
|
read_timeout = "10s"
|
||||||
|
write_timeout = "10s"
|
||||||
|
idle_timeout = "60s"
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
[logging]
|
||||||
|
level = "info"
|
||||||
|
format = "json"
|
||||||
|
output = "stdout"
|
||||||
|
|
||||||
|
# Request configuration
|
||||||
|
[request]
|
||||||
|
max_body_size = 10485760 # 10MB
|
||||||
206
docs/testing_guide.md
Normal file
206
docs/testing_guide.md
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
# 🧪 Руководство по тестированию MyGoServer
|
||||||
|
|
||||||
|
## Быстрый старт
|
||||||
|
|
||||||
|
### Способы запуска тестов:
|
||||||
|
|
||||||
|
1. **Через Makefile (рекомендуется)**:
|
||||||
|
```bash
|
||||||
|
make test # Все тесты
|
||||||
|
make test-unit # Только unit тесты
|
||||||
|
make test-coverage # Тесты с покрытием
|
||||||
|
make test-race # С race detector
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Через скрипты**:
|
||||||
|
```bash
|
||||||
|
./scripts/test.sh # Все тесты с отчетом
|
||||||
|
./scripts/test.sh "TestName" # Конкретные тесты
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Ручной запуск**:
|
||||||
|
```bash
|
||||||
|
go test ./... # Все тесты
|
||||||
|
go test -v ./internal/apiserver/... # Verbose вывод
|
||||||
|
go test -race ./... # С race detector
|
||||||
|
```
|
||||||
|
|
||||||
|
## Детальное руководство
|
||||||
|
|
||||||
|
### 📊 Покрытие кода (Coverage)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Генерация отчета о покрытии
|
||||||
|
go test -coverprofile=coverage.out ./...
|
||||||
|
go tool cover -html=coverage.out -o coverage.html
|
||||||
|
|
||||||
|
# Просмотр покрытия в терминале
|
||||||
|
go tool cover -func=coverage.out
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🏁 Race Detector
|
||||||
|
|
||||||
|
Поиск гонок данных (data races):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test -race ./...
|
||||||
|
|
||||||
|
# Для более глубокой проверки
|
||||||
|
go test -race -count=10 ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📈 Benchmark тесты
|
||||||
|
|
||||||
|
Для измерения производительности:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go test -bench=. -benchmem ./...
|
||||||
|
|
||||||
|
# Для конкретных функций
|
||||||
|
go test -bench=BenchmarkHomeHandler -benchmem ./internal/apiserver/...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🎯 Запуск по паттернам
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Только тесты с определенным именем
|
||||||
|
go test -run TestHome ./...
|
||||||
|
|
||||||
|
# Исключить некоторые тесты
|
||||||
|
go test -run '^(?!TestIntegration).*' ./...
|
||||||
|
|
||||||
|
# Искать по нескольким паттернам
|
||||||
|
go test -run 'TestHome|TestConfig' ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🔄 Непрерывное тестирование
|
||||||
|
|
||||||
|
Для разработки с автоперезапуском тестов:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Установка gotest-watcher
|
||||||
|
go install github.com/bitfield/gotest@latest
|
||||||
|
|
||||||
|
# Автоматический запуск тестов при изменении файлов
|
||||||
|
gotest -w ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📊 Отчеты и анализ
|
||||||
|
|
||||||
|
#### HTML отчет о покрытии:
|
||||||
|
- `coverage.html` - веб-интерфейс покрытия
|
||||||
|
- `coverage.out` - данные покрытия
|
||||||
|
|
||||||
|
#### JSON отчет (для CI/CD):
|
||||||
|
```bash
|
||||||
|
go test -json ./... > test-report.json
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Логи в файл:
|
||||||
|
```bash
|
||||||
|
go test -v ./... > test.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🏗️ Структура тестов
|
||||||
|
|
||||||
|
### Unit тесты:
|
||||||
|
```
|
||||||
|
internal/apiserver/handlers/
|
||||||
|
├── home_test.go
|
||||||
|
├── middleware_test.go
|
||||||
|
└── ...
|
||||||
|
|
||||||
|
internal/apiserver/models/
|
||||||
|
├── response_test.go
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration тесты:
|
||||||
|
```
|
||||||
|
test/integration/
|
||||||
|
├── api_test.go
|
||||||
|
├── server_test.go
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Файлы конфигурации для тестов:
|
||||||
|
```
|
||||||
|
test/
|
||||||
|
├── fixtures/ # Тестовые данные
|
||||||
|
├── helpers/ # Вспомогательные функции
|
||||||
|
└── testdata/ # Статические данные
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Настройка окружения тестирования
|
||||||
|
|
||||||
|
### Переменные окружения:
|
||||||
|
```bash
|
||||||
|
export TEST_ENV=development
|
||||||
|
export LOG_LEVEL=debug
|
||||||
|
export CONFIG_PATH=./configs/development.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 Примеры тестов
|
||||||
|
|
||||||
|
### Базовый тест:
|
||||||
|
```go
|
||||||
|
func TestHomeHandler_ServeHTTP(t *testing.T) {
|
||||||
|
handler := handlers.NewHomeHandler()
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "/", nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
if status := rr.Code; status != http.StatusOK {
|
||||||
|
t.Errorf("handler returned wrong status code: got %v want %v",
|
||||||
|
status, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Тест с моками:
|
||||||
|
```go
|
||||||
|
func TestConfig_Load(t *testing.T) {
|
||||||
|
// Мок конфигурации
|
||||||
|
cfg := &Config{
|
||||||
|
Server: ServerConfig{
|
||||||
|
Address: ":8080",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Server.Address != ":8080" {
|
||||||
|
t.Error("Config not loaded correctly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Benchmark тест:
|
||||||
|
```go
|
||||||
|
func BenchmarkHomeHandler_ServeHTTP(b *testing.B) {
|
||||||
|
handler := handlers.NewHomeHandler()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
req := httptest.NewRequest("GET", "/", nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Быстрые команды
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверка всех аспектов качества кода
|
||||||
|
make test-coverage && make vet && make fmt
|
||||||
|
|
||||||
|
# Быстрый цикл разработки
|
||||||
|
make test-watch
|
||||||
|
|
||||||
|
# Полная проверка проекта
|
||||||
|
make build && make test-race && make lint
|
||||||
|
|
||||||
|
# Тестирование конкретного компонента
|
||||||
|
make test-pattern PATTERN=TestHome
|
||||||
|
```
|
||||||
21
go.mod
21
go.mod
@@ -4,11 +4,28 @@ go 1.25.4
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/felixge/httpsnoop v1.0.4
|
github.com/felixge/httpsnoop v1.0.4
|
||||||
github.com/gorilla/mux v1.8.1
|
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/BurntSushi/toml v1.5.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
github.com/swaggo/http-swagger v1.3.4
|
||||||
|
github.com/swaggo/swag v1.16.6
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||||
|
github.com/go-openapi/spec v0.20.6 // indirect
|
||||||
|
github.com/go-openapi/swag v0.19.15 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.6 // indirect
|
||||||
|
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
|
||||||
|
golang.org/x/mod v0.17.0 // indirect
|
||||||
|
golang.org/x/net v0.34.0 // indirect
|
||||||
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
65
go.sum
65
go.sum
@@ -1,21 +1,78 @@
|
|||||||
|
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||||
|
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
|
||||||
|
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
|
||||||
|
github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ=
|
||||||
|
github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
|
||||||
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
|
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||||
|
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||||
|
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc=
|
||||||
|
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
|
||||||
|
github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww=
|
||||||
|
github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ=
|
||||||
|
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||||
|
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||||
|
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||||
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||||
|
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||||
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||||
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
0
internal/apiserver/docs/.gitkeep
Normal file
0
internal/apiserver/docs/.gitkeep
Normal file
37
internal/apiserver/handlers/home.go
Normal file
37
internal/apiserver/handlers/home.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HomeHandler handles the root endpoint
|
||||||
|
type HomeHandler struct{}
|
||||||
|
|
||||||
|
func NewHomeHandler() *HomeHandler {
|
||||||
|
return &HomeHandler{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP implements http.Handler
|
||||||
|
func (h *HomeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
h.handleHome(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Health check
|
||||||
|
// @Description Проверка состояния сервера
|
||||||
|
// @Tags Health
|
||||||
|
// @Success 200 {object} models.Response "Server is running"
|
||||||
|
// @Router / [get]
|
||||||
|
func (h *HomeHandler) handleHome(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Create a simple success response
|
||||||
|
response := models.NewSuccessResponse(map[string]string{
|
||||||
|
"status": "ok",
|
||||||
|
"message": "API Server is running",
|
||||||
|
})
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
41
internal/apiserver/handlers/home_test.go
Normal file
41
internal/apiserver/handlers/home_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHomeHandler_ServeHTTP(t *testing.T) {
|
||||||
|
// Create a new home handler
|
||||||
|
handler := NewHomeHandler()
|
||||||
|
|
||||||
|
// Create a test request
|
||||||
|
req := httptest.NewRequest("GET", "/", nil)
|
||||||
|
|
||||||
|
// Create a test response recorder
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// Call the handler
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
// Check status code
|
||||||
|
if status := rr.Code; status != http.StatusOK {
|
||||||
|
t.Errorf("handler returned wrong status code: got %v want %v",
|
||||||
|
status, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check content type
|
||||||
|
if ct := rr.Header().Get("Content-Type"); ct != "application/json" {
|
||||||
|
t.Errorf("handler returned wrong content type: got %v want %v",
|
||||||
|
ct, "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check response body contains expected data
|
||||||
|
expectedBody := `"success":true`
|
||||||
|
if body := rr.Body.String(); !strings.Contains(body, expectedBody) {
|
||||||
|
t.Errorf("handler returned unexpected body: got %v want %v",
|
||||||
|
body, expectedBody)
|
||||||
|
}
|
||||||
|
}
|
||||||
76
internal/apiserver/logger/logger.go
Normal file
76
internal/apiserver/logger/logger.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logger *logrus.Logger
|
||||||
|
once sync.Once
|
||||||
|
initialized bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize logger with configuration
|
||||||
|
func Initialize(level string, format string, output string) {
|
||||||
|
once.Do(func() {
|
||||||
|
logger = logrus.New()
|
||||||
|
|
||||||
|
// Set level
|
||||||
|
lvl, err := logrus.ParseLevel(level)
|
||||||
|
if err != nil {
|
||||||
|
lvl = logrus.InfoLevel
|
||||||
|
}
|
||||||
|
logger.SetLevel(lvl)
|
||||||
|
|
||||||
|
// Set format
|
||||||
|
switch format {
|
||||||
|
case "json":
|
||||||
|
logger.SetFormatter(&logrus.JSONFormatter{
|
||||||
|
TimestampFormat: "2006-01-02T15:04:05.000Z",
|
||||||
|
})
|
||||||
|
case "text":
|
||||||
|
logger.SetFormatter(&logrus.TextFormatter{
|
||||||
|
TimestampFormat: "2006-01-02 15:04:05",
|
||||||
|
FullTimestamp: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set output
|
||||||
|
if output != "" {
|
||||||
|
//TODO: Use files
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogger returns the singleton logger instance
|
||||||
|
func GetLogger() *logrus.Logger {
|
||||||
|
if !initialized {
|
||||||
|
// Initialize with defaults
|
||||||
|
Initialize("info", "json", "stdout")
|
||||||
|
}
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFields creates a logger with additional fields
|
||||||
|
func WithFields(fields map[string]interface{}) *logrus.Entry {
|
||||||
|
return GetLogger().WithFields(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs an info message
|
||||||
|
func Info(msg string) {
|
||||||
|
GetLogger().Info(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs an error message
|
||||||
|
func Error(msg string) {
|
||||||
|
GetLogger().Error(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a debug message
|
||||||
|
func Debug(msg string) {
|
||||||
|
GetLogger().Debug(msg)
|
||||||
|
}
|
||||||
42
internal/apiserver/middleware/logging.go
Normal file
42
internal/apiserver/middleware/logging.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/logger"
|
||||||
|
rhttp "git.nwaifu.su/sergey/MyGoServer/pkg/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoggingMiddleware logs HTTP requests
|
||||||
|
func LoggingMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
// Use the custom response writer to capture status code
|
||||||
|
rw := rhttp.NewResponseWriter(w)
|
||||||
|
|
||||||
|
next.ServeHTTP(rw, r)
|
||||||
|
|
||||||
|
// Log the request
|
||||||
|
logger := logger.GetLogger()
|
||||||
|
logger.WithFields(map[string]interface{}{
|
||||||
|
"method": r.Method,
|
||||||
|
"uri": r.RequestURI,
|
||||||
|
"remote_addr": r.RemoteAddr,
|
||||||
|
"user_agent": r.UserAgent(),
|
||||||
|
"status_code": rw.GetStatusCode(),
|
||||||
|
"duration": time.Since(start).String(),
|
||||||
|
"request_id": getRequestID(r.Context()),
|
||||||
|
}).Info("HTTP request")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRequestID retrieves request ID from context
|
||||||
|
func getRequestID(ctx context.Context) string {
|
||||||
|
if id, ok := ctx.Value("request_id").(string); ok {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
33
internal/apiserver/middleware/request_id.go
Normal file
33
internal/apiserver/middleware/request_id.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/logger"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestIDMiddleware adds a unique request ID to each request
|
||||||
|
func RequestIDMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Generate a new request ID
|
||||||
|
requestID := uuid.New().String()
|
||||||
|
|
||||||
|
// Set request ID in response header
|
||||||
|
w.Header().Set("X-Request-ID", requestID)
|
||||||
|
|
||||||
|
// Add request ID to context
|
||||||
|
ctx := r.Context()
|
||||||
|
ctx = context.WithValue(ctx, "request_id", requestID)
|
||||||
|
|
||||||
|
// Log the request ID assignment
|
||||||
|
logger := logger.GetLogger()
|
||||||
|
logger.WithFields(map[string]interface{}{
|
||||||
|
"request_id": requestID,
|
||||||
|
}).Debug("Request ID assigned")
|
||||||
|
|
||||||
|
// Continue with the request
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
25
internal/apiserver/models/response.go
Normal file
25
internal/apiserver/models/response.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// Response represents a standard API response
|
||||||
|
type Response struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Data interface{} `json:"data,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
RequestID string `json:"request_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSuccessResponse creates a successful response
|
||||||
|
func NewSuccessResponse(data interface{}) *Response {
|
||||||
|
return &Response{
|
||||||
|
Success: true,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErrorResponse creates an error response
|
||||||
|
func NewErrorResponse(err error) *Response {
|
||||||
|
return &Response{
|
||||||
|
Success: false,
|
||||||
|
Error: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
22
internal/apiserver/server/routes.go
Normal file
22
internal/apiserver/server/routes.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "git.nwaifu.su/sergey/MyGoServer/internal/apiserver/docs"
|
||||||
|
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/handlers"
|
||||||
|
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/middleware"
|
||||||
|
|
||||||
|
httpSwagger "github.com/swaggo/http-swagger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// setupRoutes configures all routes
|
||||||
|
func (s *Server) setupRoutes() {
|
||||||
|
// Add request ID middleware to all routes
|
||||||
|
s.router.Handle("/", middleware.RequestIDMiddleware(
|
||||||
|
middleware.LoggingMiddleware(
|
||||||
|
handlers.NewHomeHandler(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
|
||||||
|
// Swagger UI
|
||||||
|
s.router.Handle("/swagger/", httpSwagger.WrapHandler)
|
||||||
|
}
|
||||||
70
internal/apiserver/server/server.go
Normal file
70
internal/apiserver/server/server.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.nwaifu.su/sergey/MyGoServer/cmd/apiserver/config"
|
||||||
|
"git.nwaifu.su/sergey/MyGoServer/internal/apiserver/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type contextKey struct {
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
var connContextKey = &contextKey{"http-conn"}
|
||||||
|
|
||||||
|
func saveConnInContext(ctx context.Context, c net.Conn) context.Context {
|
||||||
|
return context.WithValue(ctx, connContextKey, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server represents the HTTP server
|
||||||
|
type Server struct {
|
||||||
|
config *config.Config
|
||||||
|
router *http.ServeMux
|
||||||
|
server *http.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer creates a new server instance
|
||||||
|
func NewServer(cfg *config.Config) *Server {
|
||||||
|
s := &Server{
|
||||||
|
config: cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize logger
|
||||||
|
logger.Initialize(cfg.Logging.Level, cfg.Logging.Format, cfg.Logging.Output)
|
||||||
|
|
||||||
|
// Create router
|
||||||
|
s.router = http.NewServeMux()
|
||||||
|
s.setupRoutes()
|
||||||
|
|
||||||
|
// Create HTTP server
|
||||||
|
s.server = &http.Server{
|
||||||
|
Addr: cfg.Server.Address,
|
||||||
|
Handler: s.router,
|
||||||
|
ConnContext: saveConnInContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts the server
|
||||||
|
func (s *Server) Start() error {
|
||||||
|
return s.server.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRouter returns the HTTP router
|
||||||
|
func (s *Server) GetRouter() *http.ServeMux {
|
||||||
|
return s.router
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServer returns the HTTP server instance
|
||||||
|
func (s *Server) GetServer() *http.Server {
|
||||||
|
return s.server
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig returns the server configuration
|
||||||
|
func (s *Server) GetConfig() *config.Config {
|
||||||
|
return s.config
|
||||||
|
}
|
||||||
0
internal/apiserver/services/.gitkeep
Normal file
0
internal/apiserver/services/.gitkeep
Normal file
0
internal/apiserver/utils/.gitkeep
Normal file
0
internal/apiserver/utils/.gitkeep
Normal file
@@ -1,29 +0,0 @@
|
|||||||
package apiserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type contextKey struct {
|
|
||||||
key string
|
|
||||||
}
|
|
||||||
|
|
||||||
var connContextKey = &contextKey{"http-conn"}
|
|
||||||
|
|
||||||
func saveConnInContext(ctx context.Context, c net.Conn) context.Context {
|
|
||||||
return context.WithValue(ctx, connContextKey, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the server
|
|
||||||
func Start() error {
|
|
||||||
srv := newServer()
|
|
||||||
server := http.Server{
|
|
||||||
Addr: ":8080",
|
|
||||||
ConnContext: saveConnInContext,
|
|
||||||
Handler: srv,
|
|
||||||
}
|
|
||||||
|
|
||||||
return server.ListenAndServe()
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package apiserver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ctxKeyRequestID = iota
|
|
||||||
)
|
|
||||||
|
|
||||||
type server struct {
|
|
||||||
router *mux.Router
|
|
||||||
logger *logrus.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
s.router.ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newServer() *server {
|
|
||||||
s := &server{
|
|
||||||
router: mux.NewRouter(),
|
|
||||||
logger: logrus.New(),
|
|
||||||
}
|
|
||||||
s.configureRouter()
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) configureRouter() {
|
|
||||||
s.router.Use(s.setRequestID)
|
|
||||||
s.router.Use(s.logRequest)
|
|
||||||
s.router.HandleFunc("/", s.handleHome())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) handleHome() http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte("ok"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) logRequest(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
logger := logrus.WithFields(logrus.Fields{
|
|
||||||
"remote_addr": r.RemoteAddr,
|
|
||||||
"request_id": r.Context().Value(ctxKeyRequestID),
|
|
||||||
})
|
|
||||||
logger.Infof("started %s %s", r.Method, r.RequestURI)
|
|
||||||
start := time.Now()
|
|
||||||
rw := newResponseWriter(w)
|
|
||||||
rw.code = http.StatusOK
|
|
||||||
next.ServeHTTP(rw, r)
|
|
||||||
logger.Infof("completed with %d %s in %v", rw.code, http.StatusText(rw.code), time.Since(start))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) setRequestID(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
id := uuid.New().String()
|
|
||||||
w.Header().Set("X-Request-ID", id)
|
|
||||||
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), ctxKeyRequestID, id)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers
|
|
||||||
func (s *server) error(w http.ResponseWriter, r *http.Request, status int, err error) {
|
|
||||||
s.respond(w, r, status, map[string]string{"error": err.Error()})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) respond(w http.ResponseWriter, r *http.Request, status int, data interface{}) {
|
|
||||||
w.WriteHeader(status)
|
|
||||||
if data != nil {
|
|
||||||
json.NewEncoder(w).Encode(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
internal/pkg/errors/.gitkeep
Normal file
0
internal/pkg/errors/.gitkeep
Normal file
0
internal/pkg/validators/.gitkeep
Normal file
0
internal/pkg/validators/.gitkeep
Normal file
@@ -1,4 +1,4 @@
|
|||||||
package apiserver
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -22,8 +22,13 @@ func (w *ResponseWriter) WriteHeader(statusCode int) {
|
|||||||
w.ResponseWriter.WriteHeader(statusCode)
|
w.ResponseWriter.WriteHeader(statusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetStatusCode returns the status code that was written
|
||||||
|
func (w *ResponseWriter) GetStatusCode() int {
|
||||||
|
return w.code
|
||||||
|
}
|
||||||
|
|
||||||
// Get new RW
|
// Get new RW
|
||||||
func newResponseWriter(w http.ResponseWriter) *ResponseWriter {
|
func NewResponseWriter(w http.ResponseWriter) *ResponseWriter {
|
||||||
hijacker, _ := w.(http.Hijacker)
|
hijacker, _ := w.(http.Hijacker)
|
||||||
return &ResponseWriter{
|
return &ResponseWriter{
|
||||||
ResponseWriter: httpsnoop.Wrap(w, httpsnoop.Hooks{}),
|
ResponseWriter: httpsnoop.Wrap(w, httpsnoop.Hooks{}),
|
||||||
15
scripts/build.sh
Executable file
15
scripts/build.sh
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Building MyGoServer..."
|
||||||
|
|
||||||
|
# Create build directory
|
||||||
|
mkdir -p build/bin
|
||||||
|
|
||||||
|
# Build the binary
|
||||||
|
echo "Building apiserver binary..."
|
||||||
|
go build -o build/bin/apiserver ./cmd/apiserver
|
||||||
|
|
||||||
|
echo "Build completed successfully!"
|
||||||
|
echo "Binary location: build/bin/apiserver"
|
||||||
101
scripts/test.sh
Executable file
101
scripts/test.sh
Executable file
@@ -0,0 +1,101 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🧪 Running tests for MyGoServer..."
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Function to print colored output
|
||||||
|
print_status() {
|
||||||
|
local status=$1
|
||||||
|
local message=$2
|
||||||
|
if [ "$status" = "PASS" ]; then
|
||||||
|
echo -e "${GREEN}✅ $message${NC}"
|
||||||
|
elif [ "$status" = "FAIL" ]; then
|
||||||
|
echo -e "${RED}❌ $message${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}⚠️ $message${NC}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 1. Run all unit tests
|
||||||
|
echo -e "${YELLOW}Running all unit tests...${NC}"
|
||||||
|
go test -v ./internal/apiserver/...
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
print_status "PASS" "Unit tests passed"
|
||||||
|
else
|
||||||
|
print_status "FAIL" "Unit tests failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Run tests with coverage
|
||||||
|
echo -e "${YELLOW}Running tests with coverage...${NC}"
|
||||||
|
go test -coverprofile=coverage.out ./...
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
print_status "PASS" "Coverage tests completed"
|
||||||
|
|
||||||
|
# Generate coverage report
|
||||||
|
go tool cover -html=coverage.out -o coverage.html 2>/dev/null || true
|
||||||
|
if [ -f "coverage.html" ]; then
|
||||||
|
echo -e "${GREEN}📊 Coverage report generated: coverage.html${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show coverage percentage
|
||||||
|
go tool cover -func=coverage.out | tail -1
|
||||||
|
else
|
||||||
|
print_status "FAIL" "Coverage tests failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Run tests with race detector (optional, takes longer)
|
||||||
|
echo -e "${YELLOW}Running race detector tests...${NC}"
|
||||||
|
echo "This may take longer - press Ctrl+C to skip, or wait..."
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
if go test -race ./internal/apiserver/... > /dev/null 2>&1; then
|
||||||
|
print_status "PASS" "Race detector tests passed"
|
||||||
|
else
|
||||||
|
print_status "WARN" "Race detector tests completed (some may have warnings)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Run vet (static analysis)
|
||||||
|
echo -e "${YELLOW}Running go vet (static analysis)...${NC}"
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
print_status "PASS" "Static analysis passed"
|
||||||
|
else
|
||||||
|
print_status "WARN" "Static analysis completed with warnings"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. Check formatting
|
||||||
|
echo -e "${YELLOW}Checking code formatting...${NC}"
|
||||||
|
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
|
||||||
|
print_status "FAIL" "Code formatting issues found:"
|
||||||
|
gofmt -s -l .
|
||||||
|
echo "Run 'gofmt -s -w .' to fix formatting"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
print_status "PASS" "Code formatting is correct"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. Run specific test patterns (if specified)
|
||||||
|
if [ "$1" != "" ]; then
|
||||||
|
echo -e "${YELLOW}Running specific tests: $1${NC}"
|
||||||
|
go test -v ./internal/apiserver/... -run="$1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}🎉 All tests completed successfully!${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "📝 Usage examples:"
|
||||||
|
echo " ./scripts/test.sh # Run all tests"
|
||||||
|
echo " ./scripts/test.sh \"TestHome\" # Run specific test pattern"
|
||||||
|
echo " go test ./... # Run all tests manually"
|
||||||
|
echo " go test -v ./internal/apiserver/ # Run specific package tests"
|
||||||
|
echo " go test -race ./... # Run with race detector"
|
||||||
Reference in New Issue
Block a user