Mocking of API handler generated by swagger Golang - mongodb

i really need some help to test the behaviour of my API, let me explain the context :
This API is written in go generated by swagger using the repository patern. In case of creating a new object i need to check if the name is not already exists in the database so i call repository's function to get Applications by name in the database (see below)
func (a *mongoApplicationRepository) GetApplicationByName(name string, ctx context.Context) (*models.Application, error) {
_, span := otel.Tracer("GetApplicationByName").Start(ctx, "GetApplicationByName")
defer span.End()
var application models.Application
err := a.mgConn.Collection("applications").
FindOne(context.TODO(), bson.M{"name": name}).Decode(&application)
if err != nil {
if err == mongo.ErrNoDocuments {
return &application, nil
}
return nil, err
}
return &application, nil
}
This function is called by the api handler that you can see bellow
type ApplicationCreateHandler struct {
API *operations.KubeesAPI
repo repo.ApplicationRepository
statsRepo repo.StatsRepository
}
func NewApplicationCreateHandler(API *operations.KubeesAPI, repo repo.ApplicationRepository, statsRepo repo.StatsRepository) ApplicationCreateHandler {
return ApplicationCreateHandler{
API: API,
repo: repo,
statsRepo: statsRepo,
}
}
// Handle is the HTTP handler for application creation
func (h *ApplicationCreateHandler) Handle(params application.AppCreateParams, principal *models.Principal) middleware.Responder {
traceName := "application-create"
ctx, span := otel.GetTracerProvider().Tracer(traceName).Start(context.TODO(), traceName)
defer span.End()
if params.Data == nil {
err := "unable to validate input"
logger().Errorln(err)
return application.NewAppCreateBadRequest().WithPayload(&models.APIResponse{
Msg: &err,
})
}
if err := params.Data.Validate(h.API.Formats()); err != nil {
err := fmt.Sprintf("unable to validate input: %v", err)
logger().Errorln(err)
return application.NewAppCreateBadRequest().WithPayload(&models.APIResponse{
Msg: &err,
})
}
app := models.Application{
ID: uuid.NewString(),
Name: params.Data.Name,
Selector: params.Data.Selector,
}
c, err := h.repo.GetApplicationByName(*params.Data.Name, ctx)
if err != nil {
err := fmt.Sprintf("Unable to get Application: %v", err)
logger().Errorln(err)
return application.NewAppCreateInternalServerError().WithPayload(&models.APIResponse{
Msg: &err,
})
}
if c.ID != "" {
r := "name already taken"
logger().Errorln(r)
return application.NewAppCreateInternalServerError().WithPayload(&models.APIResponse{
Msg: &r,
})
}
appDomain, err := app.ToDomainModel()
if err != nil {
err := fmt.Sprintf("Unable to convert application to domain model: %v", err)
logger().Errorln(err)
return application.NewAppCreateInternalServerError().WithPayload(&models.APIResponse{
Msg: &err,
})
}
err = h.repo.CreateApplication(appDomain, ctx)
if err != nil {
err := fmt.Sprintf("Unable to create Application: %v", err)
logger().Errorln(err)
return application.NewAppCreateInternalServerError().WithPayload(&models.APIResponse{
Msg: &err,
})
}
if err := h.statsRepo.UpdateApplicationsHistory(ctx); err != nil {
err := fmt.Sprintf("unable to update applications history: %v", err)
logger().Errorln(err)
return application.NewAppCreateInternalServerError().WithPayload(&models.APIResponse{
Msg: &err,
})
}
return application.NewAppCreateCreated()
}
I just want to check this behaviour and test in case we have already an application called the same as the new created one, which would raise an error and not in opposite case. But i don't know what to mock because the repository have some external dependencies

I would strongly suggest to make sure your ApplicationCreateHandler does only rely on interfaces. When that's the case you can mock the interfaces by writing a mock class, or generating on. You can test the ApplicationCreateHandlerby creating a new instance, and passing the mocked dependencies to it. Then execute theHandle()` method and ensure all dependencies are called.
I would strongly recommend generting your mock-classes. When you write your mocks yourself it's possible to introduce bugs in your test code. You wouldn't want that, right?
One great example of a mocking library is mockgen. You can easily instal it by following their github-readme.
Steps
Create interfaces for your dependencies.
It is best define the interface on the side that relies on the interface. So in you case that would be in the ApplicationCreateHandler file. The interfaces should reflect the needs of the handler.
type (
ApplicationRepo interface {
GetApplicationByName(name string, ctx context.Context)
CreateApplication(domain Domain, ctx context.Context)
}
// other dependencies here...
)
Writing a unit test
Create a new unit test
// youfile_test.go
func TestApplicationHandler(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockRepo := NewMockRepository(ctrl)
mockRepo.EXPECT().
Handle(gomock.any(), gomock.any()). // name, ctx
Return(nil, nil). // c, err
Once()
// other mocks...
// mockApi
// mockStatsRepo
h := NewApplicationHandler(mockApi, mockStats, mockRepo)
h.Handle(/*your test input*/)
// do checks
}
The test will throw an error if the mockRepo wasn't called exactly once.

Related

Go Echo: POST Method gives Error "Method not allowed"

Building an app with echo and basically created some routes.
The GET ones are working fine, but the post one is give me the error:
Do not really understand where the error lies here.
{...."method":"GET","uri":"/addPerson", message=Method Not Allowed","...."bytes_in":0,"bytes_out":33}
main.go snippet
func initEchoServer() {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// get all persons
e.GET("/persons", Info)
// get specific id
e.GET("/persons/:id", getPerson)
e.POST("/addPerson", addPerson)
e.Logger.Fatal(e.Start(viper.GetString("port")))
}
func addPerson(c echo.Context) error {
ctx := context.Background()
db, err := sql.Open("postgres", "host=postgres port=5432 user=postgres dbname=postgres password=postgres sslmode=disable")
if err != nil {
log.Fatal(err)
}
queries := postgres.New(db)
insertedPerson, err := queries.CreatePersons(ctx, postgres.CreatePersonsParams{
Firstname: "Mike",
Lastname: "Jordan",
})
if err != nil {
log.Errorf("Failed to insert a person %v", err)
return err
}
fmt.Println(insertedPerson)
return c.JSONPretty(http.StatusOK, insertedPerson, " ")
}
queries.sql.go snippet
type CreatePersonsParams struct {
Firstname string
Lastname string
}
func (q *Queries) CreatePersons(ctx context.Context, arg CreatePersonsParams) (Person, error) {
row := q.db.QueryRowContext(ctx, createPersons, arg.Firstname, arg.Lastname)
var i Person
err := row.Scan(&i.ID, &i.Firstname, &i.Lastname)
return i, err
}
you're use post method in routers
e.POST("/addPerson", addPerson)
You can use postman to hit API using POST method, don't use browser
If you register routes with POST in echo, it will only register POST method on that path. But it seems that you GET that path.
You can use e.GET().

ObjectIdFromHex invalid byte error on identical strings

I'm trying to implement a FindOne method in my Golang REST API. The trouble comes where i have to search by ID. I have to convert the ID into something readable by the database, so i use primitive.ObjectIDFromHex(id)
The problem is that this method throws an error :
2021/06/19 06:56:15 encoding/hex: invalid byte: U+000A
ONLY when i call it with the id that comes from my URL GET params.
I did two versions : one with hard-coded ID, and one with GET ID. See code below.
func Admin(id string) (bson.M, error) {
coll, err := db.ConnectToCollection("admin")
if err != nil {
log.Fatal(err)
}
var admin bson.M
HardCoded := "60cb275c074ab46a1aeda45e"
fmt.Println(HardCoded) // Just to be sure : the two strings seem identical
fmt.Println(id)
objetId, err := primitive.ObjectIDFromHex(id) // throws encoding error
// objetId, err := primitive.ObjectIDFromHex(HardCoded) // Doesnt throw encoding err
if err != nil {
log.Fatal(err)
}
var ctx = context.TODO()
if err := coll.FindOne(ctx, bson.M{"_id": objetId}).Decode(&admin); err != nil {
log.Fatal(err)
}
return admin, nil
}
Of course, you'll want to know where the param id comes from.
Here you go :
func GetAdmin(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
admin, err := Admin(params["id"]) // Calling the Admin function above
if err != nil {
fmt.Println(err)
http.Error(w, err.Error(), http.StatusUnauthorized)
} else {
JSON, err := json.Marshal(admin)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
w.Write(JSON)
}
}
Trim the line feed from the end of id:
id = strings.TrimSpace(id)
Use the %q format verb when debugging issues like this. The line feed is clearly visible in this output:
fmt.Printf("%q\n", HardCoded) // prints "60cb275c074ab46a1aeda45e"
fmt.Printf("%q\n", id) // prints "60cb275c074ab46a1aeda45e\n"

Unit Testing Postgres db connection golang

I am expected to have 80% test coverage even for pushing the basic project structure. I am a bit confused how do I write unit tests for the following code to Connect to postgres db and ping postgres for health check. Can someone help me please.
var postgres *sql.DB
// ConnectToPostgres func to connect to postgres
func ConnectToPostgres(connStr string) (*sql.DB, error) {
db, err := sql.Open("postgres", connStr)
if err != nil {
log.Println("postgres-client ", err)
return nil, err
}
postgres = db
return db, nil
}
// PostgresHealthCheck to ping database and check for errors
func PostgresHealthCheck() error {
if err := postgres.Ping(); err != nil {
return err
}
return nil
}
type PostgresRepo struct {
db *sql.DB
}
// NewPostgresRepo constructor
func NewPostgresRepo(database *sql.DB) *PostgresRepo {
return &PostgresRepo{
db: database,
}
}
You need to use this : https://github.com/DATA-DOG/go-sqlmock
Its very easy to use. Here is an example where a controller is getting tested using a mocked SQL :
Implementation
func (up UserProvider) GetUsers() ([]models.User, error) {
var users = make([]models.User, 0, 10)
rows, err := up.DatabaseProvider.Query("SELECT firstname, lastname, email, age FROM Users;")
if err != nil {
return nil, err
}
for rows.Next() {
var u models.User = models.User{}
err := rows.Scan(&u.Name, &u.Lastname, &u.Email, &u.Age)
if err != nil {
return nil, err
}
users = append(users, u)
}
if err := rows.Err(); err != nil {
return nil, err
}
return users, nil
}
Test
func TestGetUsersOk(t *testing.T) {
db, mock := NewMock()
mock.ExpectQuery("SELECT firstname, lastname, email, age FROM Users;").
WillReturnRows(sqlmock.NewRows([]string{"firstname", "lastname", "email", "age"}).
AddRow("pepe", "guerra", "pepe#gmail.com", 34))
subject := UserProvider{
DatabaseProvider: repositories.NewMockDBProvider(db, nil),
}
resp, err := subject.GetUsers()
assert.Nil(t, err)
assert.NotNil(t, resp)
assert.Equal(t, 1, len(resp))
}
func NewMock() (*sql.DB, sqlmock.Sqlmock) {
db, mock, err := sqlmock.New()
if err != nil {
log.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
return db, mock
}
I find that writing tests against a live database makes for more high quality tests. The challenge with Postgres is that there's no good in-memory fake that you can substitute in.
What I came up with is standing up the postgres Docker container and creating temporary databases in there. The PostgresContainer type in the github.com/bitcomplete/sqltestutil package does exactly this:
# Postgres version is "12"
pg, _ := sqltestutil.StartPostgresContainer(context.Background(), "12")
defer pg.Shutdown(ctx)
db, err := sql.Open("postgres", pg.ConnectionString())
// ... execute SQL
Per the docs, it's a good idea to set up your tests so that the container is only started once, as it can take a few seconds to start up (more if the image needs to be downloaded). It suggests some approaches for mitigating that problem.

How to serve a file from mongodb with golang

I am working on a go project where I need to serve files stored in mongodb. The files are stored in a GridFs. I use gopkg.in/mgo.v2 as package to connect and query the db.
I can retrieve the file from the db, that is not hard.
f, err := s.files.OpenId(id)
But how can I serve that file with http?
I work with the JulienSchmidt router to handle all the other restfull requests.
The solutions I find always use static files, not files from a db.
Thanks in advance
Tip: Recommended to use github.com/globalsign/mgo instead of gopkg.in/mgo.v2 (the latter is not maintained anymore).
The mgo.GridFile type implements io.Reader, so you could use io.Copy() to copy its content into the http.ResponseWriter.
But since mgo.GridFile also implements io.Seeker, you may take advantage of http.ServeContent(). Quoting its doc:
The main benefit of ServeContent over io.Copy is that it handles Range requests properly, sets the MIME type, and handles If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since, and If-Range requests.
Example handler serving a file:
func serveFromDB(w http.ResponseWriter, r *http.Request) {
var gridfs *mgo.GridFS // Obtain GridFS via Database.GridFS(prefix)
name := "somefile.pdf"
f, err := gridfs.Open(name)
if err != nil {
log.Printf("Failed to open %s: %v", name, err)
http.Error(w, "something went wrong", http.StatusInternalServerError)
return
}
defer f.Close()
http.ServeContent(w, r, name, time.Now(), f) // Use proper last mod time
}
its old but i got another solution with goMongo driver by importing
"go.mongodb.org/mongo-driver/mongo/gridfs"
var bucket *gridfs.Bucket //creates a bucket
dbConnection, err := db.GetDBCollection() //connect db with your your
if err != nil {
log.Fatal(err)
}
bucket, err = gridfs.NewBucket(dbConnection)
if err != nil {
log.Fatal(err)
}
name := "br100_update.txt"
downloadStream, err := bucket.OpenDownloadStreamByName(name)
if err != nil {
log.Printf("Failed to open %s: %v", name, err)
http.Error(w, "something went wrong", http.StatusInternalServerError)
return
}
defer func() {
if err := downloadStream.Close(); err != nil {
log.Fatal(err)
}
}()
// Use SetReadDeadline to force a timeout if the download does not succeed in
// 2 seconds.
if err = downloadStream.SetReadDeadline(time.Now().Add(2 * time.Second)); err
!= nil {
log.Fatal(err)
}
// this code below use to read the file
fileBuffer := bytes.NewBuffer(nil)
if _, err := io.Copy(fileBuffer, downloadStream); err != nil {
log.Fatal(err)
}

How to read data (xml) sent by server if it doesn't send new line

Let's say we try to communicate with a server (XMPP) which sends back XML data. We can use
conn, err := net.Dial("tcp", s.Addr+":5222")
//...
r := bufio.NewReader(conn)
//...
s, err := s.R.ReadString(10) // to read a string
But there is one problem that the server doesn't send the \10 (newline) symbol. I also tried 12 but without any luck. Same goes for readLine function as it also relies on \10. So how do I read the data sent by server?
I tried using '>' as a delimiter and succeeded to receive only parts of the messages (predictable). I had an idea to loop while error is nil and use delimiter of '>' but it also didn't work.
My research shown that the last symbol of the message is really '>' (62) and there is not any anything else at the end.
Use an xml.Decoder to read stanzas from an XMPP stream.
conn, err := net.Dial("tcp", s.Addr+":5222")
if err != nil {
// handle error
}
dec := xml.NewDecoder(conn)
Use the decoder Token method to read the root document element and to skip over character data between stanzas:
func readStartElement(dec *xml.Decoder) (xml.StartElement, error) {
for {
t, err := dec.Token()
if err != nil {
return xml.StartElement{}, err
}
switch t := t.(type) {
case xml.StartElement:
return t, nil
}
}
}
Use the decoder DecodeElement method to read a stanza:
func readStanza(dec *xml.Decoder) (interface{}, error) {
se, err := readStartElement(dec)
if err != nil {
return nil, err
}
var v interface{}
switch se.Name.Space + " " + se.Name.Local {
case "jabber:client message":
v = &jabberMessage{} // jabberMessage is struct type defined by app for messages
// Add other stanza types here.
default:
v = &struct{}{}
}
if err := dec.DecodeElement(v, &se); err != nil {
return nil, err
}
return v, nil
}
Type switch on the return value from readStanza to handle the different types of received stanzas.
A client reads stanzas synchronously. Here's rough outline (ignoring authentication, etc).
conn, err := net.Dial("tcp", s.Addr+":5222")
if err != nil {
// handle error
}
dec := xml.NewDecoder(conn)
// read and discard root element
_, err := readStartElement(dec)
if err != nil {
// handle error
}
// read stanzas
for {
v, err := readStanza(dec)
if err != nil {
// handle error
// must break out of loop on error
}
switch v := v.(type) {
case *jabberMessage:
// handle message
case *someOtherStanzaType:
// handle other stanza types
// ... and so on
}
}