diff --git a/api/.golangci.yml b/api/.golangci.yml new file mode 100644 index 0000000..4fe3e6c --- /dev/null +++ b/api/.golangci.yml @@ -0,0 +1,28 @@ +linters: + enable: + - errcheck + - govet + - staticcheck + - gofmt + - goimports + +run: + timeout: 5m + tests: true + +issues: + exclude-use-default: false + exclude: + - "Error return value of" + - "SA4006" + +linters-settings: + errcheck: + exclude-functions: + - fmt.Println + - bytes.Buffer.WriteString + gofmt: + simplify: true + staticcheck: + checks: + - all diff --git a/api/auth.go b/api/auth.go index d31d833..f9db406 100644 --- a/api/auth.go +++ b/api/auth.go @@ -232,54 +232,53 @@ func (app *App) LoginUser(w http.ResponseWriter, r *http.Request) { } } - func (app *App) VerifySessionToken(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Get the session token from the cookie - cookie, err := r.Cookie("token") - if err != nil { - if err == http.ErrNoCookie { - // If the cookie is not set, return an unauthorized status - w.WriteHeader(http.StatusUnauthorized) - if encodeErr := json.NewEncoder(w).Encode(ErrorResponse{Message: "Unauthorized access"}); encodeErr != nil { - app.Logger.Printf("Error encoding JSON: %v", encodeErr) - } - return - } - // For any other type of error, return a bad request status - w.WriteHeader(http.StatusBadRequest) - if encodeErr := json.NewEncoder(w).Encode(ErrorResponse{Message: "Bad request"}); encodeErr != nil { - app.Logger.Printf("Error encoding JSON: %v", encodeErr) - } - return - } - - // Retrieve the session token from the cookie - sessionToken := cookie.Value - - // Get the session from the store - session, exists := sessionStore.Get(sessionToken) - if !exists { - // If the session token is not valid, return unauthorized - w.WriteHeader(http.StatusUnauthorized) - if encodeErr := json.NewEncoder(w).Encode(ErrorResponse{Message: "Invalid session token"}); encodeErr != nil { - app.Logger.Printf("Error encoding JSON: %v", encodeErr) - } - return - } - - // Check if the session has expired - if session.ExpiresAt.Before(time.Now()) { - // If the session is expired, return unauthorized - w.WriteHeader(http.StatusUnauthorized) - if encodeErr := json.NewEncoder(w).Encode(ErrorResponse{Message: "Session expired"}); encodeErr != nil { - app.Logger.Printf("Error encoding JSON: %v", encodeErr) - } - return - } - - next.ServeHTTP(w, r) - }) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Get the session token from the cookie + cookie, err := r.Cookie("token") + if err != nil { + if err == http.ErrNoCookie { + // If the cookie is not set, return an unauthorized status + w.WriteHeader(http.StatusUnauthorized) + if encodeErr := json.NewEncoder(w).Encode(ErrorResponse{Message: "Unauthorized access"}); encodeErr != nil { + app.Logger.Printf("Error encoding JSON: %v", encodeErr) + } + return + } + // For any other type of error, return a bad request status + w.WriteHeader(http.StatusBadRequest) + if encodeErr := json.NewEncoder(w).Encode(ErrorResponse{Message: "Bad request"}); encodeErr != nil { + app.Logger.Printf("Error encoding JSON: %v", encodeErr) + } + return + } + + // Retrieve the session token from the cookie + sessionToken := cookie.Value + + // Get the session from the store + session, exists := sessionStore.Get(sessionToken) + if !exists { + // If the session token is not valid, return unauthorized + w.WriteHeader(http.StatusUnauthorized) + if encodeErr := json.NewEncoder(w).Encode(ErrorResponse{Message: "Invalid session token"}); encodeErr != nil { + app.Logger.Printf("Error encoding JSON: %v", encodeErr) + } + return + } + + // Check if the session has expired + if session.ExpiresAt.Before(time.Now()) { + // If the session is expired, return unauthorized + w.WriteHeader(http.StatusUnauthorized) + if encodeErr := json.NewEncoder(w).Encode(ErrorResponse{Message: "Session expired"}); encodeErr != nil { + app.Logger.Printf("Error encoding JSON: %v", encodeErr) + } + return + } + + next.ServeHTTP(w, r) + }) } func generateSessionToken() (string, error) { diff --git a/api/main.go b/api/main.go index 91889f7..9162bca 100644 --- a/api/main.go +++ b/api/main.go @@ -5,14 +5,13 @@ import ( "encoding/json" "flag" "fmt" + "io" "log" "net/http" "os" "path/filepath" "strconv" - "io" - - + _ "github.com/go-sql-driver/mysql" "github.com/gorilla/mux" "github.com/joho/godotenv" @@ -110,8 +109,8 @@ func (app *App) setupRouter() *mux.Router { r.HandleFunc("/search_authors", app.SearchAuthors).Methods("GET") // Routes for login - r.HandleFunc("/signup", app.SignupUser).Methods("POST") - r.HandleFunc("/login", app.LoginUser).Methods("POST") + r.HandleFunc("/signup", app.SignupUser).Methods("POST") + r.HandleFunc("/login", app.LoginUser).Methods("POST") return r } @@ -174,7 +173,7 @@ func initDB(username, password, hostname, port, dbname string) (*sql.DB, error) dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", username, password, hostname, port, dbname) // Open a connection to the database - db, err := sqlOpen("mysql", dsn) // Use the sqlOpen variable here + db, err := sqlOpen("mysql", dsn) // Use the sqlOpen variable here if err != nil { return nil, fmt.Errorf("failed to connect to the database: %w", err) } @@ -201,165 +200,164 @@ func (app *App) Info(w http.ResponseWriter, r *http.Request) { // HandleError is a method of App that handles errors by logging them and sending an appropriate HTTP response func (app *App) HandleError(w http.ResponseWriter, message string, err error, status int) { - app.Logger.Printf("%s: %v", message, err) - http.Error(w, message, status) + app.Logger.Printf("%s: %v", message, err) + http.Error(w, message, status) } func RespondWithJSON(w http.ResponseWriter, status int, payload interface{}) { - // Set Content-Type header to application/json - w.Header().Set("Content-Type", "application/json") - // Attempt to encode the payload - if err := json.NewEncoder(w).Encode(payload); err != nil { - log.Printf("Error encoding response: %v", err) - // Set Content-Type header again in case of error - w.Header().Set("Content-Type", "application/json") - // Set status to 500 - w.WriteHeader(http.StatusInternalServerError) - // Write the error message, including a newline - if _, writeErr := w.Write([]byte("Error encoding response\n")); writeErr != nil { - log.Printf("Error writing response: %v", writeErr) - } - return - } - // Set the status code - w.WriteHeader(status) + // Set Content-Type header to application/json + w.Header().Set("Content-Type", "application/json") + // Attempt to encode the payload + if err := json.NewEncoder(w).Encode(payload); err != nil { + log.Printf("Error encoding response: %v", err) + // Set Content-Type header again in case of error + w.Header().Set("Content-Type", "application/json") + // Set status to 500 + w.WriteHeader(http.StatusInternalServerError) + // Write the error message, including a newline + if _, writeErr := w.Write([]byte("Error encoding response\n")); writeErr != nil { + log.Printf("Error writing response: %v", writeErr) + } + return + } + // Set the status code + w.WriteHeader(status) } - // HandleError handles errors by logging them and sending an appropriate HTTP response func HandleError(w http.ResponseWriter, logger *log.Logger, message string, err error, status int) { - // Log the error message with additional context - logger.Printf("%s: %v", message, err) - // Send an HTTP error response with the given status code - http.Error(w, message, status) + // Log the error message with additional context + logger.Printf("%s: %v", message, err) + // Send an HTTP error response with the given status code + http.Error(w, message, status) } // GetIDFromRequest extracts and validates an ID parameter from the URL func GetIDFromRequest(r *http.Request, paramName string) (int, error) { - // Retrieve the parameter from the URL - vars := mux.Vars(r) - idStr := vars[paramName] - // Convert the string parameter to an integer - id, err := strconv.Atoi(idStr) - if err != nil { - // Return an error if conversion fails, using a lowercase message - return 0, fmt.Errorf("invalid %s: %v", paramName, err) - } - return id, nil + // Retrieve the parameter from the URL + vars := mux.Vars(r) + idStr := vars[paramName] + // Convert the string parameter to an integer + id, err := strconv.Atoi(idStr) + if err != nil { + // Return an error if conversion fails, using a lowercase message + return 0, fmt.Errorf("invalid %s: %v", paramName, err) + } + return id, nil } // ScanAuthors processes rows from the SQL query and returns a list of authors func ScanAuthors(rows *sql.Rows) ([]Author, error) { - defer rows.Close() + defer rows.Close() - var authors []Author + var authors []Author - for rows.Next() { - var author Author - if err := rows.Scan(&author.ID, &author.Lastname, &author.Firstname, &author.Photo); err != nil { - return nil, err - } - authors = append(authors, author) - } + for rows.Next() { + var author Author + if err := rows.Scan(&author.ID, &author.Lastname, &author.Firstname, &author.Photo); err != nil { + return nil, err + } + authors = append(authors, author) + } - if err := rows.Err(); err != nil { - return nil, err - } + if err := rows.Err(); err != nil { + return nil, err + } - return authors, nil + return authors, nil } // ValidateAuthorData checks if the required fields for an author are present func ValidateAuthorData(author Author) error { - if author.Firstname == "" || author.Lastname == "" { - return fmt.Errorf("firstname and lastname are required fields") // Lowercase error message - } - return nil + if author.Firstname == "" || author.Lastname == "" { + return fmt.Errorf("firstname and lastname are required fields") // Lowercase error message + } + return nil } // ValidateBookData checks if the required fields for a book are present func ValidateBookData(book Book) error { - // Ensure Title and AuthorID are not empty or zero - if book.Title == "" || book.AuthorID == 0 { - return fmt.Errorf("title and authorID are required fields") // Lowercase error message - } - return nil + // Ensure Title and AuthorID are not empty or zero + if book.Title == "" || book.AuthorID == 0 { + return fmt.Errorf("title and authorID are required fields") // Lowercase error message + } + return nil } // SearchAuthors searches for authors based on a query parameter func (app *App) SearchAuthors(w http.ResponseWriter, r *http.Request) { - // Log the entry to the handler for debugging purposes - app.Logger.Println("SearchAuthors handler called") - - // Get the "query" parameter from the URL - query := r.URL.Query().Get("query") - if query == "" { - // If the query parameter is missing, return a 400 Bad Request error - HandleError(w, app.Logger, "Query parameter is required", nil, http.StatusBadRequest) - return - } - - // Prepare the SQL query with an ORDER BY clause to ensure consistent result order - sqlQuery := ` + // Log the entry to the handler for debugging purposes + app.Logger.Println("SearchAuthors handler called") + + // Get the "query" parameter from the URL + query := r.URL.Query().Get("query") + if query == "" { + // If the query parameter is missing, return a 400 Bad Request error + HandleError(w, app.Logger, "Query parameter is required", nil, http.StatusBadRequest) + return + } + + // Prepare the SQL query with an ORDER BY clause to ensure consistent result order + sqlQuery := ` SELECT id, Firstname, Lastname, photo FROM authors WHERE Firstname LIKE ? OR Lastname LIKE ? ORDER BY Lastname, Firstname ` - // Execute the SQL query to fetch authors based on the query parameter - rows, err := app.DB.Query(sqlQuery, "%"+query+"%", "%"+query+"%") - if err != nil { - // Log error executing the query and return a 500 Internal Server Error - HandleError(w, app.Logger, "Error executing query", err, http.StatusInternalServerError) - return - } - defer rows.Close() // Always close rows after use to release resources - - // Use the utility function to scan and process the SQL rows - authors, err := ScanAuthors(rows) - if err != nil { - // Log error scanning the rows and return a 500 Internal Server Error - HandleError(w, app.Logger, "Error scanning authors", err, http.StatusInternalServerError) - return - } - - // Send the JSON response using the utility function - RespondWithJSON(w, http.StatusOK, authors) + // Execute the SQL query to fetch authors based on the query parameter + rows, err := app.DB.Query(sqlQuery, "%"+query+"%", "%"+query+"%") + if err != nil { + // Log error executing the query and return a 500 Internal Server Error + HandleError(w, app.Logger, "Error executing query", err, http.StatusInternalServerError) + return + } + defer rows.Close() // Always close rows after use to release resources + + // Use the utility function to scan and process the SQL rows + authors, err := ScanAuthors(rows) + if err != nil { + // Log error scanning the rows and return a 500 Internal Server Error + HandleError(w, app.Logger, "Error scanning authors", err, http.StatusInternalServerError) + return + } + + // Send the JSON response using the utility function + RespondWithJSON(w, http.StatusOK, authors) } // ScanBooks processes rows from the SQL query and returns a list of books with author information func ScanBooks(rows *sql.Rows) ([]BookAuthorInfo, error) { - defer rows.Close() + defer rows.Close() - var books []BookAuthorInfo + var books []BookAuthorInfo - for rows.Next() { - var book BookAuthorInfo - if err := rows.Scan(&book.BookID, &book.BookTitle, &book.AuthorID, &book.BookPhoto, &book.IsBorrowed, &book.BookDetails, &book.AuthorLastname, &book.AuthorFirstname); err != nil { - return nil, err - } - books = append(books, book) - } + for rows.Next() { + var book BookAuthorInfo + if err := rows.Scan(&book.BookID, &book.BookTitle, &book.AuthorID, &book.BookPhoto, &book.IsBorrowed, &book.BookDetails, &book.AuthorLastname, &book.AuthorFirstname); err != nil { + return nil, err + } + books = append(books, book) + } - if err := rows.Err(); err != nil { - return nil, err - } + if err := rows.Err(); err != nil { + return nil, err + } - return books, nil + return books, nil } // SearchBooks searches for books based on a query parameter func (app *App) SearchBooks(w http.ResponseWriter, r *http.Request) { - app.Logger.Println("SearchBooks handler called") + app.Logger.Println("SearchBooks handler called") - query := r.URL.Query().Get("query") - if query == "" { - HandleError(w, app.Logger, "Query parameter is required", nil, http.StatusBadRequest) - return - } + query := r.URL.Query().Get("query") + if query == "" { + HandleError(w, app.Logger, "Query parameter is required", nil, http.StatusBadRequest) + return + } - sqlQuery := ` + sqlQuery := ` SELECT books.id AS book_id, books.title AS book_title, @@ -375,53 +373,52 @@ func (app *App) SearchBooks(w http.ResponseWriter, r *http.Request) { ORDER BY books.title, authors.Lastname, authors.Firstname ` - rows, err := app.DB.Query(sqlQuery, "%"+query+"%", "%"+query+"%", "%"+query+"%") - if err != nil { - HandleError(w, app.Logger, "Error executing query", err, http.StatusInternalServerError) - return - } - defer rows.Close() + rows, err := app.DB.Query(sqlQuery, "%"+query+"%", "%"+query+"%", "%"+query+"%") + if err != nil { + HandleError(w, app.Logger, "Error executing query", err, http.StatusInternalServerError) + return + } + defer rows.Close() - books, err := ScanBooks(rows) - if err != nil { - HandleError(w, app.Logger, "Error scanning books", err, http.StatusInternalServerError) - return - } + books, err := ScanBooks(rows) + if err != nil { + HandleError(w, app.Logger, "Error scanning books", err, http.StatusInternalServerError) + return + } - RespondWithJSON(w, http.StatusOK, books) + RespondWithJSON(w, http.StatusOK, books) } - // GetAuthors retrieves all authors from the database func (app *App) GetAuthors(w http.ResponseWriter, r *http.Request) { - app.Logger.Println("GetAuthors handler called") + app.Logger.Println("GetAuthors handler called") - sqlQuery := ` + sqlQuery := ` SELECT id, Lastname, Firstname, photo FROM authors ORDER BY Lastname, Firstname ` - rows, err := app.DB.Query(sqlQuery) - if err != nil { - HandleError(w, app.Logger, "Error executing query", err, http.StatusInternalServerError) - return - } - defer rows.Close() - - authors, err := ScanAuthors(rows) - if err != nil { - HandleError(w, app.Logger, "Error scanning authors", err, http.StatusInternalServerError) - return - } - - RespondWithJSON(w, http.StatusOK, authors) + rows, err := app.DB.Query(sqlQuery) + if err != nil { + HandleError(w, app.Logger, "Error executing query", err, http.StatusInternalServerError) + return + } + defer rows.Close() + + authors, err := ScanAuthors(rows) + if err != nil { + HandleError(w, app.Logger, "Error scanning authors", err, http.StatusInternalServerError) + return + } + + RespondWithJSON(w, http.StatusOK, authors) } // GetAllBooks retrieves all books from the database along with the author's first and last name func (app *App) GetAllBooks(w http.ResponseWriter, r *http.Request) { - app.Logger.Println("GetAllBooks handler called") + app.Logger.Println("GetAllBooks handler called") - query := ` + query := ` SELECT books.id AS book_id, books.title AS book_title, @@ -436,61 +433,61 @@ func (app *App) GetAllBooks(w http.ResponseWriter, r *http.Request) { ORDER BY books.title, authors.Lastname, authors.Firstname ` - rows, err := app.DB.Query(query) - if err != nil { - HandleError(w, app.Logger, "Error executing query", err, http.StatusInternalServerError) - return - } + rows, err := app.DB.Query(query) + if err != nil { + HandleError(w, app.Logger, "Error executing query", err, http.StatusInternalServerError) + return + } - books, err := ScanBooks(rows) - if err != nil { - HandleError(w, app.Logger, "Error scanning books", err, http.StatusInternalServerError) - return - } + books, err := ScanBooks(rows) + if err != nil { + HandleError(w, app.Logger, "Error scanning books", err, http.StatusInternalServerError) + return + } - RespondWithJSON(w, http.StatusOK, books) + RespondWithJSON(w, http.StatusOK, books) } // GetAuthorsAndBooks retrieves information about authors and their books func (app *App) GetAuthorsAndBooks(w http.ResponseWriter, r *http.Request) { - app.Logger.Println("GetAuthorsAndBooks handler called") + app.Logger.Println("GetAuthorsAndBooks handler called") - query := ` + query := ` SELECT a.Firstname AS author_firstname, a.Lastname AS author_lastname, b.title AS book_title, b.photo AS book_photo FROM authors_books ab JOIN authors a ON ab.author_id = a.id JOIN books b ON ab.book_id = b.id ` - rows, err := app.DB.Query(query) - if err != nil { - app.Logger.Printf("Query error: %v", err) - HandleError(w, app.Logger, "Error executing query", err, http.StatusInternalServerError) - return - } - defer rows.Close() - - authorsAndBooks, err := ScanAuthorAndBooks(rows) - if err != nil { - app.Logger.Printf("Scan error: %v", err) - HandleError(w, app.Logger, "Error scanning authors and books", err, http.StatusInternalServerError) - return - } - - RespondWithJSON(w, http.StatusOK, authorsAndBooks) + rows, err := app.DB.Query(query) + if err != nil { + app.Logger.Printf("Query error: %v", err) + HandleError(w, app.Logger, "Error executing query", err, http.StatusInternalServerError) + return + } + defer rows.Close() + + authorsAndBooks, err := ScanAuthorAndBooks(rows) + if err != nil { + app.Logger.Printf("Scan error: %v", err) + HandleError(w, app.Logger, "Error scanning authors and books", err, http.StatusInternalServerError) + return + } + + RespondWithJSON(w, http.StatusOK, authorsAndBooks) } // GetAuthorBooksByID retrieves information about an author and their books by the author's ID func (app *App) GetAuthorBooksByID(w http.ResponseWriter, r *http.Request) { - app.Logger.Println("GetAuthorBooksByID handler called") + app.Logger.Println("GetAuthorBooksByID handler called") - authorID, err := GetIDFromRequest(r, "id") - if err != nil { - app.HandleError(w, "Invalid author ID", err, http.StatusBadRequest) - return - } + authorID, err := GetIDFromRequest(r, "id") + if err != nil { + app.HandleError(w, "Invalid author ID", err, http.StatusBadRequest) + return + } - query := ` + query := ` SELECT a.Firstname AS author_firstname, a.Lastname AS author_lastname, b.title AS book_title, b.photo AS book_photo FROM authors_books ab JOIN authors a ON ab.author_id = a.id @@ -498,52 +495,52 @@ func (app *App) GetAuthorBooksByID(w http.ResponseWriter, r *http.Request) { WHERE a.id = ? ` - rows, err := app.DB.Query(query, authorID) - if err != nil { - app.HandleError(w, "Error executing query", err, http.StatusInternalServerError) - return - } - defer rows.Close() + rows, err := app.DB.Query(query, authorID) + if err != nil { + app.HandleError(w, "Error executing query", err, http.StatusInternalServerError) + return + } + defer rows.Close() - authorAndBooks, err := ScanAuthorAndBooks(rows) - if err != nil { - app.HandleError(w, "Error scanning results", err, http.StatusInternalServerError) - return - } + authorAndBooks, err := ScanAuthorAndBooks(rows) + if err != nil { + app.HandleError(w, "Error scanning results", err, http.StatusInternalServerError) + return + } - RespondWithJSON(w, http.StatusOK, authorAndBooks) + RespondWithJSON(w, http.StatusOK, authorAndBooks) } // ScanAuthorAndBooks processes rows from the SQL query and returns a list of author and book information func ScanAuthorAndBooks(rows *sql.Rows) ([]AuthorBook, error) { - var authorsAndBooks []AuthorBook + var authorsAndBooks []AuthorBook - for rows.Next() { - var authorBook AuthorBook - if err := rows.Scan(&authorBook.AuthorFirstname, &authorBook.AuthorLastname, &authorBook.BookTitle, &authorBook.BookPhoto); err != nil { - return nil, err - } - authorsAndBooks = append(authorsAndBooks, authorBook) - } + for rows.Next() { + var authorBook AuthorBook + if err := rows.Scan(&authorBook.AuthorFirstname, &authorBook.AuthorLastname, &authorBook.BookTitle, &authorBook.BookPhoto); err != nil { + return nil, err + } + authorsAndBooks = append(authorsAndBooks, authorBook) + } - if err := rows.Err(); err != nil { - return nil, err - } + if err := rows.Err(); err != nil { + return nil, err + } - return authorsAndBooks, nil + return authorsAndBooks, nil } // GetBookByID retrieves information about a specific book based on its ID func (app *App) GetBookByID(w http.ResponseWriter, r *http.Request) { - app.Logger.Println("GetBookByID handler called") + app.Logger.Println("GetBookByID handler called") - bookID, err := GetIDFromRequest(r, "id") - if err != nil { - HandleError(w, app.Logger, "Invalid book ID", err, http.StatusBadRequest) - return - } + bookID, err := GetIDFromRequest(r, "id") + if err != nil { + HandleError(w, app.Logger, "Invalid book ID", err, http.StatusBadRequest) + return + } - query := ` + query := ` SELECT books.title AS book_title, books.author_id AS author_id, @@ -558,27 +555,26 @@ func (app *App) GetBookByID(w http.ResponseWriter, r *http.Request) { WHERE books.id = ? ` - rows, err := app.DB.Query(query, bookID) - if err != nil { - HandleError(w, app.Logger, "Error executing query", err, http.StatusInternalServerError) - return - } - defer rows.Close() - - var book BookAuthorInfo - if rows.Next() { - if err := rows.Scan(&book.BookTitle, &book.AuthorID, &book.BookPhoto, &book.IsBorrowed, &book.BookID, &book.BookDetails, &book.AuthorLastname, &book.AuthorFirstname); err != nil { - HandleError(w, app.Logger, "Error scanning book", err, http.StatusInternalServerError) - return - } - } else { - HandleError(w, app.Logger, "Book not found", nil, http.StatusNotFound) - return - } - - RespondWithJSON(w, http.StatusOK, book) -} + rows, err := app.DB.Query(query, bookID) + if err != nil { + HandleError(w, app.Logger, "Error executing query", err, http.StatusInternalServerError) + return + } + defer rows.Close() + var book BookAuthorInfo + if rows.Next() { + if err := rows.Scan(&book.BookTitle, &book.AuthorID, &book.BookPhoto, &book.IsBorrowed, &book.BookID, &book.BookDetails, &book.AuthorLastname, &book.AuthorFirstname); err != nil { + HandleError(w, app.Logger, "Error scanning book", err, http.StatusInternalServerError) + return + } + } else { + HandleError(w, app.Logger, "Book not found", nil, http.StatusNotFound) + return + } + + RespondWithJSON(w, http.StatusOK, book) +} // GetSubscribersByBookID retrieves the list of subscribers who have borrowed a specific book based on the book's ID func (app *App) GetSubscribersByBookID(w http.ResponseWriter, r *http.Request) { @@ -617,20 +613,20 @@ func (app *App) GetSubscribersByBookID(w http.ResponseWriter, r *http.Request) { } func ScanRows(rows *sql.Rows, subscribers *[]Subscriber) error { - for rows.Next() { - var subscriber Subscriber - if err := rows.Scan(&subscriber.Lastname, &subscriber.Firstname, &subscriber.Email); err != nil { - return err - } - *subscribers = append(*subscribers, subscriber) - } - return rows.Err() + for rows.Next() { + var subscriber Subscriber + if err := rows.Scan(&subscriber.Lastname, &subscriber.Firstname, &subscriber.Email); err != nil { + return err + } + *subscribers = append(*subscribers, subscriber) + } + return rows.Err() } // GetAllSubscribers retrieves all subscribers from the database func (app *App) GetAllSubscribers(w http.ResponseWriter, r *http.Request) { query := "SELECT lastname, firstname, email FROM subscribers" - + rows, err := app.DB.Query(query) if err != nil { HandleError(w, app.Logger, "Error querying the database", err, http.StatusInternalServerError) @@ -649,107 +645,107 @@ func (app *App) GetAllSubscribers(w http.ResponseWriter, r *http.Request) { var mkdirAll = os.MkdirAll var osCreate = os.Create -var ioCopy = io.Copy +var ioCopy = io.Copy // AddAuthorPhoto handles the upload of an author's photo and updates the database func (app *App) AddAuthorPhoto(w http.ResponseWriter, r *http.Request) { - app.Logger.Println("AddAuthorPhoto handler called") - - if r.Method != http.MethodPost { - HandleError(w, app.Logger, "Only POST method is supported", nil, http.StatusMethodNotAllowed) - return - } - - authorID, err := GetIDFromRequest(r, "id") - if err != nil { - HandleError(w, app.Logger, "Invalid author ID", err, http.StatusBadRequest) - return - } - - file, _, err := r.FormFile("file") - if err != nil { - HandleError(w, app.Logger, "Error getting file from request", err, http.StatusInternalServerError) - return - } - defer file.Close() - - filename := "fullsize.jpg" - ext := filepath.Ext(filename) - - photoDir := "./upload/" + strconv.Itoa(authorID) - photoPath := photoDir + "/fullsize" + ext - - if err := mkdirAll(photoDir, 0777); err != nil { - HandleError(w, app.Logger, "Error creating directories", err, http.StatusInternalServerError) - return - } - - out, err := osCreate(photoPath) - if err != nil { - HandleError(w, app.Logger, "Error creating file on disk", err, http.StatusInternalServerError) - return - } - defer out.Close() - - if _, err := ioCopy(out, file); err != nil { - HandleError(w, app.Logger, "Error saving file", err, http.StatusInternalServerError) - return - } - - query := `UPDATE authors SET photo = ? WHERE id = ?` - if _, err := app.DB.Exec(query, photoPath, authorID); err != nil { - HandleError(w, app.Logger, "Failed to update author photo", err, http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, "File uploaded successfully: %s\n", photoPath) + app.Logger.Println("AddAuthorPhoto handler called") + + if r.Method != http.MethodPost { + HandleError(w, app.Logger, "Only POST method is supported", nil, http.StatusMethodNotAllowed) + return + } + + authorID, err := GetIDFromRequest(r, "id") + if err != nil { + HandleError(w, app.Logger, "Invalid author ID", err, http.StatusBadRequest) + return + } + + file, _, err := r.FormFile("file") + if err != nil { + HandleError(w, app.Logger, "Error getting file from request", err, http.StatusInternalServerError) + return + } + defer file.Close() + + filename := "fullsize.jpg" + ext := filepath.Ext(filename) + + photoDir := "./upload/" + strconv.Itoa(authorID) + photoPath := photoDir + "/fullsize" + ext + + if err := mkdirAll(photoDir, 0777); err != nil { + HandleError(w, app.Logger, "Error creating directories", err, http.StatusInternalServerError) + return + } + + out, err := osCreate(photoPath) + if err != nil { + HandleError(w, app.Logger, "Error creating file on disk", err, http.StatusInternalServerError) + return + } + defer out.Close() + + if _, err := ioCopy(out, file); err != nil { + HandleError(w, app.Logger, "Error saving file", err, http.StatusInternalServerError) + return + } + + query := `UPDATE authors SET photo = ? WHERE id = ?` + if _, err := app.DB.Exec(query, photoPath, authorID); err != nil { + HandleError(w, app.Logger, "Failed to update author photo", err, http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "File uploaded successfully: %s\n", photoPath) } var jsonEncoder = json.NewEncoder // AddAuthor adds a new author to the database func (app *App) AddAuthor(w http.ResponseWriter, r *http.Request) { - app.Logger.Println("AddAuthor handler called") - - if r.Method != http.MethodPost { - HandleError(w, app.Logger, "Only POST method is supported", nil, http.StatusMethodNotAllowed) - return - } - - var author Author - if err := json.NewDecoder(r.Body).Decode(&author); err != nil { - HandleError(w, app.Logger, "Invalid JSON data", err, http.StatusBadRequest) - return - } - defer r.Body.Close() - - if err := ValidateAuthorData(author); err != nil { - HandleError(w, app.Logger, err.Error(), nil, http.StatusBadRequest) - return - } - - query := `INSERT INTO authors (lastname, firstname, photo) VALUES (?, ?, ?)` - result, err := app.DB.Exec(query, author.Lastname, author.Firstname, "") - if err != nil { - HandleError(w, app.Logger, "Failed to insert author", err, http.StatusInternalServerError) - return - } - - id, err := result.LastInsertId() - if err != nil || id == 0 { - HandleError(w, app.Logger, "Failed to get last insert ID", err, http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusCreated) - - response := map[string]int{"id": int(id)} - if err := jsonEncoder(w).Encode(response); err != nil { - app.Logger.Printf("JSON encoding error: %v", err) - http.Error(w, "Error encoding response", http.StatusInternalServerError) - } + app.Logger.Println("AddAuthor handler called") + + if r.Method != http.MethodPost { + HandleError(w, app.Logger, "Only POST method is supported", nil, http.StatusMethodNotAllowed) + return + } + + var author Author + if err := json.NewDecoder(r.Body).Decode(&author); err != nil { + HandleError(w, app.Logger, "Invalid JSON data", err, http.StatusBadRequest) + return + } + defer r.Body.Close() + + if err := ValidateAuthorData(author); err != nil { + HandleError(w, app.Logger, err.Error(), nil, http.StatusBadRequest) + return + } + + query := `INSERT INTO authors (lastname, firstname, photo) VALUES (?, ?, ?)` + result, err := app.DB.Exec(query, author.Lastname, author.Firstname, "") + if err != nil { + HandleError(w, app.Logger, "Failed to insert author", err, http.StatusInternalServerError) + return + } + + id, err := result.LastInsertId() + if err != nil || id == 0 { + HandleError(w, app.Logger, "Failed to get last insert ID", err, http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + + response := map[string]int{"id": int(id)} + if err := jsonEncoder(w).Encode(response); err != nil { + app.Logger.Printf("JSON encoding error: %v", err) + http.Error(w, "Error encoding response", http.StatusInternalServerError) + } } // Define a global variable for os.MkdirAll to allow overriding in tests @@ -757,97 +753,97 @@ var osMkdirAll = os.MkdirAll // AddBookPhoto handles the upload of a book's photo and updates the database func (app *App) AddBookPhoto(w http.ResponseWriter, r *http.Request) { - app.Logger.Println("AddBookPhoto handler called") - - if r.Method != http.MethodPost { - HandleError(w, app.Logger, "Only POST method is supported", nil, http.StatusMethodNotAllowed) - return - } - - bookID, err := GetIDFromRequest(r, "id") - if err != nil { - HandleError(w, app.Logger, "Invalid book ID", err, http.StatusBadRequest) - return - } - - file, _, err := r.FormFile("file") - if err != nil { - HandleError(w, app.Logger, "Error getting file from request", err, http.StatusInternalServerError) - return - } - defer file.Close() - - photoDir := "./upload/books/" + strconv.Itoa(bookID) - photoPath := photoDir + "/fullsize.jpg" - - if err := osMkdirAll(photoDir, 0777); err != nil { - HandleError(w, app.Logger, "Error creating directories", err, http.StatusInternalServerError) - return - } - - out, err := osCreate(photoPath) - if err != nil { - HandleError(w, app.Logger, "Error creating file on disk", err, http.StatusInternalServerError) - return - } - defer out.Close() - - if _, err := ioCopy(out, file); err != nil { - HandleError(w, app.Logger, "Error saving file", err, http.StatusInternalServerError) - return - } - - query := `UPDATE books SET photo = ? WHERE id = ?` - if _, err := app.DB.Exec(query, photoPath, bookID); err != nil { - HandleError(w, app.Logger, "Failed to update book photo", err, http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, "File uploaded successfully: %s\n", photoPath) + app.Logger.Println("AddBookPhoto handler called") + + if r.Method != http.MethodPost { + HandleError(w, app.Logger, "Only POST method is supported", nil, http.StatusMethodNotAllowed) + return + } + + bookID, err := GetIDFromRequest(r, "id") + if err != nil { + HandleError(w, app.Logger, "Invalid book ID", err, http.StatusBadRequest) + return + } + + file, _, err := r.FormFile("file") + if err != nil { + HandleError(w, app.Logger, "Error getting file from request", err, http.StatusInternalServerError) + return + } + defer file.Close() + + photoDir := "./upload/books/" + strconv.Itoa(bookID) + photoPath := photoDir + "/fullsize.jpg" + + if err := osMkdirAll(photoDir, 0777); err != nil { + HandleError(w, app.Logger, "Error creating directories", err, http.StatusInternalServerError) + return + } + + out, err := osCreate(photoPath) + if err != nil { + HandleError(w, app.Logger, "Error creating file on disk", err, http.StatusInternalServerError) + return + } + defer out.Close() + + if _, err := ioCopy(out, file); err != nil { + HandleError(w, app.Logger, "Error saving file", err, http.StatusInternalServerError) + return + } + + query := `UPDATE books SET photo = ? WHERE id = ?` + if _, err := app.DB.Exec(query, photoPath, bookID); err != nil { + HandleError(w, app.Logger, "Failed to update book photo", err, http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "File uploaded successfully: %s\n", photoPath) } // AddBook adds a new book to the database func (app *App) AddBook(w http.ResponseWriter, r *http.Request) { - app.Logger.Println("AddBook handler called") - - if r.Method != http.MethodPost { - HandleError(w, app.Logger, "Only POST method is supported", nil, http.StatusMethodNotAllowed) - return - } - - var book Book - if err := json.NewDecoder(r.Body).Decode(&book); err != nil { - HandleError(w, app.Logger, "Invalid JSON data", err, http.StatusBadRequest) - return - } - defer r.Body.Close() - - if err := ValidateBookData(book); err != nil { - HandleError(w, app.Logger, err.Error(), nil, http.StatusBadRequest) - return - } - - query := `INSERT INTO books (title, photo, details, author_id, is_borrowed) VALUES (?, ?, ?, ?, ?)` - result, err := app.DB.Exec(query, book.Title, "", book.Details, book.AuthorID, book.IsBorrowed) - if err != nil { - HandleError(w, app.Logger, "Failed to insert book", err, http.StatusInternalServerError) - return - } - - id, err := result.LastInsertId() - if err != nil || id == 0 { - HandleError(w, app.Logger, "Failed to get last insert ID", err, http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusCreated) - response := map[string]int{"id": int(id)} - if err := json.NewEncoder(w).Encode(response); err != nil { - app.Logger.Printf("JSON encoding error: %v", err) - http.Error(w, "Error encoding response", http.StatusInternalServerError) - } + app.Logger.Println("AddBook handler called") + + if r.Method != http.MethodPost { + HandleError(w, app.Logger, "Only POST method is supported", nil, http.StatusMethodNotAllowed) + return + } + + var book Book + if err := json.NewDecoder(r.Body).Decode(&book); err != nil { + HandleError(w, app.Logger, "Invalid JSON data", err, http.StatusBadRequest) + return + } + defer r.Body.Close() + + if err := ValidateBookData(book); err != nil { + HandleError(w, app.Logger, err.Error(), nil, http.StatusBadRequest) + return + } + + query := `INSERT INTO books (title, photo, details, author_id, is_borrowed) VALUES (?, ?, ?, ?, ?)` + result, err := app.DB.Exec(query, book.Title, "", book.Details, book.AuthorID, book.IsBorrowed) + if err != nil { + HandleError(w, app.Logger, "Failed to insert book", err, http.StatusInternalServerError) + return + } + + id, err := result.LastInsertId() + if err != nil || id == 0 { + HandleError(w, app.Logger, "Failed to get last insert ID", err, http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + response := map[string]int{"id": int(id)} + if err := json.NewEncoder(w).Encode(response); err != nil { + app.Logger.Printf("JSON encoding error: %v", err) + http.Error(w, "Error encoding response", http.StatusInternalServerError) + } } // AddSubscriber adds a new subscriber to the database @@ -884,14 +880,14 @@ func (app *App) AddSubscriber(w http.ResponseWriter, r *http.Request) { return } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusCreated) - if err := json.NewEncoder(w).Encode(map[string]int{"id": int(id)}); err != nil { - app.Logger.Printf("Error encoding JSON response: %v", err) - w.WriteHeader(http.StatusInternalServerError) - return - } - + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + if err := json.NewEncoder(w).Encode(map[string]int{"id": int(id)}); err != nil { + app.Logger.Printf("Error encoding JSON response: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + } // BorrowBook handles borrowing a book by a subscriber @@ -993,7 +989,6 @@ func (app *App) ReturnBorrowedBook(w http.ResponseWriter, r *http.Request) { return } - _, err = app.DB.Exec("UPDATE books SET is_borrowed = FALSE WHERE id = ?", requestBody.BookID) if err != nil { app.Logger.Printf("Database error: %v", err) @@ -1008,161 +1003,160 @@ func (app *App) ReturnBorrowedBook(w http.ResponseWriter, r *http.Request) { // UpdateAuthor updates an existing author in the database func (app *App) UpdateAuthor(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPut && r.Method != http.MethodPost { - HandleError(w, app.Logger, "Only PUT or POST methods are supported", nil, http.StatusMethodNotAllowed) - return - } - - authorID, err := GetIDFromRequest(r, "id") - if err != nil { - HandleError(w, app.Logger, "Invalid author ID", err, http.StatusBadRequest) - return - } - - var author Author - if err := json.NewDecoder(r.Body).Decode(&author); err != nil { - HandleError(w, app.Logger, "Invalid JSON data", err, http.StatusBadRequest) - return - } - defer r.Body.Close() - - if author.Firstname == "" || author.Lastname == "" { - HandleError(w, app.Logger, "Firstname and Lastname are required fields", nil, http.StatusBadRequest) - return - } - - query := ` + if r.Method != http.MethodPut && r.Method != http.MethodPost { + HandleError(w, app.Logger, "Only PUT or POST methods are supported", nil, http.StatusMethodNotAllowed) + return + } + + authorID, err := GetIDFromRequest(r, "id") + if err != nil { + HandleError(w, app.Logger, "Invalid author ID", err, http.StatusBadRequest) + return + } + + var author Author + if err := json.NewDecoder(r.Body).Decode(&author); err != nil { + HandleError(w, app.Logger, "Invalid JSON data", err, http.StatusBadRequest) + return + } + defer r.Body.Close() + + if author.Firstname == "" || author.Lastname == "" { + HandleError(w, app.Logger, "Firstname and Lastname are required fields", nil, http.StatusBadRequest) + return + } + + query := ` UPDATE authors SET lastname = ?, firstname = ?, photo = ? WHERE id = ? ` - result, err := app.DB.Exec(query, author.Lastname, author.Firstname, author.Photo, authorID) - if err != nil { - HandleError(w, app.Logger, "Failed to update author", err, http.StatusInternalServerError) - return - } - - rowsAffected, err := result.RowsAffected() - if err != nil { - HandleError(w, app.Logger, "Failed to retrieve affected rows", err, http.StatusInternalServerError) - return - } - if rowsAffected == 0 { - HandleError(w, app.Logger, "Author not found", nil, http.StatusNotFound) - return - } - - RespondWithJSON(w, http.StatusOK, map[string]string{"message": "Author updated successfully"}) + result, err := app.DB.Exec(query, author.Lastname, author.Firstname, author.Photo, authorID) + if err != nil { + HandleError(w, app.Logger, "Failed to update author", err, http.StatusInternalServerError) + return + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + HandleError(w, app.Logger, "Failed to retrieve affected rows", err, http.StatusInternalServerError) + return + } + if rowsAffected == 0 { + HandleError(w, app.Logger, "Author not found", nil, http.StatusNotFound) + return + } + + RespondWithJSON(w, http.StatusOK, map[string]string{"message": "Author updated successfully"}) } // UpdateBook handles the updating of an existing book in the database func (app *App) UpdateBook(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPut && r.Method != http.MethodPost { - HandleError(w, app.Logger, "Only PUT or POST methods are supported", nil, http.StatusMethodNotAllowed) - return - } - - bookID, err := GetIDFromRequest(r, "id") - if err != nil { - HandleError(w, app.Logger, "Invalid book ID", err, http.StatusBadRequest) - return - } - - var book Book - if err := json.NewDecoder(r.Body).Decode(&book); err != nil { - HandleError(w, app.Logger, "Invalid JSON data", err, http.StatusBadRequest) - return - } - defer r.Body.Close() - - if err := ValidateBookData(book); err != nil { - HandleError(w, app.Logger, err.Error(), nil, http.StatusBadRequest) - return - } - - query := ` + if r.Method != http.MethodPut && r.Method != http.MethodPost { + HandleError(w, app.Logger, "Only PUT or POST methods are supported", nil, http.StatusMethodNotAllowed) + return + } + + bookID, err := GetIDFromRequest(r, "id") + if err != nil { + HandleError(w, app.Logger, "Invalid book ID", err, http.StatusBadRequest) + return + } + + var book Book + if err := json.NewDecoder(r.Body).Decode(&book); err != nil { + HandleError(w, app.Logger, "Invalid JSON data", err, http.StatusBadRequest) + return + } + defer r.Body.Close() + + if err := ValidateBookData(book); err != nil { + HandleError(w, app.Logger, err.Error(), nil, http.StatusBadRequest) + return + } + + query := ` UPDATE books SET title = ?, author_id = ?, photo = ?, details = ?, is_borrowed = ? WHERE id = ? ` - result, err := app.DB.Exec(query, book.Title, book.AuthorID, book.Photo, book.Details, book.IsBorrowed, bookID) - if err != nil { - HandleError(w, app.Logger, "Failed to update book", err, http.StatusInternalServerError) - return - } - - rowsAffected, err := result.RowsAffected() - if err != nil { - HandleError(w, app.Logger, "Failed to retrieve affected rows", err, http.StatusInternalServerError) - return - } - if rowsAffected == 0 { - HandleError(w, app.Logger, "Book not found", nil, http.StatusNotFound) - return - } - - RespondWithJSON(w, http.StatusOK, map[string]string{"message": "Book updated successfully"}) -} + result, err := app.DB.Exec(query, book.Title, book.AuthorID, book.Photo, book.Details, book.IsBorrowed, bookID) + if err != nil { + HandleError(w, app.Logger, "Failed to update book", err, http.StatusInternalServerError) + return + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + HandleError(w, app.Logger, "Failed to retrieve affected rows", err, http.StatusInternalServerError) + return + } + if rowsAffected == 0 { + HandleError(w, app.Logger, "Book not found", nil, http.StatusNotFound) + return + } + RespondWithJSON(w, http.StatusOK, map[string]string{"message": "Book updated successfully"}) +} // UpdateSubscriber updates an existing subscriber in the database func (app *App) UpdateSubscriber(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPut && r.Method != http.MethodPost { - HandleError(w, app.Logger, "Only PUT or POST methods are supported", nil, http.StatusMethodNotAllowed) - return - } - - subscriberID, err := GetIDFromRequest(r, "id") - if err != nil { - HandleError(w, app.Logger, "Invalid subscriber ID", err, http.StatusBadRequest) - return - } - - var subscriber Subscriber - if err := json.NewDecoder(r.Body).Decode(&subscriber); err != nil { - HandleError(w, app.Logger, "Invalid JSON data", err, http.StatusBadRequest) - return - } - defer r.Body.Close() - - if err := ValidateSubscriberData(subscriber); err != nil { - HandleError(w, app.Logger, err.Error(), nil, http.StatusBadRequest) - return - } - - query := ` + if r.Method != http.MethodPut && r.Method != http.MethodPost { + HandleError(w, app.Logger, "Only PUT or POST methods are supported", nil, http.StatusMethodNotAllowed) + return + } + + subscriberID, err := GetIDFromRequest(r, "id") + if err != nil { + HandleError(w, app.Logger, "Invalid subscriber ID", err, http.StatusBadRequest) + return + } + + var subscriber Subscriber + if err := json.NewDecoder(r.Body).Decode(&subscriber); err != nil { + HandleError(w, app.Logger, "Invalid JSON data", err, http.StatusBadRequest) + return + } + defer r.Body.Close() + + if err := ValidateSubscriberData(subscriber); err != nil { + HandleError(w, app.Logger, err.Error(), nil, http.StatusBadRequest) + return + } + + query := ` UPDATE subscribers SET lastname = ?, firstname = ?, email = ? WHERE id = ? ` - result, err := app.DB.Exec(query, subscriber.Lastname, subscriber.Firstname, subscriber.Email, subscriberID) - if err != nil { - HandleError(w, app.Logger, "Failed to update subscriber", err, http.StatusInternalServerError) - return - } - - rowsAffected, err := result.RowsAffected() - if err != nil { - HandleError(w, app.Logger, "Failed to retrieve affected rows", err, http.StatusInternalServerError) - return - } - if rowsAffected == 0 { - HandleError(w, app.Logger, "Subscriber not found", nil, http.StatusNotFound) - return - } - - RespondWithJSON(w, http.StatusOK, map[string]string{"message": "Subscriber updated successfully"}) + result, err := app.DB.Exec(query, subscriber.Lastname, subscriber.Firstname, subscriber.Email, subscriberID) + if err != nil { + HandleError(w, app.Logger, "Failed to update subscriber", err, http.StatusInternalServerError) + return + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + HandleError(w, app.Logger, "Failed to retrieve affected rows", err, http.StatusInternalServerError) + return + } + if rowsAffected == 0 { + HandleError(w, app.Logger, "Subscriber not found", nil, http.StatusNotFound) + return + } + + RespondWithJSON(w, http.StatusOK, map[string]string{"message": "Subscriber updated successfully"}) } // ValidateSubscriberData checks if the required fields for a subscriber are present func ValidateSubscriberData(subscriber Subscriber) error { - if subscriber.Firstname == "" || subscriber.Lastname == "" || subscriber.Email == "" { - return fmt.Errorf("firstname, lastname, and email are required fields") - } - return nil + if subscriber.Firstname == "" || subscriber.Lastname == "" || subscriber.Email == "" { + return fmt.Errorf("firstname, lastname, and email are required fields") + } + return nil } // DeleteAuthor deletes an existing author from the database @@ -1285,4 +1279,3 @@ func (app *App) DeleteSubscriber(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Subscriber deleted successfully") } - diff --git a/api/main_test.go b/api/main_test.go index 3197210..70946e1 100644 --- a/api/main_test.go +++ b/api/main_test.go @@ -257,38 +257,38 @@ func TestRespondWithJSON_Error(t *testing.T) { } type ErrorWriter struct { - HeaderMap http.Header - StatusCode int + HeaderMap http.Header + StatusCode int } func (e *ErrorWriter) Header() http.Header { - if e.HeaderMap == nil { - e.HeaderMap = make(http.Header) - } - return e.HeaderMap + if e.HeaderMap == nil { + e.HeaderMap = make(http.Header) + } + return e.HeaderMap } func (e *ErrorWriter) Write([]byte) (int, error) { - return 0, fmt.Errorf("simulated write error") + return 0, fmt.Errorf("simulated write error") } func (e *ErrorWriter) WriteHeader(statusCode int) { - e.StatusCode = statusCode + e.StatusCode = statusCode } // Test for error handling in RespondWithJSON func TestRespondWithJSON_WriteError(t *testing.T) { - writer := &ErrorWriter{} - payload := map[string]string{"message": "test"} + writer := &ErrorWriter{} + payload := map[string]string{"message": "test"} - RespondWithJSON(writer, http.StatusOK, payload) + RespondWithJSON(writer, http.StatusOK, payload) } // TestHandleError tests the HandleError function func TestHandleError(t *testing.T) { rr := httptest.NewRecorder() - logger := log.New(io.Discard, "", log.LstdFlags) + logger := log.New(io.Discard, "", log.LstdFlags) message := "test error" err := fmt.Errorf("an example error") @@ -583,7 +583,7 @@ func TestSearchBooks_ErrorScanningRows(t *testing.T) { WithArgs("%Sample%", "%Sample%", "%Sample%"). WillReturnRows(sqlmock.NewRows([]string{ "book_id", "book_title", "author_id", "book_photo", "is_borrowed", "book_details", "author_lastname", "author_firstname", - }).AddRow("invalid_id", "Sample Book", 1, "book.jpg", false, "A sample book", "Doe", "John")) + }).AddRow("invalid_id", "Sample Book", 1, "book.jpg", false, "A sample book", "Doe", "John")) handler := http.HandlerFunc(app.SearchBooks) handler.ServeHTTP(rr, req) @@ -2070,49 +2070,49 @@ func TestAddBook_InvalidMethod(t *testing.T) { } func TestAddSubscriber_Success(t *testing.T) { - db, mock, err := sqlmock.New() - if err != nil { - t.Fatalf("An error '%s' was not expected when opening a stub database connection", err) - } - defer db.Close() + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("An error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() - app := &App{ - DB: db, - Logger: log.New(os.Stdout, "test: ", log.LstdFlags), - } + app := &App{ + DB: db, + Logger: log.New(os.Stdout, "test: ", log.LstdFlags), + } - mock.ExpectExec("INSERT INTO subscribers").WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectExec("INSERT INTO subscribers").WillReturnResult(sqlmock.NewResult(1, 1)) - body := bytes.NewBuffer([]byte(`{"firstname": "John", "lastname": "Doe", "email": "john.doe@example.com"}`)) + body := bytes.NewBuffer([]byte(`{"firstname": "John", "lastname": "Doe", "email": "john.doe@example.com"}`)) - req, err := http.NewRequest("POST", "/add-subscriber", body) - if err != nil { - t.Fatal(err) - } + req, err := http.NewRequest("POST", "/add-subscriber", body) + if err != nil { + t.Fatal(err) + } - rr := httptest.NewRecorder() + rr := httptest.NewRecorder() - handler := http.HandlerFunc(app.AddSubscriber) + handler := http.HandlerFunc(app.AddSubscriber) - handler.ServeHTTP(rr, req) + handler.ServeHTTP(rr, req) - if rr.Code != http.StatusCreated { - t.Errorf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusCreated) - } + if rr.Code != http.StatusCreated { + t.Errorf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusCreated) + } - var response map[string]int - if err := json.NewDecoder(rr.Body).Decode(&response); err != nil { - t.Fatalf("Could not decode response: %v", err) - } + var response map[string]int + if err := json.NewDecoder(rr.Body).Decode(&response); err != nil { + t.Fatalf("Could not decode response: %v", err) + } - expected := map[string]int{"id": 1} - if response["id"] != expected["id"] { - t.Errorf("handler returned unexpected body: got %v want %v", response, expected) - } + expected := map[string]int{"id": 1} + if response["id"] != expected["id"] { + t.Errorf("handler returned unexpected body: got %v want %v", response, expected) + } - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("there were unfulfilled expectations: %s", err) - } + if err := mock.ExpectationsWereMet(); err != nil { + t.Errorf("there were unfulfilled expectations: %s", err) + } } func TestAddSubscriber_InvalidJSON(t *testing.T) { @@ -2212,35 +2212,35 @@ func TestAddSubscriber_InvalidMethod(t *testing.T) { } func TestAddSubscriber_EncodingError(t *testing.T) { - db, mock, err := sqlmock.New() - if err != nil { - t.Fatalf("An error '%s' was not expected when opening a stub database connection", err) - } - defer db.Close() + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("An error '%s' was not expected when opening a stub database connection", err) + } + defer db.Close() - app := &App{ - DB: db, - Logger: log.New(os.Stdout, "test: ", log.LstdFlags), - } + app := &App{ + DB: db, + Logger: log.New(os.Stdout, "test: ", log.LstdFlags), + } - mock.ExpectExec("INSERT INTO subscribers").WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectExec("INSERT INTO subscribers").WillReturnResult(sqlmock.NewResult(1, 1)) - body := bytes.NewBuffer([]byte(`{"firstname": "John", "lastname": "Doe", "email": "john.doe@example.com"}`)) + body := bytes.NewBuffer([]byte(`{"firstname": "John", "lastname": "Doe", "email": "john.doe@example.com"}`)) - req, err := http.NewRequest("POST", "/add-subscriber", body) - if err != nil { - t.Fatal(err) - } + req, err := http.NewRequest("POST", "/add-subscriber", body) + if err != nil { + t.Fatal(err) + } - rr := &ErrorWriter{} + rr := &ErrorWriter{} - handler := http.HandlerFunc(app.AddSubscriber) + handler := http.HandlerFunc(app.AddSubscriber) - handler.ServeHTTP(rr, req) + handler.ServeHTTP(rr, req) - if rr.StatusCode != http.StatusInternalServerError { - t.Errorf("handler returned wrong status code: got %v want %v", rr.StatusCode, http.StatusInternalServerError) - } + if rr.StatusCode != http.StatusInternalServerError { + t.Errorf("handler returned wrong status code: got %v want %v", rr.StatusCode, http.StatusInternalServerError) + } } // TestBorrowBook_Success tests the BorrowBook handler when borrowing is successful @@ -2758,255 +2758,254 @@ func TestReturnBorrowedBook_MissingFields(t *testing.T) { // TestUpdateAuthor tests the UpdateAuthor handler func TestUpdateAuthor_Success(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - requestBody := Author{ - Firstname: "John", - Lastname: "Doe", - Photo: "updated_photo.jpg", - } - body, err := json.Marshal(requestBody) - assert.NoError(t, err) + requestBody := Author{ + Firstname: "John", + Lastname: "Doe", + Photo: "updated_photo.jpg", + } + body, err := json.Marshal(requestBody) + assert.NoError(t, err) - req := httptest.NewRequest("PUT", "/authors/1", bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") + req := httptest.NewRequest("PUT", "/authors/1", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") - vars := map[string]string{ - "id": "1", - } - req = mux.SetURLVars(req, vars) + vars := map[string]string{ + "id": "1", + } + req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + rr := httptest.NewRecorder() - mock.ExpectExec(regexp.QuoteMeta(` + mock.ExpectExec(regexp.QuoteMeta(` UPDATE authors SET lastname = ?, firstname = ?, photo = ? WHERE id = ?`)). - WithArgs("Doe", "John", "updated_photo.jpg", 1). - WillReturnResult(sqlmock.NewResult(1, 1)) + WithArgs("Doe", "John", "updated_photo.jpg", 1). + WillReturnResult(sqlmock.NewResult(1, 1)) - handler := http.HandlerFunc(app.UpdateAuthor) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.UpdateAuthor) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) - assert.Contains(t, rr.Body.String(), "Author updated successfully") + assert.Equal(t, http.StatusOK, rr.Code) + assert.Contains(t, rr.Body.String(), "Author updated successfully") - err = mock.ExpectationsWereMet() - assert.NoError(t, err) + err = mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestUpdateAuthor_InvalidJSON(t *testing.T) { - app, _ := createTestApp(t) - defer app.DB.Close() + app, _ := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("PUT", "/authors/1", bytes.NewBuffer([]byte("invalid json body"))) - req.Header.Set("Content-Type", "application/json") + req := httptest.NewRequest("PUT", "/authors/1", bytes.NewBuffer([]byte("invalid json body"))) + req.Header.Set("Content-Type", "application/json") - vars := map[string]string{ - "id": "1", - } - req = mux.SetURLVars(req, vars) + vars := map[string]string{ + "id": "1", + } + req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + rr := httptest.NewRecorder() - handler := http.HandlerFunc(app.UpdateAuthor) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.UpdateAuthor) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusBadRequest, rr.Code) - assert.Contains(t, rr.Body.String(), "Invalid JSON data") + assert.Equal(t, http.StatusBadRequest, rr.Code) + assert.Contains(t, rr.Body.String(), "Invalid JSON data") } func TestUpdateAuthor_InvalidAuthorID(t *testing.T) { - app, _ := createTestApp(t) - defer app.DB.Close() + app, _ := createTestApp(t) + defer app.DB.Close() - requestBody := Author{ - Firstname: "John", - Lastname: "Doe", - Photo: "updated_photo.jpg", - } - body, err := json.Marshal(requestBody) - assert.NoError(t, err) + requestBody := Author{ + Firstname: "John", + Lastname: "Doe", + Photo: "updated_photo.jpg", + } + body, err := json.Marshal(requestBody) + assert.NoError(t, err) - req := httptest.NewRequest("PUT", "/authors/invalid", bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") + req := httptest.NewRequest("PUT", "/authors/invalid", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") - rr := httptest.NewRecorder() + rr := httptest.NewRecorder() - handler := http.HandlerFunc(app.UpdateAuthor) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.UpdateAuthor) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusBadRequest, rr.Code) - assert.Contains(t, rr.Body.String(), "Invalid author ID") + assert.Equal(t, http.StatusBadRequest, rr.Code) + assert.Contains(t, rr.Body.String(), "Invalid author ID") } func TestUpdateAuthor_ValidationError(t *testing.T) { - app, _ := createTestApp(t) - defer app.DB.Close() + app, _ := createTestApp(t) + defer app.DB.Close() - requestBody := Author{ - Firstname: "", - Lastname: "", - Photo: "updated_photo.jpg", - } - body, err := json.Marshal(requestBody) - assert.NoError(t, err) + requestBody := Author{ + Firstname: "", + Lastname: "", + Photo: "updated_photo.jpg", + } + body, err := json.Marshal(requestBody) + assert.NoError(t, err) - req := httptest.NewRequest("PUT", "/authors/1", bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") + req := httptest.NewRequest("PUT", "/authors/1", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") - vars := map[string]string{ - "id": "1", - } - req = mux.SetURLVars(req, vars) + vars := map[string]string{ + "id": "1", + } + req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + rr := httptest.NewRecorder() - handler := http.HandlerFunc(app.UpdateAuthor) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.UpdateAuthor) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusBadRequest, rr.Code) - assert.Contains(t, rr.Body.String(), "Firstname and Lastname are required fields") + assert.Equal(t, http.StatusBadRequest, rr.Code) + assert.Contains(t, rr.Body.String(), "Firstname and Lastname are required fields") } func TestUpdateAuthor_AuthorNotFound(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - requestBody := Author{ - Firstname: "John", - Lastname: "Doe", - Photo: "updated_photo.jpg", - } - body, err := json.Marshal(requestBody) - assert.NoError(t, err) + requestBody := Author{ + Firstname: "John", + Lastname: "Doe", + Photo: "updated_photo.jpg", + } + body, err := json.Marshal(requestBody) + assert.NoError(t, err) - req := httptest.NewRequest("PUT", "/authors/1", bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") + req := httptest.NewRequest("PUT", "/authors/1", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") - vars := map[string]string{ - "id": "1", - } - req = mux.SetURLVars(req, vars) + vars := map[string]string{ + "id": "1", + } + req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + rr := httptest.NewRecorder() - mock.ExpectExec(regexp.QuoteMeta(` + mock.ExpectExec(regexp.QuoteMeta(` UPDATE authors SET lastname = ?, firstname = ?, photo = ? WHERE id = ?`)). - WithArgs("Doe", "John", "updated_photo.jpg", 1). - WillReturnResult(sqlmock.NewResult(1, 0)) + WithArgs("Doe", "John", "updated_photo.jpg", 1). + WillReturnResult(sqlmock.NewResult(1, 0)) - handler := http.HandlerFunc(app.UpdateAuthor) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.UpdateAuthor) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusNotFound, rr.Code) - assert.Contains(t, rr.Body.String(), "Author not found") + assert.Equal(t, http.StatusNotFound, rr.Code) + assert.Contains(t, rr.Body.String(), "Author not found") - err = mock.ExpectationsWereMet() - assert.NoError(t, err) + err = mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestUpdateAuthor_DBError(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - requestBody := Author{ - Firstname: "John", - Lastname: "Doe", - Photo: "updated_photo.jpg", - } - body, err := json.Marshal(requestBody) - assert.NoError(t, err) + requestBody := Author{ + Firstname: "John", + Lastname: "Doe", + Photo: "updated_photo.jpg", + } + body, err := json.Marshal(requestBody) + assert.NoError(t, err) - req := httptest.NewRequest("PUT", "/authors/1", bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") + req := httptest.NewRequest("PUT", "/authors/1", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") - vars := map[string]string{ - "id": "1", - } - req = mux.SetURLVars(req, vars) + vars := map[string]string{ + "id": "1", + } + req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + rr := httptest.NewRecorder() - mock.ExpectExec(regexp.QuoteMeta(` + mock.ExpectExec(regexp.QuoteMeta(` UPDATE authors SET lastname = ?, firstname = ?, photo = ? WHERE id = ?`)). - WithArgs("Doe", "John", "updated_photo.jpg", 1). - WillReturnError(fmt.Errorf("DB error")) + WithArgs("Doe", "John", "updated_photo.jpg", 1). + WillReturnError(fmt.Errorf("DB error")) - handler := http.HandlerFunc(app.UpdateAuthor) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.UpdateAuthor) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusInternalServerError, rr.Code) - assert.Contains(t, rr.Body.String(), "Failed to update author") + assert.Equal(t, http.StatusInternalServerError, rr.Code) + assert.Contains(t, rr.Body.String(), "Failed to update author") - err = mock.ExpectationsWereMet() - assert.NoError(t, err) + err = mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestUpdateAuthor_MethodNotAllowed(t *testing.T) { - app, _ := createTestApp(t) - defer app.DB.Close() + app, _ := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("GET", "/authors/1", nil) - rr := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/authors/1", nil) + rr := httptest.NewRecorder() - vars := map[string]string{ - "id": "1", - } - req = mux.SetURLVars(req, vars) + vars := map[string]string{ + "id": "1", + } + req = mux.SetURLVars(req, vars) - handler := http.HandlerFunc(app.UpdateAuthor) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.UpdateAuthor) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusMethodNotAllowed, rr.Code) - assert.Contains(t, rr.Body.String(), "Only PUT or POST methods are supported") + assert.Equal(t, http.StatusMethodNotAllowed, rr.Code) + assert.Contains(t, rr.Body.String(), "Only PUT or POST methods are supported") } func TestUpdateAuthor_FailedToRetrieveAffectedRows(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - requestBody := Author{ - Firstname: "John", - Lastname: "Doe", - Photo: "updated_photo.jpg", - } - body, err := json.Marshal(requestBody) - assert.NoError(t, err) + requestBody := Author{ + Firstname: "John", + Lastname: "Doe", + Photo: "updated_photo.jpg", + } + body, err := json.Marshal(requestBody) + assert.NoError(t, err) - req := httptest.NewRequest("PUT", "/authors/1", bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") + req := httptest.NewRequest("PUT", "/authors/1", bytes.NewBuffer(body)) + req.Header.Set("Content-Type", "application/json") - vars := map[string]string{ - "id": "1", - } - req = mux.SetURLVars(req, vars) + vars := map[string]string{ + "id": "1", + } + req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + rr := httptest.NewRecorder() - mock.ExpectExec(regexp.QuoteMeta(` + mock.ExpectExec(regexp.QuoteMeta(` UPDATE authors SET lastname = ?, firstname = ?, photo = ? WHERE id = ?`)). - WithArgs("Doe", "John", "updated_photo.jpg", 1). - WillReturnResult(sqlmock.NewErrorResult(fmt.Errorf("RowsAffected error"))) + WithArgs("Doe", "John", "updated_photo.jpg", 1). + WillReturnResult(sqlmock.NewErrorResult(fmt.Errorf("RowsAffected error"))) - handler := http.HandlerFunc(app.UpdateAuthor) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.UpdateAuthor) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusInternalServerError, rr.Code) - assert.Contains(t, rr.Body.String(), "Failed to retrieve affected rows") + assert.Equal(t, http.StatusInternalServerError, rr.Code) + assert.Contains(t, rr.Body.String(), "Failed to retrieve affected rows") - err = mock.ExpectationsWereMet() - assert.NoError(t, err) + err = mock.ExpectationsWereMet() + assert.NoError(t, err) } - // Tests for UpdateBook handler func TestUpdateBook_Success(t *testing.T) { app, mock := createTestApp(t) @@ -3520,511 +3519,508 @@ func TestUpdateSubscriber_FailedToRetrieveAffectedRows(t *testing.T) { // Tests for DeleteAuthor handler func TestDeleteAuthor_Success(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/authors/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/authors/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - mock.ExpectQuery(`SELECT COUNT\(\*\) FROM books WHERE author_id = ?`). - WithArgs(1). - WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) + mock.ExpectQuery(`SELECT COUNT\(\*\) FROM books WHERE author_id = ?`). + WithArgs(1). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) - mock.ExpectExec(`DELETE FROM authors WHERE id = ?`). - WithArgs(1). - WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectExec(`DELETE FROM authors WHERE id = ?`). + WithArgs(1). + WillReturnResult(sqlmock.NewResult(1, 1)) - handler := http.HandlerFunc(app.DeleteAuthor) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteAuthor) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) - assert.Contains(t, rr.Body.String(), "Author deleted successfully") + assert.Equal(t, http.StatusOK, rr.Code) + assert.Contains(t, rr.Body.String(), "Author deleted successfully") - err := mock.ExpectationsWereMet() - assert.NoError(t, err) + err := mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestDeleteAuthor_InvalidAuthorID(t *testing.T) { - app, _ := createTestApp(t) - defer app.DB.Close() + app, _ := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/authors/invalid", nil) - vars := map[string]string{"id": "invalid"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/authors/invalid", nil) + vars := map[string]string{"id": "invalid"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - handler := http.HandlerFunc(app.DeleteAuthor) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteAuthor) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusBadRequest, rr.Code) - assert.Contains(t, rr.Body.String(), "Invalid author ID") + assert.Equal(t, http.StatusBadRequest, rr.Code) + assert.Contains(t, rr.Body.String(), "Invalid author ID") } func TestDeleteAuthor_HasAssociatedBooks(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/authors/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/authors/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - mock.ExpectQuery(`SELECT COUNT\(\*\) FROM books WHERE author_id = ?`). - WithArgs(1). - WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(5)) + mock.ExpectQuery(`SELECT COUNT\(\*\) FROM books WHERE author_id = ?`). + WithArgs(1). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(5)) - handler := http.HandlerFunc(app.DeleteAuthor) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteAuthor) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusBadRequest, rr.Code) - assert.Contains(t, rr.Body.String(), "Author has associated books, delete books first") + assert.Equal(t, http.StatusBadRequest, rr.Code) + assert.Contains(t, rr.Body.String(), "Author has associated books, delete books first") - err := mock.ExpectationsWereMet() - assert.NoError(t, err) + err := mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestDeleteAuthor_DBErrorCheckingBooks(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/authors/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/authors/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - mock.ExpectQuery(`SELECT COUNT\(\*\) FROM books WHERE author_id = ?`). - WithArgs(1). - WillReturnError(fmt.Errorf("DB error")) + mock.ExpectQuery(`SELECT COUNT\(\*\) FROM books WHERE author_id = ?`). + WithArgs(1). + WillReturnError(fmt.Errorf("DB error")) - handler := http.HandlerFunc(app.DeleteAuthor) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteAuthor) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusInternalServerError, rr.Code) - assert.Contains(t, rr.Body.String(), "Failed to check for books") + assert.Equal(t, http.StatusInternalServerError, rr.Code) + assert.Contains(t, rr.Body.String(), "Failed to check for books") - err := mock.ExpectationsWereMet() - assert.NoError(t, err) + err := mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestDeleteAuthor_NotFound(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/authors/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/authors/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - mock.ExpectQuery(`SELECT COUNT\(\*\) FROM books WHERE author_id = ?`). - WithArgs(1). - WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) + mock.ExpectQuery(`SELECT COUNT\(\*\) FROM books WHERE author_id = ?`). + WithArgs(1). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) - mock.ExpectExec(`DELETE FROM authors WHERE id = ?`). - WithArgs(1). - WillReturnResult(sqlmock.NewResult(1, 0)) + mock.ExpectExec(`DELETE FROM authors WHERE id = ?`). + WithArgs(1). + WillReturnResult(sqlmock.NewResult(1, 0)) - handler := http.HandlerFunc(app.DeleteAuthor) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteAuthor) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusNotFound, rr.Code) - assert.Contains(t, rr.Body.String(), "Author not found") + assert.Equal(t, http.StatusNotFound, rr.Code) + assert.Contains(t, rr.Body.String(), "Author not found") - err := mock.ExpectationsWereMet() - assert.NoError(t, err) + err := mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestDeleteAuthor_DBErrorDeletingAuthor(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/authors/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/authors/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - mock.ExpectQuery(`SELECT COUNT\(\*\) FROM books WHERE author_id = ?`). - WithArgs(1). - WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) + mock.ExpectQuery(`SELECT COUNT\(\*\) FROM books WHERE author_id = ?`). + WithArgs(1). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) - mock.ExpectExec(`DELETE FROM authors WHERE id = ?`). - WithArgs(1). - WillReturnError(fmt.Errorf("DB error")) + mock.ExpectExec(`DELETE FROM authors WHERE id = ?`). + WithArgs(1). + WillReturnError(fmt.Errorf("DB error")) - handler := http.HandlerFunc(app.DeleteAuthor) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteAuthor) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusInternalServerError, rr.Code) - assert.Contains(t, rr.Body.String(), "Failed to delete author") + assert.Equal(t, http.StatusInternalServerError, rr.Code) + assert.Contains(t, rr.Body.String(), "Failed to delete author") - err := mock.ExpectationsWereMet() - assert.NoError(t, err) + err := mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestDeleteAuthor_MethodNotAllowed(t *testing.T) { - app, _ := createTestApp(t) - defer app.DB.Close() + app, _ := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("GET", "/authors/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/authors/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - handler := http.HandlerFunc(app.DeleteAuthor) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteAuthor) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusMethodNotAllowed, rr.Code) - assert.Contains(t, rr.Body.String(), "Only DELETE method is supported") + assert.Equal(t, http.StatusMethodNotAllowed, rr.Code) + assert.Contains(t, rr.Body.String(), "Only DELETE method is supported") } // Tests for DeleteBook handler func TestDeleteBook_Success(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/books/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/books/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT author_id FROM books WHERE id = ?`)). - WithArgs(1). - WillReturnRows(sqlmock.NewRows([]string{"author_id"}).AddRow(1)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT author_id FROM books WHERE id = ?`)). + WithArgs(1). + WillReturnRows(sqlmock.NewRows([]string{"author_id"}).AddRow(1)) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM books WHERE author_id = ? AND id != ?`)). - WithArgs(1, 1). - WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM books WHERE author_id = ? AND id != ?`)). + WithArgs(1, 1). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1)) - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM books WHERE id = ?`)). - WithArgs(1). - WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM books WHERE id = ?`)). + WithArgs(1). + WillReturnResult(sqlmock.NewResult(1, 1)) - handler := http.HandlerFunc(app.DeleteBook) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteBook) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) - assert.Contains(t, rr.Body.String(), "Book deleted successfully") + assert.Equal(t, http.StatusOK, rr.Code) + assert.Contains(t, rr.Body.String(), "Book deleted successfully") - err := mock.ExpectationsWereMet() - assert.NoError(t, err) + err := mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestDeleteBook_InvalidBookID(t *testing.T) { - app, _ := createTestApp(t) - defer app.DB.Close() + app, _ := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/books/invalid", nil) - vars := map[string]string{"id": "invalid"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/books/invalid", nil) + vars := map[string]string{"id": "invalid"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - handler := http.HandlerFunc(app.DeleteBook) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteBook) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusBadRequest, rr.Code) - assert.Contains(t, rr.Body.String(), "Invalid book ID") + assert.Equal(t, http.StatusBadRequest, rr.Code) + assert.Contains(t, rr.Body.String(), "Invalid book ID") } func TestDeleteBook_DBErrorRetrievingAuthorID(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/books/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/books/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT author_id FROM books WHERE id = ?`)). - WithArgs(1). - WillReturnError(fmt.Errorf("DB error")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT author_id FROM books WHERE id = ?`)). + WithArgs(1). + WillReturnError(fmt.Errorf("DB error")) - handler := http.HandlerFunc(app.DeleteBook) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteBook) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusInternalServerError, rr.Code) - assert.Contains(t, rr.Body.String(), "Failed to retrieve author ID") + assert.Equal(t, http.StatusInternalServerError, rr.Code) + assert.Contains(t, rr.Body.String(), "Failed to retrieve author ID") - err := mock.ExpectationsWereMet() - assert.NoError(t, err) + err := mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestDeleteBook_DBErrorCheckingOtherBooks(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/books/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/books/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT author_id FROM books WHERE id = ?`)). - WithArgs(1). - WillReturnRows(sqlmock.NewRows([]string{"author_id"}).AddRow(1)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT author_id FROM books WHERE id = ?`)). + WithArgs(1). + WillReturnRows(sqlmock.NewRows([]string{"author_id"}).AddRow(1)) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM books WHERE author_id = ? AND id != ?`)). - WithArgs(1, 1). - WillReturnError(fmt.Errorf("DB error")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM books WHERE author_id = ? AND id != ?`)). + WithArgs(1, 1). + WillReturnError(fmt.Errorf("DB error")) - handler := http.HandlerFunc(app.DeleteBook) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteBook) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusInternalServerError, rr.Code) - assert.Contains(t, rr.Body.String(), "Failed to check for other books") + assert.Equal(t, http.StatusInternalServerError, rr.Code) + assert.Contains(t, rr.Body.String(), "Failed to check for other books") - err := mock.ExpectationsWereMet() - assert.NoError(t, err) + err := mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestDeleteBook_NotFound(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/books/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/books/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT author_id FROM books WHERE id = ?`)). - WithArgs(1). - WillReturnRows(sqlmock.NewRows([]string{"author_id"}).AddRow(1)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT author_id FROM books WHERE id = ?`)). + WithArgs(1). + WillReturnRows(sqlmock.NewRows([]string{"author_id"}).AddRow(1)) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM books WHERE author_id = ? AND id != ?`)). - WithArgs(1, 1). - WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM books WHERE author_id = ? AND id != ?`)). + WithArgs(1, 1). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM books WHERE id = ?`)). - WithArgs(1). - WillReturnResult(sqlmock.NewResult(1, 0)) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM books WHERE id = ?`)). + WithArgs(1). + WillReturnResult(sqlmock.NewResult(1, 0)) - handler := http.HandlerFunc(app.DeleteBook) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteBook) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusNotFound, rr.Code) - assert.Contains(t, rr.Body.String(), "Book not found") + assert.Equal(t, http.StatusNotFound, rr.Code) + assert.Contains(t, rr.Body.String(), "Book not found") - err := mock.ExpectationsWereMet() - assert.NoError(t, err) + err := mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestDeleteBook_DBErrorDeletingBook(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/books/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/books/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT author_id FROM books WHERE id = ?`)). - WithArgs(1). - WillReturnRows(sqlmock.NewRows([]string{"author_id"}).AddRow(1)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT author_id FROM books WHERE id = ?`)). + WithArgs(1). + WillReturnRows(sqlmock.NewRows([]string{"author_id"}).AddRow(1)) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM books WHERE author_id = ? AND id != ?`)). - WithArgs(1, 1). - WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM books WHERE author_id = ? AND id != ?`)). + WithArgs(1, 1). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM books WHERE id = ?`)). - WithArgs(1). - WillReturnError(fmt.Errorf("DB error")) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM books WHERE id = ?`)). + WithArgs(1). + WillReturnError(fmt.Errorf("DB error")) - handler := http.HandlerFunc(app.DeleteBook) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteBook) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusInternalServerError, rr.Code) - assert.Contains(t, rr.Body.String(), "Failed to delete book") + assert.Equal(t, http.StatusInternalServerError, rr.Code) + assert.Contains(t, rr.Body.String(), "Failed to delete book") - err := mock.ExpectationsWereMet() - assert.NoError(t, err) + err := mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestDeleteBook_DeleteAuthorWhenNoOtherBooks(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/books/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/books/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT author_id FROM books WHERE id = ?`)). - WithArgs(1). - WillReturnRows(sqlmock.NewRows([]string{"author_id"}).AddRow(1)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT author_id FROM books WHERE id = ?`)). + WithArgs(1). + WillReturnRows(sqlmock.NewRows([]string{"author_id"}).AddRow(1)) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM books WHERE author_id = ? AND id != ?`)). - WithArgs(1, 1). - WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM books WHERE author_id = ? AND id != ?`)). + WithArgs(1, 1). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM books WHERE id = ?`)). - WithArgs(1). - WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM books WHERE id = ?`)). + WithArgs(1). + WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM authors WHERE id = ?`)). - WithArgs(1). - WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM authors WHERE id = ?`)). + WithArgs(1). + WillReturnResult(sqlmock.NewResult(1, 1)) - handler := http.HandlerFunc(app.DeleteBook) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteBook) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) - assert.Contains(t, rr.Body.String(), "Book deleted successfully") + assert.Equal(t, http.StatusOK, rr.Code) + assert.Contains(t, rr.Body.String(), "Book deleted successfully") - err := mock.ExpectationsWereMet() - assert.NoError(t, err) + err := mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestDeleteBook_MethodNotAllowed(t *testing.T) { - app, _ := createTestApp(t) - defer app.DB.Close() + app, _ := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("GET", "/books/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/books/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - handler := http.HandlerFunc(app.DeleteBook) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteBook) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusMethodNotAllowed, rr.Code) - assert.Contains(t, rr.Body.String(), "Only DELETE method is supported") + assert.Equal(t, http.StatusMethodNotAllowed, rr.Code) + assert.Contains(t, rr.Body.String(), "Only DELETE method is supported") } func TestDeleteBook_FailedToDeleteAuthor(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/books/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/books/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - mock.ExpectQuery(regexp.QuoteMeta(`SELECT author_id FROM books WHERE id = ?`)). - WithArgs(1). - WillReturnRows(sqlmock.NewRows([]string{"author_id"}).AddRow(1)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT author_id FROM books WHERE id = ?`)). + WithArgs(1). + WillReturnRows(sqlmock.NewRows([]string{"author_id"}).AddRow(1)) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM books WHERE author_id = ? AND id != ?`)). - WithArgs(1, 1). - WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM books WHERE author_id = ? AND id != ?`)). + WithArgs(1, 1). + WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0)) - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM books WHERE id = ?`)). - WithArgs(1). - WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM books WHERE id = ?`)). + WithArgs(1). + WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM authors WHERE id = ?`)). - WithArgs(1). - WillReturnError(fmt.Errorf("DB error")) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM authors WHERE id = ?`)). + WithArgs(1). + WillReturnError(fmt.Errorf("DB error")) - handler := http.HandlerFunc(app.DeleteBook) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteBook) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusInternalServerError, rr.Code) - assert.Contains(t, rr.Body.String(), "Failed to delete author") + assert.Equal(t, http.StatusInternalServerError, rr.Code) + assert.Contains(t, rr.Body.String(), "Failed to delete author") - err := mock.ExpectationsWereMet() - assert.NoError(t, err) + err := mock.ExpectationsWereMet() + assert.NoError(t, err) } // Tests for DeleteSubscriber handler func TestDeleteSubscriber_Success(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/subscribers/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/subscribers/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM subscribers WHERE id = ?`)). - WithArgs(1). - WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM subscribers WHERE id = ?`)). + WithArgs(1). + WillReturnResult(sqlmock.NewResult(1, 1)) - handler := http.HandlerFunc(app.DeleteSubscriber) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteSubscriber) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) - assert.Contains(t, rr.Body.String(), "Subscriber deleted successfully") + assert.Equal(t, http.StatusOK, rr.Code) + assert.Contains(t, rr.Body.String(), "Subscriber deleted successfully") - err := mock.ExpectationsWereMet() - assert.NoError(t, err) + err := mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestDeleteSubscriber_InvalidSubscriberID(t *testing.T) { - app, _ := createTestApp(t) - defer app.DB.Close() + app, _ := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/subscribers/invalid", nil) - vars := map[string]string{"id": "invalid"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/subscribers/invalid", nil) + vars := map[string]string{"id": "invalid"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - handler := http.HandlerFunc(app.DeleteSubscriber) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteSubscriber) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusBadRequest, rr.Code) - assert.Contains(t, rr.Body.String(), "Invalid subscriber ID") + assert.Equal(t, http.StatusBadRequest, rr.Code) + assert.Contains(t, rr.Body.String(), "Invalid subscriber ID") } func TestDeleteSubscriber_NotFound(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/subscribers/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/subscribers/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM subscribers WHERE id = ?`)). - WithArgs(1). - WillReturnResult(sqlmock.NewResult(1, 0)) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM subscribers WHERE id = ?`)). + WithArgs(1). + WillReturnResult(sqlmock.NewResult(1, 0)) - handler := http.HandlerFunc(app.DeleteSubscriber) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteSubscriber) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusNotFound, rr.Code) - assert.Contains(t, rr.Body.String(), "Subscriber not found") + assert.Equal(t, http.StatusNotFound, rr.Code) + assert.Contains(t, rr.Body.String(), "Subscriber not found") - err := mock.ExpectationsWereMet() - assert.NoError(t, err) + err := mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestDeleteSubscriber_DBError(t *testing.T) { - app, mock := createTestApp(t) - defer app.DB.Close() + app, mock := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("DELETE", "/subscribers/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/subscribers/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM subscribers WHERE id = ?`)). - WithArgs(1). - WillReturnError(fmt.Errorf("DB error")) + mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM subscribers WHERE id = ?`)). + WithArgs(1). + WillReturnError(fmt.Errorf("DB error")) - handler := http.HandlerFunc(app.DeleteSubscriber) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteSubscriber) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusInternalServerError, rr.Code) - assert.Contains(t, rr.Body.String(), "Failed to delete subscriber") + assert.Equal(t, http.StatusInternalServerError, rr.Code) + assert.Contains(t, rr.Body.String(), "Failed to delete subscriber") - err := mock.ExpectationsWereMet() - assert.NoError(t, err) + err := mock.ExpectationsWereMet() + assert.NoError(t, err) } func TestDeleteSubscriber_MethodNotAllowed(t *testing.T) { - app, _ := createTestApp(t) - defer app.DB.Close() + app, _ := createTestApp(t) + defer app.DB.Close() - req := httptest.NewRequest("GET", "/subscribers/1", nil) - vars := map[string]string{"id": "1"} - req = mux.SetURLVars(req, vars) - rr := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/subscribers/1", nil) + vars := map[string]string{"id": "1"} + req = mux.SetURLVars(req, vars) + rr := httptest.NewRecorder() - handler := http.HandlerFunc(app.DeleteSubscriber) - handler.ServeHTTP(rr, req) + handler := http.HandlerFunc(app.DeleteSubscriber) + handler.ServeHTTP(rr, req) - assert.Equal(t, http.StatusMethodNotAllowed, rr.Code) - assert.Contains(t, rr.Body.String(), "Only DELETE method is supported") + assert.Equal(t, http.StatusMethodNotAllowed, rr.Code) + assert.Contains(t, rr.Body.String(), "Only DELETE method is supported") } - - -