Skip to content

Commit

Permalink
Merge branch 'test'
Browse files Browse the repository at this point in the history
  • Loading branch information
kweeuhree committed Jan 19, 2025
2 parents b92dc1a + 801ae5e commit 0dc3252
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 65 deletions.
13 changes: 13 additions & 0 deletions cmd/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,16 @@ func (h *Handlers) ReceiptFactory(input ReceiptInput) (models.Receipt, error) {

return newReceipt, nil
}

func (h *Handlers) DeleteReceipt(w http.ResponseWriter, r *http.Request) {
receiptID := h.Helpers.GetIdFromParams(r, "id")

// Remove the receipt from receiptStore
err := h.ReceiptStore.Delete(receiptID)

if err != nil {
h.ErrorLog.Printf("Failed to delete the receipt with ID %s. Error: %+v", receiptID, err)
return
}
h.Helpers.EncodeJSON(w, http.StatusNoContent, "")
}
137 changes: 127 additions & 10 deletions cmd/handlers/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,47 @@ package handlers

import (
"bytes"
"context"
"encoding/json"
"log"
"net/http"
"net/http/httptest"
"testing"

"github.com/julienschmidt/httprouter"
"kweeuhree.receipt-processor-challenge/cmd/helpers"
"kweeuhree.receipt-processor-challenge/cmd/utils"
"kweeuhree.receipt-processor-challenge/internal/models"
)

var handlers *Handlers
var receiptStore *models.ReceiptStore
type TestDependencies struct {
receiptStore *models.ReceiptStore
utils *utils.Utils
helpers *helpers.Helpers
handlers *Handlers
}

func TestMain(m *testing.M) {
receiptStore = models.NewStore()
func setupTestDependencies() *TestDependencies {
receiptStore := models.NewStore()
utils := &utils.Utils{}
helpers := &helpers.Helpers{}
handlers = NewHandlers(log.Default(), log.Default(), receiptStore, utils, helpers)
m.Run()
handlers := NewHandlers(log.Default(), log.Default(), receiptStore, utils, helpers)

return &TestDependencies{
receiptStore: receiptStore,
utils: utils,
helpers: helpers,
handlers: handlers,
}
}

// Writes an OK status to the response
func mockHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
w.WriteHeader(http.StatusOK)
}

func TestProcessReceipt(t *testing.T) {
d := setupTestDependencies()
tests := []struct {
name string
input ReceiptInput
Expand All @@ -48,12 +66,12 @@ func TestProcessReceipt(t *testing.T) {
t.Fatalf("Failed to marshal input: %v", err)
}
// Create request and response
req := httptest.NewRequest("POST", "/receipts/process", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req := httptest.NewRequest(http.MethodPost, "/receipts/process", bytes.NewBuffer(body))
// req.Header.Set("Content-Type", "application/json")
resp := httptest.NewRecorder()

// Test ProcessReceipt function
handlers.ProcessReceipt(resp, req)
d.handlers.ProcessReceipt(resp, req)

// Check the response status
if status := resp.Code; status != entry.expectedStatus {
Expand Down Expand Up @@ -88,8 +106,107 @@ func TestProcessReceipt(t *testing.T) {
}
}

})
}
}

func TestGetReceiptPoints(t *testing.T) {
d := setupTestDependencies()
router := httprouter.New()
// Register the route
router.GET("/receipts/:id/points", mockHandler)

tests := []struct {
name string
ID string
url string
status int
}{
{"Valid receipt id", SimpleReceipt.ID, "/receipts/123-qwe-456-rty-7890/points", http.StatusOK},
{"Invalid receipt id", "hello-world", "/receipts/hello-world/points", http.StatusNotFound},
{"Invalid request url", SimpleReceipt.ID, "/hello/123-qwe-456-rty-7890/world", http.StatusNotFound},
}

for _, entry := range tests {
t.Run(entry.name, func(t *testing.T) {
d.receiptStore.Insert(*SimpleReceipt)

// Create request and response
req := httptest.NewRequest(http.MethodGet, entry.url, nil)
// Create request context that will enable id extraction
params := httprouter.Params{
httprouter.Param{Key: "id", Value: entry.ID},
}
ctx := context.WithValue(req.Context(), httprouter.ParamsKey, params)
req = req.WithContext(ctx)
// Create response
resp := httptest.NewRecorder()

// Serve the request
router.ServeHTTP(resp, req)

// Test GetReceiptPoints function
d.handlers.GetReceiptPoints(resp, req)

// Check response status
if resp.Code == http.StatusOK && entry.status == http.StatusOK {
var response PointsResponse
// Decode the request body
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
t.Fatalf("Failed to decode response: %v", err)
}
// Check if the points match
if response.Points != SimpleReceipt.Points {
t.Fatalf("Expected %d points, received %d", SimpleReceipt.Points, response.Points)
}
}

t.Cleanup(func() {
d.receiptStore = models.NewStore()
})
})
}
}

func TestDeleteReceipt(t *testing.T) {
d := setupTestDependencies()
router := httprouter.New()
// Register the route
router.DELETE("/receipts/:id/delete", mockHandler)

tests := []struct {
name string
id string
url string
}{
{"Vaild id", SimpleReceipt.ID, "/receipts/123-qwe-456-rty-7890/delete"},
{"Invalid id", "123", "/receipts/123/delete"},
{"Empty id", "", "/receipts//delete"},
}

for _, entry := range tests {
t.Run(entry.name, func(t *testing.T) {
d.receiptStore.Insert(*SimpleReceipt)
// Construct request with context
req := httptest.NewRequest(http.MethodDelete, entry.url, nil)
params := httprouter.Params{
httprouter.Param{Key: "id", Value: entry.id},
}
ctx := context.WithValue(req.Context(), httprouter.ParamsKey, params)
req = req.WithContext(ctx)
// Create a response
resp := httptest.NewRecorder()

// Attempt to delete the receipt
d.handlers.DeleteReceipt(resp, req)

receipt, _ := d.receiptStore.Get(entry.id)
if receipt.ID != "" {
t.Errorf("Expected receipt with id %s to be deleted, but it was not.", entry.id)
}

t.Cleanup(func() {
receiptStore = models.NewStore()
d.receiptStore = models.NewStore()
})
})
}
Expand Down
16 changes: 16 additions & 0 deletions cmd/handlers/handlers_test_cases.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@ package handlers

import "kweeuhree.receipt-processor-challenge/internal/models"

var SimpleReceipt = &models.Receipt{
ID: "123-qwe-456-rty-7890",
Retailer: "Target",
PurchaseDate: "2022-01-01",
PurchaseTime: "13:01",
Items: []models.Item{
{ShortDescription: "Mountain Dew 12PK", Price: "6.49"},
{ShortDescription: "Emils Cheese Pizza", Price: "12.25"},
{ShortDescription: "Knorr Creamy Chicken", Price: "1.26"},
{ShortDescription: "Doritos Nacho Cheese", Price: "3.35"},
{ShortDescription: " Klarbrunn 12-PK 12 FL OZ ", Price: "12.00"},
},
Total: "35.35",
Points: 28,
}

var ValidReceipt = &ReceiptInput{
Retailer: "Target",
PurchaseDate: "2022-01-02",
Expand Down
3 changes: 1 addition & 2 deletions cmd/handlers/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import (
"kweeuhree.receipt-processor-challenge/internal/validator"
)

var v *validator.Validator

func (input *ReceiptInput) Validate() {
var v *validator.Validator
input.CheckField(v.NotBlank(input.Retailer), "retailerName", "This field cannot be blank")
input.CheckField(v.NotBlank(input.PurchaseDate), "purchaseDate", "This field cannot be blank")
input.CheckField(v.ValidDate(input.PurchaseDate), "purchaseDate", "This field must be a valid date")
Expand Down
25 changes: 17 additions & 8 deletions cmd/helpers/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,10 @@ import (
"github.com/julienschmidt/httprouter"
)

var h *Helpers

func TestMain(m *testing.M) {
h = &Helpers{
func TestServerError(t *testing.T) {
h := &Helpers{
ErrorLog: log.Default(),
}
m.Run()
}

func TestServerError(t *testing.T) {
tests := []struct {
name string
err error
Expand Down Expand Up @@ -54,6 +48,9 @@ func TestServerError(t *testing.T) {
}

func TestClientError(t *testing.T) {
h := &Helpers{
ErrorLog: log.Default(),
}
tests := []struct {
name string
status int
Expand All @@ -76,6 +73,9 @@ func TestClientError(t *testing.T) {
}

func TestNotFound(t *testing.T) {
h := &Helpers{
ErrorLog: log.Default(),
}
resp := httptest.NewRecorder()
h.NotFound(resp)

Expand All @@ -85,6 +85,9 @@ func TestNotFound(t *testing.T) {
}

func TestDecodeJSON(t *testing.T) {
h := &Helpers{
ErrorLog: log.Default(),
}
tests := []struct {
name string
reqBody string
Expand Down Expand Up @@ -114,6 +117,9 @@ func TestDecodeJSON(t *testing.T) {
}

func TestEncodeJSON(t *testing.T) {
h := &Helpers{
ErrorLog: log.Default(),
}
tests := []struct {
name string
data interface{}
Expand Down Expand Up @@ -151,6 +157,9 @@ func TestEncodeJSON(t *testing.T) {
}

func TestGetIdFromParams(t *testing.T) {
h := &Helpers{
ErrorLog: log.Default(),
}
tests := []struct {
name string
paramsId string
Expand Down
16 changes: 8 additions & 8 deletions cmd/utils/calculate-utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,8 @@ import (
"kweeuhree.receipt-processor-challenge/testdata"
)

// Declare and initialize application instance for all tests
var utils *Utils

func TestMain(m *testing.M) {
utils = &Utils{}
m.Run()
}

func Test_getRetailerNamePoints(t *testing.T) {
var utils *Utils
tests := []struct {
name string
expected int
Expand All @@ -37,6 +30,7 @@ func Test_getRetailerNamePoints(t *testing.T) {
}

func Test_isAlphanumeric(t *testing.T) {
var utils *Utils
tests := []struct {
name string
char string
Expand All @@ -61,6 +55,7 @@ func Test_isAlphanumeric(t *testing.T) {
}

func Test_getRoundTotalPoints(t *testing.T) {
var utils *Utils
tests := []struct {
name string
num float64
Expand All @@ -85,6 +80,7 @@ func Test_getRoundTotalPoints(t *testing.T) {
}

func Test_getQuartersPoints(t *testing.T) {
var utils *Utils
tests := []struct {
name string
num float64
Expand All @@ -109,6 +105,7 @@ func Test_getQuartersPoints(t *testing.T) {
}

func Test_getEveryTwoItemsPoints(t *testing.T) {
var utils *Utils
tests := []struct {
name string
items []models.Item
Expand All @@ -130,6 +127,7 @@ func Test_getEveryTwoItemsPoints(t *testing.T) {
}

func Test_getItemDescriptionPoints(t *testing.T) {
var utils *Utils
tests := []struct {
name string
items []models.Item
Expand All @@ -152,6 +150,7 @@ func Test_getItemDescriptionPoints(t *testing.T) {
}

func Test_getOddDayPoints(t *testing.T) {
var utils *Utils
tests := []struct {
name string
date string
Expand All @@ -176,6 +175,7 @@ func Test_getOddDayPoints(t *testing.T) {
}

func Test_getPurchaseTimePoints(t *testing.T) {
var utils *Utils
tests := []struct {
date string
expected int
Expand Down
3 changes: 3 additions & 0 deletions cmd/web/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ func (app *application) routes() http.Handler {
// Get receipt points
router.Handler(http.MethodGet, "/receipts/:id/points", http.HandlerFunc(app.handlers.GetReceiptPoints))

// Delete a receipt
router.Handler(http.MethodDelete, "/receipts/:id/delete", http.HandlerFunc(app.handlers.DeleteReceipt))

// Initialize the middleware chain using alice
// Includes:
// - recoverPanic: Middleware to recover from panics and prevent server crashes;
Expand Down
11 changes: 11 additions & 0 deletions internal/models/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,14 @@ func (s *ReceiptStore) Get(id string) (Receipt, error) {

return receipt, nil
}

func (s *ReceiptStore) Delete(id string) error {
_, exists := s.receipts[id]
if !exists {
return fmt.Errorf("no receipt found for that ID")
}

delete(s.receipts, id)

return nil
}
Loading

0 comments on commit 0dc3252

Please sign in to comment.