
This commit adds a complete Go implementation of the Todo application. The application uses Gin framework for the web server, GORM for database access, and SQLite for storage. Key features: - Todo CRUD operations with the same API endpoints - Health check endpoint - Database migrations - Tests for models, services, and API handlers - Documentation for the API - Configurable settings
222 lines
5.5 KiB
Go
222 lines
5.5 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/simpletodoapp/go-todo-app/internal/dto"
|
|
"github.com/simpletodoapp/go-todo-app/internal/model"
|
|
"github.com/simpletodoapp/go-todo-app/internal/service"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// setupTestRouter sets up a test router and database
|
|
func setupTestRouter(t *testing.T) (*gin.Engine, *gorm.DB) {
|
|
gin.SetMode(gin.TestMode)
|
|
router := gin.Default()
|
|
|
|
// Create an in-memory database
|
|
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("Failed to connect to in-memory database: %v", err)
|
|
}
|
|
|
|
// Auto-migrate the schema
|
|
err = db.AutoMigrate(&model.Todo{})
|
|
if err != nil {
|
|
t.Fatalf("Failed to migrate test database: %v", err)
|
|
}
|
|
|
|
// Create services and handlers
|
|
todoService := service.NewTodoService(db)
|
|
todoHandler := NewTodoHandler(todoService)
|
|
|
|
// Register routes
|
|
api := router.Group("/api")
|
|
todoHandler.RegisterRoutes(api)
|
|
|
|
return router, db
|
|
}
|
|
|
|
// populateTestTodos adds test todos to the database
|
|
func populateTestTodos(t *testing.T, db *gorm.DB) {
|
|
todos := []model.Todo{
|
|
{
|
|
Title: "Test Todo 1",
|
|
Description: "Test Description 1",
|
|
Completed: false,
|
|
},
|
|
{
|
|
Title: "Test Todo 2",
|
|
Description: "Test Description 2",
|
|
Completed: true,
|
|
},
|
|
}
|
|
|
|
for _, todo := range todos {
|
|
if err := db.Create(&todo).Error; err != nil {
|
|
t.Fatalf("Failed to create test todo: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetTodosHandler(t *testing.T) {
|
|
router, db := setupTestRouter(t)
|
|
populateTestTodos(t, db)
|
|
|
|
// Test GET /api/todos
|
|
req, _ := http.NewRequest("GET", "/api/todos", nil)
|
|
resp := httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
if resp.Code != http.StatusOK {
|
|
t.Errorf("Expected status code %d, got %d", http.StatusOK, resp.Code)
|
|
}
|
|
|
|
var todos []model.Todo
|
|
if err := json.Unmarshal(resp.Body.Bytes(), &todos); err != nil {
|
|
t.Fatalf("Failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if len(todos) != 2 {
|
|
t.Errorf("Expected 2 todos, got %d", len(todos))
|
|
}
|
|
}
|
|
|
|
func TestGetTodoHandler(t *testing.T) {
|
|
router, db := setupTestRouter(t)
|
|
populateTestTodos(t, db)
|
|
|
|
// Test GET /api/todos/:id
|
|
req, _ := http.NewRequest("GET", "/api/todos/1", nil)
|
|
resp := httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
if resp.Code != http.StatusOK {
|
|
t.Errorf("Expected status code %d, got %d", http.StatusOK, resp.Code)
|
|
}
|
|
|
|
var todo model.Todo
|
|
if err := json.Unmarshal(resp.Body.Bytes(), &todo); err != nil {
|
|
t.Fatalf("Failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if todo.ID != 1 {
|
|
t.Errorf("Expected todo ID to be 1, got %d", todo.ID)
|
|
}
|
|
|
|
// Test non-existent todo
|
|
req, _ = http.NewRequest("GET", "/api/todos/999", nil)
|
|
resp = httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
if resp.Code != http.StatusNotFound {
|
|
t.Errorf("Expected status code %d, got %d", http.StatusNotFound, resp.Code)
|
|
}
|
|
}
|
|
|
|
func TestCreateTodoHandler(t *testing.T) {
|
|
router, _ := setupTestRouter(t)
|
|
|
|
// Create request body
|
|
todoCreate := dto.TodoCreate{
|
|
TodoBase: dto.TodoBase{
|
|
Title: "New Todo",
|
|
Description: "New Description",
|
|
Completed: false,
|
|
},
|
|
}
|
|
body, _ := json.Marshal(todoCreate)
|
|
|
|
// Test POST /api/todos
|
|
req, _ := http.NewRequest("POST", "/api/todos", bytes.NewBuffer(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp := httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
if resp.Code != http.StatusCreated {
|
|
t.Errorf("Expected status code %d, got %d", http.StatusCreated, resp.Code)
|
|
}
|
|
|
|
var todo model.Todo
|
|
if err := json.Unmarshal(resp.Body.Bytes(), &todo); err != nil {
|
|
t.Fatalf("Failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if todo.Title != "New Todo" {
|
|
t.Errorf("Expected title to be 'New Todo', got '%s'", todo.Title)
|
|
}
|
|
}
|
|
|
|
func TestUpdateTodoHandler(t *testing.T) {
|
|
router, db := setupTestRouter(t)
|
|
populateTestTodos(t, db)
|
|
|
|
// Create request body
|
|
title := "Updated Title"
|
|
completed := true
|
|
todoUpdate := dto.TodoUpdate{
|
|
Title: &title,
|
|
Completed: &completed,
|
|
}
|
|
body, _ := json.Marshal(todoUpdate)
|
|
|
|
// Test PUT /api/todos/:id
|
|
req, _ := http.NewRequest("PUT", "/api/todos/1", bytes.NewBuffer(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp := httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
if resp.Code != http.StatusOK {
|
|
t.Errorf("Expected status code %d, got %d", http.StatusOK, resp.Code)
|
|
}
|
|
|
|
var todo model.Todo
|
|
if err := json.Unmarshal(resp.Body.Bytes(), &todo); err != nil {
|
|
t.Fatalf("Failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if todo.Title != "Updated Title" {
|
|
t.Errorf("Expected title to be 'Updated Title', got '%s'", todo.Title)
|
|
}
|
|
|
|
if !todo.Completed {
|
|
t.Errorf("Expected completed to be true, got %t", todo.Completed)
|
|
}
|
|
}
|
|
|
|
func TestDeleteTodoHandler(t *testing.T) {
|
|
router, db := setupTestRouter(t)
|
|
populateTestTodos(t, db)
|
|
|
|
// Test DELETE /api/todos/:id
|
|
req, _ := http.NewRequest("DELETE", "/api/todos/1", nil)
|
|
resp := httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
if resp.Code != http.StatusNoContent {
|
|
t.Errorf("Expected status code %d, got %d", http.StatusNoContent, resp.Code)
|
|
}
|
|
|
|
// Verify the todo was deleted
|
|
var count int64
|
|
db.Model(&model.Todo{}).Where("id = ?", 1).Count(&count)
|
|
if count != 0 {
|
|
t.Errorf("Expected todo to be deleted, but it still exists")
|
|
}
|
|
|
|
// Test deleting non-existent todo
|
|
req, _ = http.NewRequest("DELETE", "/api/todos/999", nil)
|
|
resp = httptest.NewRecorder()
|
|
router.ServeHTTP(resp, req)
|
|
|
|
if resp.Code != http.StatusNotFound {
|
|
t.Errorf("Expected status code %d, got %d", http.StatusNotFound, resp.Code)
|
|
}
|
|
} |