From 8e790ef8ede7ba77eaa65cadfc89a8faecc199ca Mon Sep 17 00:00:00 2001 From: Manuel Cabezas Date: Fri, 5 Dec 2025 19:14:01 -0300 Subject: [PATCH] feat: initial project setup --- README.md | 66 ++++++++++++++++++++++++++ go.mod | 5 ++ go.sum | 2 + internal/handlers/book_handler.go | 36 ++++++++++++++ internal/handlers/book_handler_test.go | 64 +++++++++++++++++++++++++ internal/models/book.go | 7 +++ main.go | 21 ++++++++ 7 files changed, 201 insertions(+) create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/handlers/book_handler.go create mode 100644 internal/handlers/book_handler_test.go create mode 100644 internal/models/book.go create mode 100644 main.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..ed86a70 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# Go Book API Example + +This is a simple Go web service that provides a basic API for retrieving book information. It's built using the `chi` router and serves hardcoded book data from memory. + +## Project Structure + +- `main.go`: Application entry point, sets up the HTTP server and defines the `/books/{id}` endpoint. +- `internal/handlers/book_handler.go`: Contains the `BookHandler` responsible for processing requests to the `/books/{id}` endpoint. It uses an in-memory list of books. +- `internal/models/book.go`: Defines the `Book` struct used to represent book data. + +## Getting Started + +### Prerequisites + +- Go (version 1.16 or higher recommended) + +### Running the Application + +1. **Clone the repository:** + ```bash + git clone + cd testing-go-example + ``` + (Note: Replace `` with the actual repository URL if this project were hosted.) + +2. **Run the server:** + ```bash + go run main.go + ``` + The server will start on `http://localhost:3000`. + +### API Endpoints + +#### `GET /books/{id}` + +Retrieves details for a single book by its ID. + +**Example Request:** + +```bash +curl http://localhost:3000/books/1 +``` + +**Example Response:** + +```json +{ + "id": "1", + "title": "The Hitchhiker's Guide to the Galaxy", + "author": "Douglas Adams" +} +``` + +```bash +curl http://localhost:3000/books/2 +``` + +**Example Response:** + +```json +{ + "id": "2", + "title": "The Restaurant at the End of the Universe", + "author": "Douglas Adams" +} +``` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..59ad142 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.workflows.cl/mcabezas/testing-go-example + +go 1.25.4 + +require github.com/go-chi/chi/v5 v5.2.3 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5bd7be3 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= +github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= diff --git a/internal/handlers/book_handler.go b/internal/handlers/book_handler.go new file mode 100644 index 0000000..2e27e55 --- /dev/null +++ b/internal/handlers/book_handler.go @@ -0,0 +1,36 @@ +package handlers + +import ( + "encoding/json" + "net/http" + + "github.com/go-chi/chi/v5" + "git.workflows.cl/mcabezas/testing-go-example/internal/models" +) + +type BookHandler struct { + books []models.Book +} + +func NewBookHandler() *BookHandler { + return &BookHandler{ + books: []models.Book{ + {ID: "1", Title: "El Quijote", Author: "Cervantes"}, + {ID: "2", Title: "Cien años de soledad", Author: "García Márquez"}, + }, + } +} + +func (h *BookHandler) GetBook(w http.ResponseWriter, r *http.Request) { + id := chi.URLParam(r, "id") + + for _, book := range h.books { + if book.ID == id { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(book) + return + } + } + + http.Error(w, "Book not found", http.StatusNotFound) +} diff --git a/internal/handlers/book_handler_test.go b/internal/handlers/book_handler_test.go new file mode 100644 index 0000000..a148ca0 --- /dev/null +++ b/internal/handlers/book_handler_test.go @@ -0,0 +1,64 @@ +package handlers + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-chi/chi/v5" + "git.workflows.cl/mcabezas/testing-go-example/internal/models" +) + +func TestGetBook(t *testing.T) { + handler := NewBookHandler() + + req := httptest.NewRequest(http.MethodGet, "/books/1", nil) + + // Configurar contexto de chi con el parámetro id + rctx := chi.NewRouteContext() + rctx.URLParams.Add("id", "1") + req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx)) + + w := httptest.NewRecorder() + + handler.GetBook(w, req) + + // Verificar código de estado + if w.Code != http.StatusOK { + t.Errorf("esperaba status 200, obtuvo %d", w.Code) + } + + // Verificar respuesta + var book models.Book + if err := json.NewDecoder(w.Body).Decode(&book); err != nil { + t.Fatalf("error al decodificar: %v", err) + } + + if book.ID != "1" { + t.Errorf("esperaba ID '1', obtuvo '%s'", book.ID) + } + + if book.Title != "El Quijote" { + t.Errorf("esperaba título 'El Quijote', obtuvo '%s'", book.Title) + } +} + +func TestGetBook_NotFound(t *testing.T) { + handler := NewBookHandler() + + req := httptest.NewRequest(http.MethodGet, "/books/999", nil) + + rctx := chi.NewRouteContext() + rctx.URLParams.Add("id", "999") + req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx)) + + w := httptest.NewRecorder() + + handler.GetBook(w, req) + + if w.Code != http.StatusNotFound { + t.Errorf("esperaba status 404, obtuvo %d", w.Code) + } +} diff --git a/internal/models/book.go b/internal/models/book.go new file mode 100644 index 0000000..5ac4c42 --- /dev/null +++ b/internal/models/book.go @@ -0,0 +1,7 @@ +package models + +type Book struct { + ID string `json:"id"` + Title string `json:"title"` + Author string `json:"author"` +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..e0be473 --- /dev/null +++ b/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "log" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "git.workflows.cl/mcabezas/testing-go-example/internal/handlers" +) + +func main() { + r := chi.NewRouter() + r.Use(middleware.Logger) + + bookHandler := handlers.NewBookHandler() + r.Get("/books/{id}", bookHandler.GetBook) + + log.Println("Servidor en http://localhost:3000") + http.ListenAndServe(":3000", r) +}