Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Jyothis-P committed May 30, 2024
1 parent 6248923 commit c751d62
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 3 deletions.
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,80 @@ An opinionated Go web framework with MongoDB support for rapid REST API developm
os.Exit(0)
```
## Writing your custom handle functions with App Context
Create the handler as usual with the addition of *grf.Ctx in the parameters.
```go
// Customer Handler function using the generic Delete service.
func customDeleteHandler(appCtx *grf.Ctx, w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
// You can use any of the generic service functions or your own custom service.
err := grf.Delete[Todo](appCtx.DB, vars["id"])
if err != nil {
http.Error(w, "Error deleting TODO.", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "Group deleted.")
}
```
Make sure you are wrapping this in the adapter when registering the route.
```go
// Earlier code to generate the crud routes that returns a subrouted.
todoRouter := grf.RegisterCRUDRoutes[Todo]("/todo", r, &appContext)
// Register the new customer handler as a new route in the subrouter.
todoRouter.Handle("/{id}/customDeleteTodo", grf.H{Ctx: &appContext, Fn: customDeleteHandler}).Methods("DELETE")
```
### Custom services
Even when writing your own service, you can pass the db context as a parameter instead of using global variables.
```go
// Custom Handler with a custom service making use of the db connection passed from context.
func markComplete(appCtx *grf.Ctx, w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
err := finisherService(appCtx.DB, vars["id"])
if err != nil {
http.Error(w, "Error completing TODO.", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "Marked complete!")
}
func finisherService(db *mongo.Database, id string) error {
collection := db.Collection("todos")
// Converting the group id from the hex string to the ObjectID format that mongo use
objectID, err := primitive.ObjectIDFromHex(id)
if err != nil {
log.Println("Error converting id to ObjectId:", err)
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
filter := bson.D{{Key: "_id", Value: objectID}}
update := bson.M{"$set": bson.M{"completed": true}}
res, err := collection.UpdateOne(ctx, filter, update)
if err != nil || res.ModifiedCount < 1 {
log.Println("Error Marking complete:", err)
log.Println("Matched Count: ", res.MatchedCount)
log.Println("Modified Count: ", res.ModifiedCount)
return err
}
return nil
}
```
## TODO
Expand Down
70 changes: 67 additions & 3 deletions example/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"fmt"
"log"
"net/http"
"os"
Expand All @@ -10,13 +11,21 @@ import (

grf "github.com/Jyothis-P/go-rest-framework"
"github.com/gorilla/mux"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)

// Create a model
// Make sure to give json and bson structs as necessary.
type Todo struct {
Title string `json:"title" bson:"title"`
Completed bool `json:"completed" bson:"completed"`
Id primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`
Title string `json:"title" bson:"title"`
Completed bool `json:"completed" bson:"completed"`
}

func (todo *Todo) markCompleted(completed bool) {
todo.Completed = completed
}

func main() {
Expand All @@ -38,7 +47,10 @@ func main() {
r := mux.NewRouter().StrictSlash(true)

// Register routes for the model.
grf.RegisterCRUDRoutes[Todo]("/todo", r, &appContext)
todoRouter := grf.RegisterCRUDRoutes[Todo]("/todo", r, &appContext)

todoRouter.Handle("/{id}/customDeleteTodo", grf.H{Ctx: &appContext, Fn: customDeleteHandler}).Methods("DELETE")
todoRouter.Handle("/{id}/markComplete", grf.H{Ctx: &appContext, Fn: markComplete}).Methods("PUT")

// Set up server.
const PORT string = "8001"
Expand Down Expand Up @@ -72,3 +84,55 @@ func main() {
log.Println("Shutting down.")
os.Exit(0)
}

// Customer Handler function using the generic Delete service.
func customDeleteHandler(appCtx *grf.Ctx, w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)

// You can use any of the generic service functions or your own custom service.
err := grf.Delete[Todo](appCtx.DB, vars["id"])
if err != nil {
http.Error(w, "Error deleting TODO.", http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "Group deleted.")
}

// Custom Handler with a custom service making use of the db connection passed from context.
func markComplete(appCtx *grf.Ctx, w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
err := finisherService(appCtx.DB, vars["id"])
if err != nil {
http.Error(w, "Error completing TODO.", http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "Marked complete!")
}

func finisherService(db *mongo.Database, id string) error {
collection := db.Collection("todos")

// Converting the group id from the hex string to the ObjectID format that mongo use
objectID, err := primitive.ObjectIDFromHex(id)
if err != nil {
log.Println("Error converting id to ObjectId:", err)
return err
}

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
filter := bson.D{{Key: "_id", Value: objectID}}
update := bson.M{"$set": bson.M{"completed": true}}
res, err := collection.UpdateOne(ctx, filter, update)
if err != nil || res.ModifiedCount < 1 {
log.Println("Error Marking complete:", err)
log.Println("Matched Count: ", res.MatchedCount)
log.Println("Modified Count: ", res.ModifiedCount)
return err
}
return nil
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
Expand Down
71 changes: 71 additions & 0 deletions handlers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package grf_test

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"regexp"
"testing"

grf "github.com/Jyothis-P/go-rest-framework"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/integration/mtest"
)

// Create a model
// Make sure to give json and bson structs as necessary.
type Todo struct {
Id primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`
Title string `json:"title" bson:"title"`
Completed bool `json:"completed" bson:"completed"`
}

var testTodo Todo = Todo{
Title: "Finish testing this.",
Completed: false,
}

func TestCreateHandler(t *testing.T) {

mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))

mt.Run("Create todo test", func(mt *mtest.T) {

jsonTodo, err := json.Marshal(testTodo)
if err != nil {
t.Fatal(err)
}
req, err := http.NewRequest("POST", "http://localhost:8001/todo/", bytes.NewReader(jsonTodo))
if err != nil {
t.Fatal(err)
}
res := httptest.NewRecorder()

mt.AddMockResponses(mtest.CreateSuccessResponse())
appContext := grf.Ctx{
DB: mt.DB,
}
grf.CreateHandler[Todo](&appContext, res, req)

// Regex pattern to match the string with a changeable ObjectID
pattern := `Object created!, id: \{ObjectID\("[0-9a-fA-F]{24}"\)}`

// Compile the regex pattern
r, err := regexp.Compile(pattern)
if err != nil {
fmt.Println("There was an error compiling the regex pattern:", err)
return
}

act := res.Body.String()
exp := `Object created!, id: \{ObjectID\("[0-9a-fA-F]{24}"\)}`
fmt.Println("Res:", act)
// Check if the pattern matches the string
match := r.MatchString(res.Body.String())
if !match {
t.Fatalf("Response is %s. Expected: %s", act, exp)
}
})
}

0 comments on commit c751d62

Please sign in to comment.