Skip to content

Commit

Permalink
Merge pull request #924 from gofr-dev/release/v1.17.0
Browse files Browse the repository at this point in the history
Release v1.17.0
  • Loading branch information
vipul-rawat authored Aug 12, 2024
2 parents cb21d5b + 0abac52 commit f78d8c7
Show file tree
Hide file tree
Showing 58 changed files with 4,864 additions and 316 deletions.
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ func TestFunctionName(t *testing.T) {
```go
Some services will be required to pass the entire test suite. We recommend using docker for running those services.

docker run --name mongodb -d -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=user -e MONGO_INITDB_ROOT_PASSWORD=password mongodb/mongodb-community-server:latest
docker run -d -p 21:21 -p 21000-21010:21000-21010 -e USERS='user|password' delfer/alpine-ftp-server
docker run --name gofr-mysql -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=test -p 2001:3306 -d mysql:8.0.30
docker run --name gofr-redis -p 2002:6379 -d redis:7.0.5
docker run --name gofr-zipkin -d -p 2005:9411 openzipkin/zipkin:2
Expand Down
40 changes: 34 additions & 6 deletions docs/advanced-guide/handling-file/page.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,34 @@ GoFr simplifies the complexity of working with different file stores by offering

By default, local file-store is initialised and user can access it from the context.

Gofr also supports FTP file-store. The file-store can be initialised with FTP as follows:

### FTP file-store
```go
package main

import (
"gofr.dev/pkg/gofr"

"gofr.dev/pkg/gofr/datasource/file/ftp"
)

func main() {
app := gofr.New()

app.AddFTP(ftp.New(&ftp.Config{
Host: "127.0.0.1",
User: "user",
Password: "password",
Port: 21,
RemoteDir: "/ftp/user",
}))

app.Run()
}
```


### Creating Directory

To create a single directory
Expand All @@ -15,13 +43,13 @@ err := ctx.File.Mkdir("my_dir",os.ModePerm)

To create subdirectories as well
```go
err := c.File.MkdirAll("my_dir/sub_dir", os.ModePerm)
err := ctx.File.MkdirAll("my_dir/sub_dir", os.ModePerm)
```

### Creating and Save a File with Content

```go
file, _ := c.File.Create("my_file.text")
file, _ := ctx.File.Create("my_file.text")

_, _ = file.Write([]byte("Hello World!"))

Expand Down Expand Up @@ -51,7 +79,7 @@ for reader.Next() {

To open file with default options
```go
file, _ := c.File.Open("my_file.text")
file, _ := ctx.File.Open("my_file.text")
defer file.Close()

b := make([]byte, 200)
Expand All @@ -67,18 +95,18 @@ In case of renaming a file provide current name as source, new_name in destinati
To move file from one location to another provide current location as source and new location as destination.

```go
err := c.File.Rename("old_name.text", "new_name.text")
err := ctx.File.Rename("old_name.text", "new_name.text")
```

### Deleting Files
To delete a single file
```go
err := c.File.Remove("my_dir")
err := ctx.File.Remove("my_dir")
```

To delete all sub directories as well
```go
err := c.File.RemoveAll("my_dir/my_text")
err := ctx.File.RemoveAll("my_dir/my_text")
```


Expand Down
22 changes: 19 additions & 3 deletions docs/advanced-guide/injecting-databases-drivers/page.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,27 @@ suits your project's needs.

```go
type Cassandra interface {
Query(dest interface{}, stmt string, values ...interface{}) error
Query(dest interface{}, stmt string, values ...any) error

Exec(stmt string, values ...interface{}) error
Exec(stmt string, values ...any) error

ExecCAS(dest interface{}, stmt string, values ...interface{}) (bool, error)
ExecCAS(dest any, stmt string, values ...any) (bool, error)

BatchQuery(stmt string, values ...any) error

ExecuteBatch() error

NewBatch(name string, batchType int) error

CassandraBatch
}

type CassandraBatch interface {
BatchQuery(name, stmt string, values ...any)

ExecuteBatch(name string) error

ExecuteBatchCAS(name string, dest ...any) (bool, error)
}
```

Expand Down
5 changes: 5 additions & 0 deletions docs/advanced-guide/websocket/page.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ func WSHandler(ctx *gofr.Context) (interface{}, error) {

ctx.Logger.Infof("Received message: %s", message)

err = ctx.WriteMessageToSocket("Hello! GoFr")
if err != nil {
return nil, err
}

return message, nil
}
```
Expand Down
195 changes: 195 additions & 0 deletions docs/references/testing/page.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Testing REST APIs with GoFr

Testing REST APIs ensures that your endpoints function correctly under various conditions. This guide demonstrates how to write tests for GoFr-based REST APIs.

## Mocking Databases in GoFr

Mocking databases allows for isolated testing by simulating various scenarios. GoFr's built-in mock container supports, not only SQL databases, but also extends to other data stores, including Redis, Cassandra, Key-Value stores, MongoDB, and ClickHouse.

## Example of Unit Testing a REST API Using GoFr

Below is an example of how to test, say the `Add` method of a handler that interacts with a SQL database.

Here’s an `Add` function for adding a book to the database using GoFr:

```go
// main.go
package main

import (
"gofr.dev/pkg/gofr"
"gofr.dev/pkg/gofr/http"
)

type Book struct {
Id int `json:"id"`
ISBN int `json:"isbn"`
Title string `json:"title"`
}

func Add(ctx *gofr.Context) (interface{}, error) {
var book Book

if err := ctx.Bind(&book); err != nil {
ctx.Logger.Errorf("error in binding: %v", err)
return nil, http.ErrorInvalidParam{Params: []string{"body"}}
}

// we assume the `id` column in the database is set to auto-increment.
res, err := ctx.SQL.ExecContext(ctx, `INSERT INTO books (title, isbn) VALUES (?, ?)`, book.Title, book.ISBN)
if err != nil {
return nil, err
}

id, err := res.LastInsertId()
if err != nil {
return nil, err
}

return id, nil
}

func main() {
// initialise gofr object
app := gofr.New()

app.POST("/book", Add)

// Run the application
app.Run()
}

```

Here’s how to write tests using GoFr:

```go
// main_test.go
package main

import (
"bytes"
"context"
"database/sql"
"errors"
"net/http"
"net/http/httptest"
"testing"

"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"

"gofr.dev/pkg/gofr"
"gofr.dev/pkg/gofr/container"
gofrHttp "gofr.dev/pkg/gofr/http"
)

func TestAdd(t *testing.T) {
type gofrResponse struct {
result interface{}
err error
}

// NewMockContainer provides mock implementations for various databases including:
// Redis, SQL, ClickHouse, Cassandra, MongoDB, and KVStore.
// These mock can be used to define database expectations in unit tests,
// similar to the SQL example demonstrated here.
mockContainer, mock := container.NewMockContainer(t)

ctx := &gofr.Context{
Context: context.Background(),
Request: nil,
Container: mockContainer,
}

tests := []struct {
name string
requestBody string
mockExpect func()
expectedResponse interface{}
}{
{
name: "Error while Binding",
requestBody: `title":"Book Title","isbn":12345}`,
mockExpect: func() {
},
expectedResponse: gofrResponse{
nil,
gofrHttp.ErrorInvalidParam{Params: []string{"body"}}},
},
{
name: "Successful Insertion",
requestBody: `{"title":"Book Title","isbn":12345}`,
mockExpect: func() {
mock.SQL.
EXPECT().
ExecContext(ctx, `INSERT INTO books (title, isbn) VALUES (?, ?)`, "Book Title", 12345).
Return(sqlmock.NewResult(12, 1), nil)
},
expectedResponse: gofrResponse{
int64(12),
nil,
},
},
{
name: "Error on Insertion",
requestBody: `{"title":"Book Title","isbn":12345}`,
mockExpect: func() {
mock.SQL.
EXPECT().
ExecContext(ctx, `INSERT INTO books (title, isbn) VALUES (?, ?)`, "Book Title", 12345).
Return(nil, sql.ErrConnDone)
},
expectedResponse: gofrResponse{
nil,
sql.ErrConnDone},
},
{
name: "Error while fetching LastInsertId",
requestBody: `{"title":"Book Title","isbn":12345}`,
mockExpect: func() {
mock.SQL.
EXPECT().
ExecContext(ctx, `INSERT INTO books (title, isbn) VALUES (?, ?)`, "Book Title", 12345).
Return(sqlmock.NewErrorResult(errors.New("mocked result error")), nil)
},
expectedResponse: gofrResponse{
nil,
errors.New("mocked result error")},
},
}

for i, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockExpect()

var req *http.Request

req = httptest.NewRequest(
http.MethodPost,
"/book",
bytes.NewBuffer([]byte(tt.requestBody)),
)

req.Header.Set("Content-Type", "application/json")

request := gofrHttp.NewRequest(req)

ctx.Request = request

val, err := Add(ctx)

response := gofrResponse{val, err}

assert.Equal(t, tt.expectedResponse, response, "TEST[%d], Failed.\n%s", i, tt.name)
})
}
}
```
### Summary

- **Mocking Database Interactions**: Use GoFr mock container to simulate database interactions.
- **Define Test Cases**: Create table-driven tests to handle various scenarios.
- **Run and Validate**: Ensure that your tests check for expected results, and handle errors correctly.

This approach guarantees that your database interactions are tested independently, allowing you to simulate different responses and errors hassle-free.
2 changes: 2 additions & 0 deletions examples/grpc-server/configs/.env
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
GRPC_PORT=10000

LOG_LEVEL=DEBUG
10 changes: 9 additions & 1 deletion examples/grpc-server/grpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,26 @@ package grpc
import (
"context"
"fmt"

"gofr.dev/pkg/gofr/container"
)

type Server struct {
// container can be embedded into the server struct
// to access the datasource and logger functionalities
*container.Container

UnimplementedHelloServer
}

func (Server) SayHello(ctx context.Context, req *HelloRequest) (*HelloResponse, error) {
func (s *Server) SayHello(ctx context.Context, req *HelloRequest) (*HelloResponse, error) {
name := req.Name
if name == "" {
name = "World"
}

s.Logger.Debug("container injected!")

return &HelloResponse{
Message: fmt.Sprintf("Hello %s!", name),
}, nil
Expand Down
8 changes: 7 additions & 1 deletion examples/grpc-server/grpc/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ import (

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"gofr.dev/pkg/gofr/container"
)

func TestServer_SayHello(t *testing.T) {
s := Server{}
c, _ := container.NewMockContainer(t)

s := Server{
Container: c,
}

tests := []struct {
input string
Expand Down
2 changes: 1 addition & 1 deletion examples/grpc-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
func main() {
app := gofr.New()

grpc.RegisterHelloServer(app, grpc.Server{})
grpc.RegisterHelloServer(app, &grpc.Server{})

app.Run()
}
Loading

0 comments on commit f78d8c7

Please sign in to comment.