Initial Commit
This commit is contained in:
commit
228e60f5b9
|
@ -0,0 +1,105 @@
|
|||
# Application Overview:
|
||||
I want to build a back-end application using Go that provides an API for managing boxes and items stored in those boxes. The app should be hosted in a Docker container and use SQLite3 as the database. Additionally, I want a config.yaml file to manage configuration (database path, JWT secret, and image storage directory). It should also support JWT-based authentication with a default user of 'boxuser' and password 'boxuser'.
|
||||
|
||||
I would like it to log all logins, box creation/deletion, and item creation/deletion to a local log file, specified in config.yaml.
|
||||
|
||||
# Database Tables:
|
||||
- `boxes`: A table containing an ID and a name.
|
||||
- `items`: A table containing an item name, description, the ID of the box it is stored in, and an optional path to an image of the item.
|
||||
- `users`: A table containing usernames and passwords (hashed) for authentication.
|
||||
|
||||
# API Endpoints:
|
||||
1. Authentication:
|
||||
- POST `/login`: Authenticates a user and returns a JWT.
|
||||
2. Boxes:
|
||||
- GET `/boxes`: Retrieves all boxes.
|
||||
- POST `/boxes`: Creates a new box.
|
||||
3. Items:
|
||||
- GET `/items`: Retrieves all items, optionally searchable by description.
|
||||
- POST `/items`: Adds a new item to a box.
|
||||
- GET `/items/{id}`: Retrieves an item by its ID.
|
||||
- PUT `/items/{id}`: Updates an existing item.
|
||||
- GET `/items/{id}/items`: Retrieves all items in box with this id.
|
||||
- DELETE `/items/{id}`: Deletes an item by its ID.
|
||||
- GET `/items/{id}/image`: Retrieves the image of an item.
|
||||
|
||||
# Additional Details:
|
||||
- If the database doesn’t exist, it should be created automatically when the app starts.
|
||||
- Images should be stored locally, and their paths should be saved in the database.
|
||||
- The default user for the app should be 'boxuser' with a password of 'boxuser'.
|
||||
|
||||
Here's clarification in yaml format:
|
||||
|
||||
```yaml
|
||||
app_overview:
|
||||
language: Go
|
||||
database: SQLite3
|
||||
docker: true
|
||||
authentication: JWT
|
||||
config_file: config.yaml
|
||||
|
||||
database_tables:
|
||||
boxes:
|
||||
columns:
|
||||
- id
|
||||
- name
|
||||
items:
|
||||
columns:
|
||||
- id
|
||||
- name
|
||||
- description
|
||||
- box_id
|
||||
- image_path
|
||||
users:
|
||||
columns:
|
||||
- id
|
||||
- username
|
||||
- password
|
||||
|
||||
api_endpoints:
|
||||
login:
|
||||
method: POST
|
||||
path: /login
|
||||
description: "Authenticate a user and return a JWT."
|
||||
boxes:
|
||||
- method: GET
|
||||
path: /boxes
|
||||
description: "Retrieve all boxes."
|
||||
- method: POST
|
||||
path: /boxes
|
||||
description: "Create a new box."
|
||||
items:
|
||||
- method: GET
|
||||
path: /items
|
||||
description: "Retrieve all items, searchable by description."
|
||||
- method: POST
|
||||
path: /items
|
||||
description: "Add a new item to a box."
|
||||
- method: GET
|
||||
path: /items/{id}
|
||||
description: "Retrieve an item by its ID."
|
||||
- method: GET
|
||||
path: /items/{id}/items
|
||||
description: "Retrieve all items in box with this id."
|
||||
- method: PUT
|
||||
path: /items/{id}
|
||||
description: "Update an existing item."
|
||||
- method: DELETE
|
||||
path: /items/{id}
|
||||
description: "Delete an item by its ID."
|
||||
- method: GET
|
||||
path: /items/{id}/image
|
||||
description: "Retrieve the image of an item."
|
||||
|
||||
config_file:
|
||||
database_path: "data/boxes.db"
|
||||
jwt_secret: "super_secret_key"
|
||||
image_storage_dir: "images/"
|
||||
listening_port: 8080
|
||||
log_file: "boxes.log"
|
||||
|
||||
default_user:
|
||||
username: "boxuser"
|
||||
password: "boxuser"
|
||||
|
||||
```
|
|
@ -0,0 +1,22 @@
|
|||
FROM golang:1.21-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the source code
|
||||
COPY . .
|
||||
|
||||
# Build the Go application
|
||||
RUN go mod tidy
|
||||
RUN go build -o main .
|
||||
|
||||
# Mount the data directory
|
||||
VOLUME /app/data
|
||||
|
||||
# Copy config.yaml from the application directory
|
||||
COPY config.yaml /app/
|
||||
|
||||
# Expose the port your application listens on
|
||||
EXPOSE 8080
|
||||
|
||||
# Command to run your application
|
||||
CMD ["/app/main"]
|
|
@ -0,0 +1,41 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Define the Config struct
|
||||
type Config struct {
|
||||
DatabasePath string `yaml:"database_path"`
|
||||
TestDatabasePath string `yaml:"test_database_path"`
|
||||
JWTSecret string `yaml:"jwt_secret"`
|
||||
ImageStorageDir string `yaml:"image_storage_dir"`
|
||||
ListeningPort int `yaml:"listening_port"`
|
||||
LogFile string `yaml:"log_file"`
|
||||
}
|
||||
|
||||
func LoadConfig(configPath string) (*Config, error) {
|
||||
data, err := ioutil.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read config file: %v", err)
|
||||
}
|
||||
|
||||
var config Config
|
||||
err = yaml.Unmarshal(data, &config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal config file: %v", err)
|
||||
}
|
||||
|
||||
// Get the database path from environment variables or use the default
|
||||
dbPath := os.Getenv("TEST_DATABASE_PATH")
|
||||
if dbPath != "" {
|
||||
fmt.Println("Using test database path from environment variable:", dbPath)
|
||||
config.DatabasePath = dbPath
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
database_path: "data/boxes.db"
|
||||
test_database_path: "data/test_database.db"
|
||||
jwt_secret: "super_secret_key"
|
||||
image_storage_dir: "images/"
|
||||
listening_port: 8080
|
||||
log_file: "boxes.log"
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,42 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
)
|
||||
|
||||
// Define the Box model
|
||||
type Box struct {
|
||||
gorm.Model
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Define the Item model
|
||||
type Item struct {
|
||||
gorm.Model
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
BoxID uint `json:"box_id"`
|
||||
ImagePath *string `json:"image_path"`
|
||||
}
|
||||
|
||||
// Define the User model
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func ConnectDB(dbPath string) (*gorm.DB, error) {
|
||||
db, err := gorm.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to database: %v", err)
|
||||
}
|
||||
|
||||
// AutoMigrate will create the tables if they don't exist
|
||||
db.AutoMigrate(&Box{}, &Item{}, &User{})
|
||||
|
||||
return db, nil
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
module boxes-api
|
||||
|
||||
go 1.21.1
|
||||
|
||||
require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/jinzhu/gorm v1.9.16
|
||||
github.com/stretchr/testify v1.9.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
|
@ -0,0 +1,48 @@
|
|||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
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/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
|
||||
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
|
||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd h1:GGJVjV8waZKRHrgwvtH66z9ZGVurTD1MT0n1Bb+q4aM=
|
||||
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -0,0 +1,254 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// Define contextKey globally within the package
|
||||
type contextKey string
|
||||
|
||||
// Define your key as a constant of the custom type
|
||||
const userKey contextKey = "user"
|
||||
|
||||
// LoginRequest represents the request body for the /login endpoint.
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// LoginResponse represents the response body for the /login endpoint.
|
||||
type LoginResponse struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// loginHandler handles the /login endpoint.
|
||||
func LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var req LoginRequest
|
||||
fmt.Println(db, config)
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the user exists and the password matches
|
||||
var user User
|
||||
db.Where("username = ?", req.Username).First(&user)
|
||||
if user.ID == 0 || user.Password != req.Password {
|
||||
http.Error(w, "Invalid username or password", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate JWT token
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"username": user.Username,
|
||||
"exp": time.Now().Add(time.Hour * 24).Unix(), // Token expires in 24 hours
|
||||
})
|
||||
|
||||
tokenString, err := token.SignedString([]byte(config.JWTSecret))
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Return the token in the response
|
||||
json.NewEncoder(w).Encode(LoginResponse{Token: tokenString})
|
||||
}
|
||||
|
||||
// getBoxesHandler handles the GET /boxes endpoint.
|
||||
func GetBoxesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var boxes []Box
|
||||
db.Find(&boxes)
|
||||
json.NewEncoder(w).Encode(boxes)
|
||||
}
|
||||
|
||||
// createBoxHandler handles the POST /boxes endpoint.
|
||||
func CreateBoxHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var box Box
|
||||
err := json.NewDecoder(r.Body).Decode(&box)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
db.Create(&box)
|
||||
|
||||
// Create a response struct to include the ID
|
||||
type createBoxResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
response := createBoxResponse{
|
||||
ID: box.ID,
|
||||
Name: box.Name,
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// deleteBoxHandler handles the DELETE /boxes/{id} endpoint.
|
||||
func DeleteBoxHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
// Retrieve the box from the database
|
||||
var box Box
|
||||
if err := db.First(&box, id).Error; err != nil {
|
||||
http.Error(w, "Box not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Optionally, delete associated items (if you want cascading delete)
|
||||
// db.Where("box_id = ?", id).Delete(&Item{})
|
||||
|
||||
// Delete the box
|
||||
db.Delete(&box)
|
||||
|
||||
w.WriteHeader(http.StatusNoContent) // 204 No Content
|
||||
}
|
||||
|
||||
// getItemsHandler handles the GET /items endpoint.
|
||||
func GetItemsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var items []Item
|
||||
db.Find(&items)
|
||||
json.NewEncoder(w).Encode(items)
|
||||
}
|
||||
|
||||
// createItemHandler handles the POST /items endpoint.
|
||||
func CreateItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var item Item
|
||||
err := json.NewDecoder(r.Body).Decode(&item)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
db.Create(&item)
|
||||
|
||||
// Create a response struct to include the ID
|
||||
type createItemResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
response := createItemResponse{
|
||||
ID: item.ID,
|
||||
Name: item.Name,
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
// getItemHandler handles the GET /items/{id} endpoint.
|
||||
func GetItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
var item Item
|
||||
if err := db.First(&item, id).Error; err != nil {
|
||||
http.Error(w, "Item not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(item)
|
||||
}
|
||||
|
||||
// getItemsInBoxHandler handles the GET /items/{id}/items endpoint.
|
||||
func GetItemsInBoxHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
var items []Item
|
||||
if err := db.Where("box_id = ?", id).Find(&items).Error; err != nil {
|
||||
http.Error(w, "Items not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(items)
|
||||
}
|
||||
|
||||
// updateItemHandler handles the PUT /items/{id} endpoint.
|
||||
func UpdateItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
var item Item
|
||||
if err := db.First(&item, id).Error; err != nil {
|
||||
http.Error(w, "Item not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&item)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
db.Save(&item)
|
||||
json.NewEncoder(w).Encode(item)
|
||||
}
|
||||
|
||||
// deleteItemHandler handles the DELETE /items/{id} endpoint.
|
||||
func DeleteItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
|
||||
var item Item
|
||||
if err := db.First(&item, id).Error; err != nil {
|
||||
http.Error(w, "Item not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
db.Delete(&item)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// authMiddleware is a middleware function that checks for a valid JWT token in the request header.
|
||||
func AuthMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Get the token from the request header
|
||||
tokenString := r.Header.Get("Authorization")
|
||||
if tokenString == "" {
|
||||
http.Error(w, "Authorization header missing", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove "Bearer " prefix from token string
|
||||
tokenString = strings.Replace(tokenString, "Bearer ", "", 1)
|
||||
|
||||
// Parse and validate the JWT token
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
// Make sure that the signing method is HMAC
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(config.JWTSecret), nil
|
||||
})
|
||||
if err != nil || !token.Valid {
|
||||
http.Error(w, "Invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the user claims from the token
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
||||
// Add the "user" claim to the request context
|
||||
newCtx := context.WithValue(r.Context(), userKey, claims["username"])
|
||||
r = r.WithContext(newCtx)
|
||||
} else {
|
||||
http.Error(w, "Invalid token claims", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Call the next handler in the chain
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
db *gorm.DB // Declare db globally
|
||||
config *Config
|
||||
)
|
||||
|
||||
func main() {
|
||||
var err error
|
||||
config, err = LoadConfig("config.yaml")
|
||||
fmt.Println(config.DatabasePath)
|
||||
fmt.Println(config.ImageStorageDir)
|
||||
fmt.Println(config.JWTSecret)
|
||||
fmt.Println(config.LogFile)
|
||||
fmt.Println(config.ListeningPort)
|
||||
if err != nil || config == nil {
|
||||
log.Fatalf("Failed to load config: %v", err)
|
||||
}
|
||||
|
||||
// Conne:ct to the database
|
||||
db, err = ConnectDB(config.DatabasePath)
|
||||
fmt.Println("DB Connection String:", db.DB().Ping())
|
||||
if err != nil || db == nil {
|
||||
log.Fatalf("Failed to connect to database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
fmt.Println("Default user 'boxuser' created successfully!")
|
||||
|
||||
// Create the router
|
||||
router := mux.NewRouter()
|
||||
|
||||
// Apply JWT authentication middleware to protected endpoints
|
||||
router.Handle("/login", http.HandlerFunc(LoginHandler)).Methods("POST")
|
||||
router.Handle("/boxes", AuthMiddleware(http.HandlerFunc(GetBoxesHandler))).Methods("GET")
|
||||
router.Handle("/boxes", AuthMiddleware(http.HandlerFunc(CreateBoxHandler))).Methods("POST")
|
||||
router.Handle("/boxes/{id}", AuthMiddleware(http.HandlerFunc(DeleteBoxHandler))).Methods("DELETE")
|
||||
router.Handle("/items", AuthMiddleware(http.HandlerFunc(GetItemsHandler))).Methods("GET")
|
||||
router.Handle("/items", AuthMiddleware(http.HandlerFunc(CreateItemHandler))).Methods("POST")
|
||||
router.Handle("/items/{id}", AuthMiddleware(http.HandlerFunc(GetItemHandler))).Methods("GET")
|
||||
router.Handle("/items/{id}/items", AuthMiddleware(http.HandlerFunc(GetItemsInBoxHandler))).Methods("GET")
|
||||
router.Handle("/items/{id}", AuthMiddleware(http.HandlerFunc(UpdateItemHandler))).Methods("PUT")
|
||||
router.Handle("/items/{id}", AuthMiddleware(http.HandlerFunc(DeleteItemHandler))).Methods("DELETE")
|
||||
|
||||
// Start the server
|
||||
fmt.Printf("Server listening on port %d\n", config.ListeningPort)
|
||||
http.ListenAndServe(fmt.Sprintf(":%d", config.ListeningPort), router)
|
||||
}
|
|
@ -0,0 +1,348 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// Load the configuration
|
||||
var err error
|
||||
config, err = LoadConfig("config.yaml")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load config: %v", err)
|
||||
}
|
||||
|
||||
// Set the environment variable for the test database
|
||||
os.Setenv("TEST_DATABASE_PATH", "data/my_test_database.db")
|
||||
config.DatabasePath = os.Getenv("TEST_DATABASE_PATH")
|
||||
|
||||
// Connect to the database using the test database path
|
||||
db, err = ConnectDB(config.DatabasePath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to test database: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("DB is connected")
|
||||
|
||||
defer db.Close()
|
||||
|
||||
// Truncate tables before running tests
|
||||
for _, table := range []string{"boxes", "items", "users"} { // Add all your table names here
|
||||
if err := db.Exec(fmt.Sprintf("DELETE FROM %s", table)).Error; err != nil {
|
||||
log.Fatalf("Failed to truncate table %s: %v", table, err)
|
||||
}
|
||||
}
|
||||
|
||||
db.LogMode(true)
|
||||
|
||||
// Run the tests
|
||||
exitCode := m.Run()
|
||||
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
func TestGetBoxes(t *testing.T) {
|
||||
|
||||
// 1. Create a request (no need for a real token in testing)
|
||||
req := httptest.NewRequest("GET", "/boxes", nil)
|
||||
req = req.WithContext(context.WithValue(req.Context(), userKey, "testuser")) // Simulate authenticated user
|
||||
|
||||
// 2. Create a recorder
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// 3. Initialize your router
|
||||
router := mux.NewRouter()
|
||||
router.Handle("/boxes", http.HandlerFunc(GetBoxesHandler)).Methods("GET")
|
||||
|
||||
// 4. Serve the request
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// 5. Assert the response
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
// Add more assertions to check response body, headers, etc.
|
||||
}
|
||||
|
||||
func TestCreateBox(t *testing.T) {
|
||||
// 1. Create a request with a new box in the body
|
||||
newBox := Box{Name: "Test Box"}
|
||||
reqBody, _ := json.Marshal(newBox)
|
||||
req := httptest.NewRequest("POST", "/boxes", bytes.NewBuffer(reqBody))
|
||||
req = req.WithContext(context.WithValue(req.Context(), userKey, "testuser")) // Simulate authenticated user
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// 2. Create a recorder
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// 3. Initialize your router
|
||||
router := mux.NewRouter()
|
||||
router.Handle("/boxes", http.HandlerFunc(CreateBoxHandler)).Methods("POST")
|
||||
|
||||
// 4. Serve the request
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// 5. Assert the response
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
// 6. Decode the response body
|
||||
var createdBox Box
|
||||
json.Unmarshal(rr.Body.Bytes(), &createdBox)
|
||||
|
||||
// 7. Assert the created box
|
||||
assert.Equal(t, newBox.Name, createdBox.Name)
|
||||
}
|
||||
|
||||
func TestGetItem(t *testing.T) {
|
||||
// Create a test item in the database
|
||||
testItem := Item{Name: "Test Item", Description: "Test Description", BoxID: 1}
|
||||
db.Create(&testItem)
|
||||
|
||||
// Create a request to get the test item
|
||||
req := httptest.NewRequest("GET", fmt.Sprintf("/items/%d", testItem.ID), nil)
|
||||
req = req.WithContext(context.WithValue(req.Context(), userKey, "testuser")) // Simulate authenticated user
|
||||
|
||||
// Create a response recorder
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// Initialize the router
|
||||
router := mux.NewRouter()
|
||||
router.Handle("/items/{id}", http.HandlerFunc(GetItemHandler)).Methods("GET")
|
||||
|
||||
// Serve the request
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// Check the response status code
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
// Decode the response body
|
||||
var retrievedItem Item
|
||||
err := json.Unmarshal(rr.Body.Bytes(), &retrievedItem)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check if the retrieved item matches the test item
|
||||
assert.Equal(t, testItem.ID, retrievedItem.ID)
|
||||
assert.Equal(t, testItem.Name, retrievedItem.Name)
|
||||
assert.Equal(t, testItem.Description, retrievedItem.Description)
|
||||
assert.Equal(t, testItem.BoxID, retrievedItem.BoxID)
|
||||
|
||||
fmt.Println("TestGetItem")
|
||||
}
|
||||
|
||||
func TestGetItemsInBox(t *testing.T) {
|
||||
// Create test items associated with a specific box
|
||||
testBox := Box{Name: "Test Box for Items"}
|
||||
fmt.Println("testBox.ID (before create):", testBox.ID) // Should be 0
|
||||
|
||||
if err := db.Create(&testBox).Error; err != nil { // Check for errors!
|
||||
t.Fatalf("Failed to create test box: %v", err)
|
||||
}
|
||||
|
||||
// temporarily disable callbacks
|
||||
db.Callback().Create().Replace("gorm:create", nil)
|
||||
|
||||
fmt.Println("testBox.ID (after create):", testBox.ID) // Should be a non-zero value
|
||||
|
||||
defaultImagePath := "default.jpg"
|
||||
|
||||
testItems := []Item{
|
||||
{Name: "Item 1", Description: "Description 1", BoxID: testBox.ID, ImagePath: &defaultImagePath}, // Use "" for empty string
|
||||
{Name: "Item 2", Description: "Description 2", BoxID: testBox.ID, ImagePath: &defaultImagePath}, // Use "" for empty string
|
||||
}
|
||||
|
||||
fmt.Println("Right before creating test items in database")
|
||||
|
||||
// Marshal the testItems slice to JSON
|
||||
jsonData, err := json.MarshalIndent(testItems, "", " ") // Use " " for indentation
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal testItems to JSON: %v", err)
|
||||
}
|
||||
|
||||
// Print the formatted JSON
|
||||
fmt.Println("testItems:", string(jsonData))
|
||||
|
||||
if err := db.Create(&testItems).Error; err != nil { // Check for errors!
|
||||
t.Fatalf("Failed to create test items: %v", err)
|
||||
}
|
||||
fmt.Println("Right AFTER creating test items in database")
|
||||
|
||||
// Create a request to get items in the test box
|
||||
req := httptest.NewRequest("GET", fmt.Sprintf("/items/%d/items", testBox.ID), nil)
|
||||
req = req.WithContext(context.WithValue(req.Context(), userKey, "testuser")) // Simulate authenticated user
|
||||
|
||||
// Create a response recorder
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// Initialize the router
|
||||
router := mux.NewRouter()
|
||||
router.Handle("/items/{id}/items", http.HandlerFunc(GetItemsInBoxHandler)).Methods("GET")
|
||||
|
||||
// Serve the request
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// Check the response status code
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
// Decode the response body
|
||||
var retrievedItems []Item
|
||||
err = json.Unmarshal(rr.Body.Bytes(), &retrievedItems)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check if the correct number of items is retrieved
|
||||
assert.Equal(t, len(testItems), len(retrievedItems))
|
||||
|
||||
// You can add more assertions to check the content of retrievedItems
|
||||
}
|
||||
|
||||
func TestUpdateItem(t *testing.T) {
|
||||
// Create a test item in the database
|
||||
testItem := Item{Name: "Test Item", Description: "Test Description", BoxID: 1}
|
||||
db.Create(&testItem)
|
||||
|
||||
// Create a request to update the test item
|
||||
updatedItem := Item{Name: "Updated Item", Description: "Updated Description"}
|
||||
reqBody, _ := json.Marshal(updatedItem)
|
||||
req := httptest.NewRequest("PUT", fmt.Sprintf("/items/%d", testItem.ID), bytes.NewBuffer(reqBody))
|
||||
req = req.WithContext(context.WithValue(req.Context(), userKey, "testuser")) // Simulate authenticated user
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Create a response recorder
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// Initialize the router
|
||||
router := mux.NewRouter()
|
||||
router.Handle("/items/{id}", http.HandlerFunc(UpdateItemHandler)).Methods("PUT")
|
||||
|
||||
// Serve the request
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// Check the response status code
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
// Retrieve the updated item from the database
|
||||
var dbItem Item
|
||||
db.First(&dbItem, testItem.ID)
|
||||
|
||||
// Check if the item is updated in the database
|
||||
assert.Equal(t, updatedItem.Name, dbItem.Name)
|
||||
assert.Equal(t, updatedItem.Description, dbItem.Description)
|
||||
}
|
||||
|
||||
func TestDeleteItem(t *testing.T) {
|
||||
// Create a test item in the database
|
||||
testItem := Item{Name: "Test Item", Description: "Test Description", BoxID: 1}
|
||||
db.Create(&testItem)
|
||||
|
||||
// Create a request to delete the test item
|
||||
req := httptest.NewRequest("DELETE", fmt.Sprintf("/items/%d", testItem.ID), nil)
|
||||
req = req.WithContext(context.WithValue(req.Context(), userKey, "testuser")) // Simulate authenticated user
|
||||
|
||||
// Create a response recorder
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// Initialize the router
|
||||
router := mux.NewRouter()
|
||||
router.Handle("/items/{id}", http.HandlerFunc(DeleteItemHandler)).Methods("DELETE")
|
||||
|
||||
// Serve the request
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// Check the response status code
|
||||
assert.Equal(t, http.StatusNoContent, rr.Code)
|
||||
|
||||
// Try to retrieve the deleted item from the database
|
||||
var deletedItem Item
|
||||
err := db.First(&deletedItem, testItem.ID).Error
|
||||
assert.Error(t, err) // Expect an error because the item should be deleted
|
||||
}
|
||||
|
||||
func TestCreateItem(t *testing.T) {
|
||||
// 1. Create a request with a new item in the body
|
||||
newItem := Item{Name: "Test Item", Description: "Test Description", BoxID: 1}
|
||||
reqBody, _ := json.Marshal(newItem)
|
||||
req := httptest.NewRequest("POST", "/items", bytes.NewBuffer(reqBody))
|
||||
req = req.WithContext(context.WithValue(req.Context(), userKey, "testuser")) // Simulate authenticated user
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// 2. Create a recorder
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// 3. Initialize your router
|
||||
router := mux.NewRouter()
|
||||
router.Handle("/items", http.HandlerFunc(CreateItemHandler)).Methods("POST")
|
||||
|
||||
// 4. Serve the request
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// 5. Assert the response
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
// 6. Decode the response body
|
||||
var createdItem Item
|
||||
json.Unmarshal(rr.Body.Bytes(), &createdItem)
|
||||
|
||||
// 7. Assert the created item
|
||||
assert.Equal(t, newItem.Name, createdItem.Name)
|
||||
assert.Equal(t, newItem.Description, createdItem.Description)
|
||||
assert.Equal(t, newItem.BoxID, createdItem.BoxID)
|
||||
}
|
||||
|
||||
func TestGetItems(t *testing.T) {
|
||||
// 1. Create a request (no need for a real token in testing)
|
||||
req := httptest.NewRequest("GET", "/items", nil)
|
||||
req = req.WithContext(context.WithValue(req.Context(), userKey, "testuser")) // Simulate authenticated user
|
||||
|
||||
// 2. Create a recorder
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// 3. Initialize your router
|
||||
router := mux.NewRouter()
|
||||
router.Handle("/items", http.HandlerFunc(GetItemsHandler)).Methods("GET")
|
||||
|
||||
// 4. Serve the request
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
// 5. Assert the response
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
// Add more assertions to check response body, headers, etc.
|
||||
}
|
||||
|
||||
func ExampleLoginHandler() {
|
||||
// Create a request with login credentials
|
||||
loginReq := LoginRequest{
|
||||
Username: "testuser",
|
||||
Password: "testpassword",
|
||||
}
|
||||
reqBody, _ := json.Marshal(loginReq)
|
||||
|
||||
req := httptest.NewRequest("POST", "/login", bytes.NewBuffer(reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Create a response recorder
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// Create a test handler (usually your LoginHandler)
|
||||
handler := http.HandlerFunc(LoginHandler)
|
||||
|
||||
// Serve the request
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
// Check the response status code
|
||||
if rr.Code != http.StatusOK {
|
||||
fmt.Printf("Login failed with status code: %d\n", rr.Code)
|
||||
} else {
|
||||
// Decode the response body to get the token
|
||||
var loginResp LoginResponse
|
||||
json.Unmarshal(rr.Body.Bytes(), &loginResp)
|
||||
|
||||
fmt.Println("Login successful! Token:", loginResp.Token)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
#!/bin/bash
|
||||
|
||||
# API base URL
|
||||
API_BASE_URL="http://localhost:8080"
|
||||
|
||||
# Login credentials
|
||||
USERNAME="boxuser"
|
||||
PASSWORD="boxuser"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to make an authenticated request
|
||||
function authenticated_request() {
|
||||
local method=$1
|
||||
local endpoint=$2
|
||||
local data=$3
|
||||
|
||||
# Get a new JWT token
|
||||
TOKEN=$(curl -s -X POST -H "Content-Type: application/json" \
|
||||
-d "{\"username\":\"$USERNAME\", \"password\":\"$PASSWORD\"}" \
|
||||
"$API_BASE_URL/login" | jq -r '.token')
|
||||
|
||||
# Make the authenticated request
|
||||
curl -s -X $method -H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data" \
|
||||
"$API_BASE_URL$endpoint"
|
||||
}
|
||||
|
||||
# --- Test Cases ---
|
||||
|
||||
# 1. Login
|
||||
echo
|
||||
echo
|
||||
echo "Testing /login..."
|
||||
response=$(curl -s -X POST -H "Content-Type: application/json" \
|
||||
-d "{\"username\":\"$USERNAME\", \"password\":\"$PASSWORD\"}" \
|
||||
"$API_BASE_URL/login")
|
||||
|
||||
if [[ $(echo "$response" | jq -r '.token') != "null" ]]; then
|
||||
echo -e " /login: ${GREEN}PASS${NC}"
|
||||
else
|
||||
echo -e " /login: ${RED}FAIL${NC} (Invalid response)"
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 2. Get Boxes
|
||||
echo
|
||||
echo
|
||||
echo "Testing /boxes (GET)..."
|
||||
response=$(authenticated_request "GET" "/boxes" "")
|
||||
|
||||
if [[ $(echo "$response" | jq -r '. | length') -ge 0 ]]; then
|
||||
echo -e " /boxes (GET): ${GREEN}PASS${NC}"
|
||||
else
|
||||
echo -e " /boxes (GET): ${RED}FAIL${NC} (Invalid response)"
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 3. Create Box
|
||||
echo
|
||||
echo
|
||||
echo "Testing /boxes (POST)..."
|
||||
response=$(authenticated_request "POST" "/boxes" "{\"name\":\"Test Box\"}")
|
||||
echo $response | jq '.'
|
||||
|
||||
if [[ $(echo "$response" | jq -r '.name') == "Test Box" ]]; then
|
||||
echo -e " /boxes (POST): ${GREEN}PASS${NC}"
|
||||
echo $response
|
||||
BOX_ID=$(echo "$response" | jq -r '.id') # Extract and save the box ID
|
||||
echo $BOX_ID | jq .
|
||||
else
|
||||
echo -e " /boxes (POST): ${RED}FAIL${NC} (Invalid response)"
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 5. Create Item (Assuming a box with ID $BOX_ID exists)
|
||||
echo
|
||||
echo
|
||||
echo "Testing /items (POST)..."
|
||||
echo $BOX_ID
|
||||
response=$(authenticated_request "POST" "/items" "{\"name\":\"Test Item\", \"description\":\"Test Description\", \"box_id\":$BOX_ID}")
|
||||
if [[ $(echo "$response" | jq -r '.name') == "Test Item" ]]; then
|
||||
echo -e " /items (POST): ${GREEN}PASS${NC}"
|
||||
ITEM_ID=$(echo "$response" | jq -r '.id') # Extract and save the item ID
|
||||
echo $response
|
||||
else
|
||||
echo -e " /items (POST): ${RED}FAIL${NC} (Invalid response)"
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 6. Get Items
|
||||
echo
|
||||
echo
|
||||
echo "Testing /items (GET)..."
|
||||
response=$(authenticated_request "GET" "/items" "")
|
||||
|
||||
if [[ $(echo "$response" | jq -r '. | length') -ge 0 ]]; then
|
||||
echo -e " /items (GET): ${GREEN}PASS${NC}"
|
||||
else
|
||||
echo -e " /items (GET): ${RED}FAIL${NC} (Invalid response)"
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 7. Get Item by ID (Using the saved ITEM_ID)
|
||||
echo
|
||||
echo
|
||||
echo "Testing /items/{id} (GET)..."
|
||||
response=$(authenticated_request "GET" "/items/$ITEM_ID" "")
|
||||
echo $response | jq .
|
||||
|
||||
if [[ $(echo "$response" | jq -r '.ID') == "$ITEM_ID" ]]; then
|
||||
echo -e " /items/{id} (GET): ${GREEN}PASS${NC}"
|
||||
else
|
||||
echo -e " /items/{id} (GET): ${RED}FAIL${NC} (Invalid response)"
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 8. Get Items in Box (Using the saved BOX_ID)
|
||||
echo
|
||||
echo
|
||||
echo "Testing /items/{id}/items (GET)..."
|
||||
response=$(authenticated_request "GET" "/items/$BOX_ID/items" "")
|
||||
|
||||
if [[ $(echo "$response" | jq -r '. | length') -ge 1 ]]; then # Expecting at least one item
|
||||
echo -e " /items/{id}/items (GET): ${GREEN}PASS${NC}"
|
||||
else
|
||||
echo -e " /items/{id}/items (GET): ${RED}FAIL${NC} (Invalid response)"
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 9. Update Item (Using the saved ITEM_ID)
|
||||
echo
|
||||
echo
|
||||
echo "Testing /items/{id} (PUT)..."
|
||||
response=$(authenticated_request "PUT" "/items/$ITEM_ID" "{\"name\":\"Updated Item\", \"description\":\"Updated Description\"}")
|
||||
|
||||
if [[ $(echo "$response" | jq -r '.name') == "Updated Item" ]]; then
|
||||
echo -e " /items/{id} (PUT): ${GREEN}PASS${NC}"
|
||||
else
|
||||
echo -e " /items/{id} (PUT): ${RED}FAIL${NC} (Invalid response)"
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 10. Delete Item (Using the saved ITEM_ID)
|
||||
echo
|
||||
echo
|
||||
echo "Testing /items/{id} (DELETE)..."
|
||||
response=$(authenticated_request "DELETE" "/items/$ITEM_ID" "")
|
||||
|
||||
if [[ "$response" == "" ]]; then # Expecting 204 No Content (empty response)
|
||||
echo -e " /items/{id} (DELETE): ${GREEN}PASS${NC}"
|
||||
else
|
||||
echo -e " /items/{id} (DELETE): ${RED}FAIL${NC} (Invalid response)"
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 4. Delete Box (Using the saved BOX_ID)
|
||||
echo
|
||||
echo
|
||||
echo "Testing /boxes/{id} (DELETE)..."
|
||||
response=$(authenticated_request "DELETE" "/boxes/$BOX_ID" "")
|
||||
|
||||
if [[ "$response" == "" ]]; then # Expecting 204 No Content (empty response)
|
||||
echo -e " /boxes/{id} (DELETE): ${GREEN}PASS${NC}"
|
||||
else
|
||||
echo -e " /boxes/{id} (DELETE): ${RED}FAIL${NC} (Invalid response)"
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
echo "Tests completed."
|
|
@ -0,0 +1,176 @@
|
|||
#!/bin/bash
|
||||
|
||||
# API base URL
|
||||
API_BASE_URL="http://localhost:8080"
|
||||
|
||||
# Login credentials
|
||||
USERNAME="boxuser"
|
||||
PASSWORD="boxuser"
|
||||
|
||||
# Function to make an authenticated request
|
||||
function authenticated_request() {
|
||||
local method=$1
|
||||
local endpoint=$2
|
||||
local data=$3
|
||||
|
||||
# Get a new JWT token
|
||||
TOKEN=$(curl -s -X POST -H "Content-Type: application/json" \
|
||||
-d "{\"username\":\"$USERNAME\", \"password\":\"$PASSWORD\"}" \
|
||||
"$API_BASE_URL/login" | jq -r '.token')
|
||||
|
||||
# Make the authenticated request
|
||||
curl -s -X $method -H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data" \
|
||||
"$API_BASE_URL$endpoint"
|
||||
}
|
||||
|
||||
# --- Test Cases ---
|
||||
|
||||
# 1. Login
|
||||
echo
|
||||
echo
|
||||
echo "Testing /login..."
|
||||
response=$(curl -s -X POST -H "Content-Type: application/json" \
|
||||
-d "{\"username\":\"$USERNAME\", \"password\":\"$PASSWORD\"}" \
|
||||
"$API_BASE_URL/login")
|
||||
|
||||
if [[ $(echo "$response" | jq -r '.token') != "null" ]]; then
|
||||
echo -e "\033[32m /login: PASS\033[0m" # Green PASS
|
||||
else
|
||||
echo -e "\033[31m /login: FAIL (Invalid response)\033[0m" # Red FAIL
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 2. Get Boxes
|
||||
echo
|
||||
echo
|
||||
echo "Testing /boxes (GET)..."
|
||||
response=$(authenticated_request "GET" "/boxes" "")
|
||||
|
||||
if [[ $(echo "$response" | jq -r '. | length') -ge 0 ]]; then
|
||||
echo -e "\033[32m /boxes (GET): PASS\033[0m" # Green PASS
|
||||
else
|
||||
echo -e "\033[31m /boxes (GET): FAIL (Invalid response)\033[0m" # Red FAIL
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 3. Create Box
|
||||
echo
|
||||
echo
|
||||
echo "Testing /boxes (POST)..."
|
||||
response=$(authenticated_request "POST" "/boxes" "{\"name\":\"Test Box\"}")
|
||||
echo $response | jq '.'
|
||||
|
||||
if [[ $(echo "$response" | jq -r '.name') == "Test Box" ]]; then
|
||||
echo -e "\033[32m /boxes (POST): PASS\033[0m" # Green PASS
|
||||
echo $response
|
||||
BOX_ID=$(echo "$response" | jq -r '.id') # Extract and save the box ID
|
||||
echo $BOX_ID | jq .
|
||||
else
|
||||
echo -e "\033[31m /boxes (POST): FAIL (Invalid response)\033[0m" # Red FAIL
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 5. Create Item (Assuming a box with ID $BOX_ID exists)
|
||||
echo
|
||||
echo
|
||||
echo "Testing /items (POST)..."
|
||||
echo $BOX_ID
|
||||
response=$(authenticated_request "POST" "/items" "{\"name\":\"Test Item\", \"description\":\"Test Description\", \"box_id\":$BOX_ID}")
|
||||
if [[ $(echo "$response" | jq -r '.name') == "Test Item" ]]; then
|
||||
echo -e "\033[32m /items (POST): PASS\033[0m" # Green PASS
|
||||
ITEM_ID=$(echo "$response" | jq -r '.id') # Extract and save the item ID
|
||||
echo $response
|
||||
else
|
||||
echo -e "\033[31m /items (POST): FAIL (Invalid response)\033[0m" # Red FAIL
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 6. Get Items
|
||||
echo
|
||||
echo
|
||||
echo "Testing /items (GET)..."
|
||||
response=$(authenticated_request "GET" "/items" "")
|
||||
|
||||
if [[ $(echo "$response" | jq -r '. | length') -ge 0 ]]; then
|
||||
echo -e "\033[32m /items (GET): PASS\033[0m" # Green PASS
|
||||
else
|
||||
echo -e "\033[31m /items (GET): FAIL (Invalid response)\033[0m" # Red FAIL
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 7. Get Item by ID (Using the saved ITEM_ID)
|
||||
echo
|
||||
echo
|
||||
echo "Testing /items/{id} (GET)..."
|
||||
response=$(authenticated_request "GET" "/items/$ITEM_ID" "")
|
||||
echo $response | jq .
|
||||
|
||||
if [[ $(echo "$response" | jq -r '.ID') == "$ITEM_ID" ]]; then
|
||||
echo -e "\033[32m /items/{id} (GET): PASS\033[0m" # Green PASS
|
||||
else
|
||||
echo -e "\033[31m /items/{id} (GET): FAIL (Invalid response)\033[0m" # Red FAIL
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 8. Get Items in Box (Using the saved BOX_ID)
|
||||
echo
|
||||
echo
|
||||
echo "Testing /items/{id}/items (GET)..."
|
||||
response=$(authenticated_request "GET" "/items/$BOX_ID/items" "")
|
||||
|
||||
if [[ $(echo "$response" | jq -r '. | length') -ge 1 ]]; then # Expecting at least one item
|
||||
echo -e "\033[32m /items/{id}/items (GET): PASS\033[0m" # Green PASS
|
||||
else
|
||||
echo -e "\033[31m /items/{id}/items (GET): FAIL (Invalid response)\033[0m" # Red FAIL
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 9. Update Item (Using the saved ITEM_ID)
|
||||
echo
|
||||
echo
|
||||
echo "Testing /items/{id} (PUT)..."
|
||||
response=$(authenticated_request "PUT" "/items/$ITEM_ID" "{\"name\":\"Updated Item\", \"description\":\"Updated Description\"}")
|
||||
|
||||
if [[ $(echo "$response" | jq -r '.name') == "Updated Item" ]]; then
|
||||
echo -e "\033[32m /items/{id} (PUT): PASS\033[0m" # Green PASS
|
||||
else
|
||||
echo -e "\033[31m /items/{id} (PUT): FAIL (Invalid response)\033[0m" # Red FAIL
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 10. Delete Item (Using the saved ITEM_ID)
|
||||
echo
|
||||
echo
|
||||
echo "Testing /items/{id} (DELETE)..."
|
||||
response=$(authenticated_request "DELETE" "/items/$ITEM_ID" "")
|
||||
|
||||
if [[ "$response" == "" ]]; then # Expecting 204 No Content (empty response)
|
||||
echo -e "\033[32m /items/{id} (DELETE): PASS\033[0m" # Green PASS
|
||||
else
|
||||
echo -e "\033[31m /items/{id} (DELETE): FAIL (Invalid response)\033[0m" # Red FAIL
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# 4. Delete Box (Using the saved BOX_ID)
|
||||
echo
|
||||
echo
|
||||
echo "Testing /boxes/{id} (DELETE)..."
|
||||
response=$(authenticated_request "DELETE" "/boxes/$BOX_ID" "")
|
||||
|
||||
if [[ "$response" == "" ]]; then # Expecting 204 No Content (empty response)
|
||||
echo -e "\033[32m /boxes/{id} (DELETE): PASS\033[0m" # Green PASS
|
||||
else
|
||||
echo -e "\033[31m /boxes/{id} (DELETE): FAIL (Invalid response)\033[0m" # Red FAIL
|
||||
echo "$response"
|
||||
fi
|
||||
|
||||
# --- Add more test cases for other endpoints ---
|
||||
|
||||
# Example for GET /items/{id}
|
||||
# echo "Testing /items/{id} (GET)..."
|
||||
# response=$(authenticated_request "GET" "/items/1" "")
|
||||
# # ... (Add assertions based on the expected response)
|
||||
|
||||
echo "Tests completed."
|
Loading…
Reference in New Issue