From dc7167711e229a29db586e3413f765060bde0675 Mon Sep 17 00:00:00 2001 From: Ritik Jain Date: Sun, 20 Feb 2022 15:04:05 +0530 Subject: [PATCH 01/10] feat(mongo): Adds Distinct, CountDocuments and Aggregate methods to mock their outputs. --- integrations/kmongo/mdb.go | 275 +++++++++++++++++++++++++++++++++---- 1 file changed, 248 insertions(+), 27 deletions(-) diff --git a/integrations/kmongo/mdb.go b/integrations/kmongo/mdb.go index 64bd609..6b23a56 100644 --- a/integrations/kmongo/mdb.go +++ b/integrations/kmongo/mdb.go @@ -270,7 +270,9 @@ func (c *Collection) InsertOne(ctx context.Context, document interface{}, type Cursor struct { mongo.Cursor filter interface{} - opts []options.FindOptions + pipeline interface{} + findOpts []options.FindOptions + aggregateOpts []options.AggregateOptions ctx context.Context log *zap.Logger } @@ -303,9 +305,16 @@ func (cr *Cursor) Err() error { meta := map[string]string{ "name": "mongodb", "type": string(models.NoSqlDB), - "operation": "Find.Err", - "filter": fmt.Sprint(cr.filter), - "FindOptions": fmt.Sprint(cr.opts), + } + if cr.filter!=nil{ + meta["filter"] = fmt.Sprint(cr.filter) + meta["FindOptions"] = fmt.Sprint(cr.findOpts) + meta["operation"] = "Find.Err" + } else { + meta["pipeline"] = fmt.Sprint(cr.pipeline) + meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) + meta["operation"] = "Aggregate.Err" + } if err != nil { @@ -352,9 +361,16 @@ func (cr *Cursor) Close(ctx context.Context) error { meta := map[string]string{ "name": "mongodb", "type": string(models.NoSqlDB), - "operation": "Find.Close", - "filter": fmt.Sprint(cr.filter), - "FindOptions": fmt.Sprint(cr.opts), + } + if cr.filter!=nil{ + meta["filter"] = fmt.Sprint(cr.filter) + meta["FindOptions"] = fmt.Sprint(cr.findOpts) + meta["operation"] = "Find.Err" + } else { + meta["pipeline"] = fmt.Sprint(cr.pipeline) + meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) + meta["operation"] = "Aggregate.Err" + } if err != nil { @@ -401,9 +417,16 @@ func (cr *Cursor) TryNext(ctx context.Context) bool { meta := map[string]string{ "name": "mongodb", "type": string(models.NoSqlDB), - "operation": "Find.TryNext", - "filter": fmt.Sprint(cr.filter), - "FindOptions": fmt.Sprint(cr.opts), + } + if cr.filter!=nil{ + meta["filter"] = fmt.Sprint(cr.filter) + meta["FindOptions"] = fmt.Sprint(cr.findOpts) + meta["operation"] = "Find.Err" + } else { + meta["pipeline"] = fmt.Sprint(cr.pipeline) + meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) + meta["operation"] = "Aggregate.Err" + } mock, res := keploy.ProcessDep(cr.ctx, cr.log, meta, output) @@ -444,9 +467,16 @@ func (cr *Cursor) All(ctx context.Context, results interface{}) error { meta := map[string]string{ "name": "mongodb", "type": string(models.NoSqlDB), - "operation": "Find.All", - "filter": fmt.Sprint(cr.filter), - "FindOptions": fmt.Sprint(cr.opts), + } + if cr.filter!=nil{ + meta["filter"] = fmt.Sprint(cr.filter) + meta["FindOptions"] = fmt.Sprint(cr.findOpts) + meta["operation"] = "Find.Err" + } else { + meta["pipeline"] = fmt.Sprint(cr.pipeline) + meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) + meta["operation"] = "Aggregate.Err" + } if err != nil { @@ -493,9 +523,16 @@ func (cr *Cursor) Next(ctx context.Context) bool { meta := map[string]string{ "name": "mongodb", "type": string(models.NoSqlDB), - "operation": "Find.Next", - "filter": fmt.Sprint(cr.filter), - "FindOptions": fmt.Sprint(cr.opts), + } + if cr.filter!=nil{ + meta["filter"] = fmt.Sprint(cr.filter) + meta["FindOptions"] = fmt.Sprint(cr.findOpts) + meta["operation"] = "Find.Err" + } else { + meta["pipeline"] = fmt.Sprint(cr.pipeline) + meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) + meta["operation"] = "Aggregate.Err" + } mock, res := keploy.ProcessDep(cr.ctx, cr.log, meta, output) @@ -536,9 +573,16 @@ func (cr *Cursor) Decode(v interface{}) error { meta := map[string]string{ "name": "mongodb", "type": string(models.NoSqlDB), - "operation": "Find.Decode", - "filter": fmt.Sprint(cr.filter), - "FindOptions": fmt.Sprint(cr.opts), + } + if cr.filter!=nil{ + meta["filter"] = fmt.Sprint(cr.filter) + meta["FindOptions"] = fmt.Sprint(cr.findOpts) + meta["operation"] = "Find.Err" + } else { + meta["pipeline"] = fmt.Sprint(cr.pipeline) + meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) + meta["operation"] = "Aggregate.Err" + } if err != nil { @@ -581,7 +625,7 @@ func (c *Collection) Find(ctx context.Context, filter interface{}, if er != nil { return &Cursor{ filter: filter, - opts: derivedOpts, + findOpts: derivedOpts, log: c.log, ctx: ctx, }, er @@ -596,7 +640,7 @@ func (c *Collection) Find(ctx context.Context, filter interface{}, //don't call method in test mode return &Cursor{ filter: filter, - opts: derivedOpts, + findOpts: derivedOpts, log: c.log, ctx: ctx, }, err @@ -605,7 +649,7 @@ func (c *Collection) Find(ctx context.Context, filter interface{}, return &Cursor{ Cursor: *cursor, filter: filter, - opts: derivedOpts, + findOpts: derivedOpts, log: c.log, ctx: ctx, }, err @@ -613,7 +657,7 @@ func (c *Collection) Find(ctx context.Context, filter interface{}, c.log.Error("integrations: Not in a valid sdk mode") return &Cursor{ filter: filter, - opts: derivedOpts, + findOpts: derivedOpts, log: c.log, ctx: ctx, }, errors.New("integrations: Not in a valid sdk mode") @@ -716,7 +760,6 @@ func (c *Collection) UpdateOne(ctx context.Context, filter interface{}, update i if err != nil { kerr = &keploy.KError{Err: err} - c.log.Error(err.Error()) output = &mongo.UpdateResult{} } mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) @@ -775,7 +818,6 @@ func (c *Collection) UpdateMany(ctx context.Context, filter interface{}, update if err != nil { kerr = &keploy.KError{Err: err} - c.log.Error(err.Error()) output = &mongo.UpdateResult{} } mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) @@ -833,7 +875,6 @@ func (c *Collection) DeleteOne(ctx context.Context, filter interface{}, if err != nil { kerr = &keploy.KError{Err: err} - c.log.Error(err.Error()) output = &mongo.DeleteResult{} } mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) @@ -890,7 +931,6 @@ func (c *Collection) DeleteMany(ctx context.Context, filter interface{}, if err != nil { kerr = &keploy.KError{Err: err} - c.log.Error(err.Error()) output = &mongo.DeleteResult{} } mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) @@ -910,6 +950,170 @@ func (c *Collection) DeleteMany(ctx context.Context, filter interface{}, return output, err } +// Distinct method mocks Collection.Distinct of mongo inorder to call it only in "capture" or "off" mode. +// +// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Collection.Distinct for more info about Distinct. +func (c *Collection) Distinct(ctx context.Context, fieldName string, filter interface{}, opts ...*options.DistinctOptions) ([]interface{}, error){ + if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { + output, err := c.Collection.Distinct(ctx, fieldName, filter, opts...) + return output, err + } + var ( + output *[]interface{} = &[]interface{}{} + err error + kerr = &keploy.KError{} + data []interface{} + ) + data = append(data, fieldName) + data = append(data, filter) + for _, j := range opts { + data = append(data, j) + } + o, e := c.getOutput(ctx, "Distinct", data) + if o != nil { + dis := o.([]interface{}) + output = &dis + } + err = e + derivedOpts := []options.DistinctOptions{} + for _, j := range opts { + derivedOpts = append(derivedOpts, *j) + } + meta := map[string]string{ + "name": "mongodb", + "type": string(models.NoSqlDB), + "operation": "Distinct", + "fieldName": fieldName, + "filter": fmt.Sprint(filter), + "DistinctOptions": fmt.Sprint(derivedOpts), + } + kerr.Err = err + mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) + if mock { + var mockErr error + x := res[1].(*keploy.KError) + if x.Err != nil { + mockErr = x.Err + } + return *output, mockErr + } + return *output, err +} + +// CountDocuments method mocks Collection.CountDocuments of mongo inorder to call it only in "capture" or "off" mode. +// +// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Collection.CountDocuments for more info about CountDocuments. +func (c *Collection) CountDocuments(ctx context.Context, filter interface{}, opts ...*options.CountOptions) (int64, error){ + if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { + output, err := c.Collection.CountDocuments(ctx, filter, opts...) + return output, err + } + var ( + output *int64 + err error + kerr *keploy.KError = &keploy.KError{} + data []interface{} + ) + data = append(data, filter) + for _, j := range opts { + data = append(data, j) + } + o, e := c.getOutput(ctx, "CountDocuments", data) + if o != nil { + count := o.(int64) + output = &count + } + err = e + derivedOpts := []options.CountOptions{} + for _, j := range opts { + derivedOpts = append(derivedOpts, *j) + } + meta := map[string]string{ + "name": "mongodb", + "type": string(models.NoSqlDB), + "operation": "CountDocuments", + "filter": fmt.Sprint(filter), + "CountOptions": fmt.Sprint(derivedOpts), + } + kerr.Err = err + if output==nil{ + var count int64 = 0 + output = &count + } + mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) + if mock { + var mockErr error + x := res[1].(*keploy.KError) + if x.Err != nil { + mockErr = x.Err + } + return *output, mockErr + } + return *output, err +} + +// Aggregate method mocks Collection.Aggregate of mongo inorder to call it only in "capture" or "off" mode. +// +// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Collection.Aggregate for more info about Aggregate. +func (c *Collection) Aggregate(ctx context.Context, pipeline interface{}, opts ...*options.AggregateOptions) (*Cursor, error) { + if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { + cursor, err := c.Collection.Aggregate(ctx, pipeline, opts...) + return &Cursor{ + Cursor: *cursor, + pipeline: pipeline, + ctx: ctx, + log: c.log, + }, err + } + + derivedOpts := []options.AggregateOptions{} + for _, j := range opts { + derivedOpts = append(derivedOpts, *j) + } + kctx, er := keploy.GetState(ctx) + if er != nil { + return &Cursor{ + pipeline: pipeline, + aggregateOpts: derivedOpts, + log: c.log, + ctx: ctx, + }, er + } + mode := kctx.Mode + var ( + cursor *mongo.Cursor + err error + ) + switch mode { + case "test": + //don't call method in test mode + return &Cursor{ + pipeline: pipeline, + aggregateOpts: derivedOpts, + log: c.log, + ctx: ctx, + }, err + case "capture": + cursor, err = c.Collection.Aggregate(ctx, pipeline, opts...) + return &Cursor{ + Cursor: *cursor, + pipeline: pipeline, + aggregateOpts: derivedOpts, + log: c.log, + ctx: ctx, + }, err + default: + c.log.Error("integrations: Not in a valid sdk mode") + return &Cursor{ + pipeline: pipeline, + aggregateOpts: derivedOpts, + log: c.log, + ctx: ctx, + }, errors.New("integrations: Not in a valid sdk mode") + } + +} + func (c *Collection) getOutput(ctx context.Context, str string, data []interface{}) (interface{}, error) { var ( output interface{} @@ -989,6 +1193,23 @@ func (c *Collection) callMethod(ctx context.Context, str string, data []interfac opts = append(opts, d.(*options.DeleteOptions)) } output, err = c.Collection.DeleteMany(ctx, filter, opts...) + case "Distinct": + fieldName := data[0] + filter := data[1] + data = data[2:] + var opts []*options.DistinctOptions + for _, d := range data { + opts = append(opts, d.(*options.DistinctOptions)) + } + output, err = c.Collection.Distinct(ctx, fieldName.(string), filter, opts...) + case "CountDocuments": + filter := data[0] + data = data[1:] + var opts []*options.CountOptions + for _, d := range data { + opts = append(opts, d.(*options.CountOptions)) + } + output, err = c.Collection.CountDocuments(ctx, filter, opts...) default: return nil, errors.New("integerations: SDK Not supported for this method") } From e51732ac445dd82cbb019cf0c6d1fb0efafd1a10 Mon Sep 17 00:00:00 2001 From: re-Tick Date: Sun, 27 Feb 2022 20:05:46 +0530 Subject: [PATCH 02/10] fix(keploy): adds a delay in between capture and denoise calls to keploy server delay is necessary to capture the noise fields of response accurately --- go.mod | 4 ---- go.sum | 6 ------ keploy/keploy.go | 2 +- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 06ef6c3..bd82b0d 100644 --- a/go.mod +++ b/go.mod @@ -49,17 +49,13 @@ require ( github.com/xdg-go/scram v1.0.2 // indirect github.com/xdg-go/stringprep v1.0.2 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect - github.com/yuin/goldmark v1.4.1 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect - golang.org/x/mod v0.5.1 // indirect golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect golang.org/x/text v0.3.7 // indirect - golang.org/x/tools v0.1.9 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect google.golang.org/protobuf v1.25.0 // indirect gopkg.in/yaml.v2 v2.2.8 // indirect diff --git a/go.sum b/go.sum index 2caf474..66d7263 100644 --- a/go.sum +++ b/go.sum @@ -177,8 +177,6 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1 h1:/vn0k+RBvwlxEmP5E7SZMqNxPhfMVFEJiykr15/0XKM= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.keploy.io/server v0.1.8 h1:b50vAt1+WKMscYVP5Bm8gx/iSaR7mpHox8VpaxjrQ88= go.keploy.io/server v0.1.8/go.mod h1:ZqhwTZOBb+dzx5t30Wt6eUGI6kO5QizvPg6coNPtbow= go.mongodb.org/mongo-driver v1.8.0/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= @@ -207,8 +205,6 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -271,8 +267,6 @@ golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/keploy/keploy.go b/keploy/keploy.go index 6b50959..1d627f1 100644 --- a/keploy/keploy.go +++ b/keploy/keploy.go @@ -356,7 +356,7 @@ func (k *Keploy) put(tcs regression.TestCaseReq) { func (k *Keploy) denoise (id string, tcs regression.TestCaseReq){ // run the request again to find noisy fields - + time.Sleep(2*time.Second) resp2, err := k.simulate(models.TestCase{ ID: id, Captured: tcs.Captured, From f35ef804a9b4f6b1ec44d95a553d7650e45f3024 Mon Sep 17 00:00:00 2001 From: re-Tick Date: Thu, 3 Mar 2022 21:57:52 +0530 Subject: [PATCH 03/10] fix(kmongo): adds a check for nil pointer to prevent crash in find, findone mocked method refactors kmongo code to improve maintainence #63 --- integrations/kecho/v4/echo-v4.go | 2 - integrations/kmongo/cursor.go | 412 +++++++++++++ integrations/kmongo/delete.go | 129 ++++ integrations/kmongo/find.go | 110 ++++ integrations/kmongo/insert.go | 127 ++++ integrations/kmongo/mdb.go | 908 ---------------------------- integrations/kmongo/singleResult.go | 124 ++++ integrations/kmongo/update.go | 135 +++++ keploy/keploy.go | 1 - 9 files changed, 1037 insertions(+), 911 deletions(-) create mode 100644 integrations/kmongo/cursor.go create mode 100644 integrations/kmongo/delete.go create mode 100644 integrations/kmongo/find.go create mode 100644 integrations/kmongo/insert.go create mode 100644 integrations/kmongo/singleResult.go create mode 100644 integrations/kmongo/update.go diff --git a/integrations/kecho/v4/echo-v4.go b/integrations/kecho/v4/echo-v4.go index 15458cc..857f0ab 100644 --- a/integrations/kecho/v4/echo-v4.go +++ b/integrations/kecho/v4/echo-v4.go @@ -3,7 +3,6 @@ package kecho import ( "bytes" "context" - "fmt" "go.keploy.io/server/pkg/models" "io" "io/ioutil" @@ -114,7 +113,6 @@ func pathParamsEcho(c echo.Context) map[string]string { paramNames := c.ParamNames() paramValues := c.ParamValues() for i := 0; i < len(paramNames); i++ { - fmt.Printf("paramName : %v, paramValue : %v\n", paramNames[i], paramValues[i]) result[paramNames[i]] = paramValues[i] } return result diff --git a/integrations/kmongo/cursor.go b/integrations/kmongo/cursor.go new file mode 100644 index 0000000..3d40401 --- /dev/null +++ b/integrations/kmongo/cursor.go @@ -0,0 +1,412 @@ +package kmongo + +import ( + "context" + "errors" + "fmt" + + "github.com/keploy/go-sdk/keploy" + "go.keploy.io/server/pkg/models" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "go.uber.org/zap" +) + +// Cursor contains emedded mongo.Cursor in order to override its methods. +type Cursor struct { + mongo.Cursor + filter interface{} + pipeline interface{} + findOpts []options.FindOptions + aggregateOpts []options.AggregateOptions + ctx context.Context + log *zap.Logger +} + +// Err mocks mongo's Cursor.Err in order to store and replay its output according SDK mode. +// +// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Cursor.Err for information about Cursor.Err. +func (cr *Cursor) Err() error { + if keploy.GetModeFromContext(cr.ctx) == keploy.MODE_OFF { + err := cr.Cursor.Err() + return err + } + var err error + var kerr = &keploy.KError{} + kctx, er := keploy.GetState(cr.ctx) + if er != nil { + return er + } + mode := kctx.Mode + switch mode { + case "test": + //dont run mongo query as it is stored in context + err = nil + case "capture": + err = cr.Cursor.Err() + default: + return errors.New("integrations: Not in a valid sdk mode") + } + + meta := map[string]string{ + "name": "mongodb", + "type": string(models.NoSqlDB), + } + if cr.filter!=nil{ + meta["filter"] = fmt.Sprint(cr.filter) + meta["FindOptions"] = fmt.Sprint(cr.findOpts) + meta["operation"] = "Find.Err" + } else { + meta["pipeline"] = fmt.Sprint(cr.pipeline) + meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) + meta["operation"] = "Aggregate.Err" + + } + + if err != nil { + kerr = &keploy.KError{Err: err} + } + mock, res := keploy.ProcessDep(cr.ctx, cr.log, meta, kerr) + + if mock { + var mockErr error + x := res[0].(*keploy.KError) + if x.Err != nil { + mockErr = x.Err + } + return mockErr + } + return err +} + +// Close mocks mongo's Cursor.Close in order to store and replay its output according SDK mode. +// +// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Cursor.Close for information about Cursor.Close. +func (cr *Cursor) Close(ctx context.Context) error { + if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { + err := cr.Cursor.Close(ctx) + return err + } + var err error + var kerr = &keploy.KError{} + kctx, er := keploy.GetState(cr.ctx) + if er != nil { + return er + } + mode := kctx.Mode + switch mode { + case "test": + //dont run mongo query as it is stored in context + err = nil + case "capture": + err = cr.Cursor.Close(ctx) + default: + return errors.New("integrations: Not in a valid sdk mode") + } + + meta := map[string]string{ + "name": "mongodb", + "type": string(models.NoSqlDB), + } + if cr.filter!=nil{ + meta["filter"] = fmt.Sprint(cr.filter) + meta["FindOptions"] = fmt.Sprint(cr.findOpts) + meta["operation"] = "Find.Close" + } else { + meta["pipeline"] = fmt.Sprint(cr.pipeline) + meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) + meta["operation"] = "Aggregate.Close" + + } + + if err != nil { + kerr = &keploy.KError{Err: err} + } + mock, res := keploy.ProcessDep(cr.ctx, cr.log, meta, kerr) + + if mock { + var mockErr error + x := res[0].(*keploy.KError) + if x.Err != nil { + mockErr = x.Err + } + return mockErr + } + return err +} + +// TryNext mocks mongo's Cursor.TryNext in order to store and replay its output according SDK mode. +// +// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Cursor.TryNext for information about Cursor.TryNext. +func (cr *Cursor) TryNext(ctx context.Context) bool { + if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { + return cr.Cursor.TryNext(ctx) + } + kctx, er := keploy.GetState(cr.ctx) + if er != nil { + return false + } + var output *bool + mode := kctx.Mode + switch mode { + case "test": + //dont run mongo query as it is stored in context + n := false + output = &n + case "capture": + n := cr.Cursor.TryNext(ctx) + output = &n + default: + return false + } + + meta := map[string]string{ + "name": "mongodb", + "type": string(models.NoSqlDB), + } + if cr.filter!=nil{ + meta["filter"] = fmt.Sprint(cr.filter) + meta["FindOptions"] = fmt.Sprint(cr.findOpts) + meta["operation"] = "Find.TryNext" + } else { + meta["pipeline"] = fmt.Sprint(cr.pipeline) + meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) + meta["operation"] = "Aggregate.TryNext" + + } + + mock, res := keploy.ProcessDep(cr.ctx, cr.log, meta, output) + + if mock { + if res[0] != nil { + output = res[0].(*bool) + } + } + return *output +} + +// All mocks mongo's Cursor.All in order to store and replay its output according SDK mode. +// +// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Cursor.All for information about Cursor.All. +func (cr *Cursor) All(ctx context.Context, results interface{}) error { + if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { + err := cr.Cursor.All(ctx, results) + return err + } + var err error + var kerr = &keploy.KError{} + kctx, er := keploy.GetState(cr.ctx) + if er != nil { + return er + } + mode := kctx.Mode + switch mode { + case "test": + //dont run mongo query as it is stored in context + err = nil + case "capture": + err = cr.Cursor.All(ctx, results) + default: + return errors.New("integrations: Not in a valid sdk mode") + } + + meta := map[string]string{ + "name": "mongodb", + "type": string(models.NoSqlDB), + } + if cr.filter!=nil{ + meta["filter"] = fmt.Sprint(cr.filter) + meta["FindOptions"] = fmt.Sprint(cr.findOpts) + meta["operation"] = "Find.All" + } else { + meta["pipeline"] = fmt.Sprint(cr.pipeline) + meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) + meta["operation"] = "Aggregate.All" + + } + + if err != nil { + kerr = &keploy.KError{Err: err} + } + mock, res := keploy.ProcessDep(cr.ctx, cr.log, meta, results, kerr) + + if mock { + var mockErr error + x := res[1].(*keploy.KError) + if x.Err != nil { + mockErr = x.Err + } + return mockErr + } + return err +} + +// Next mocks mongo's Cursor.Next in order to store and replay its output according SDK mode. +// +// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Cursor.Next for information about Cursor.Next. +func (cr *Cursor) Next(ctx context.Context) bool { + if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { + return cr.Cursor.Next(ctx) + } + kctx, er := keploy.GetState(cr.ctx) + if er != nil { + return false + } + var output *bool + mode := kctx.Mode + switch mode { + case "test": + //dont run mongo query as it is stored in context + n := false + output = &n + case "capture": + n := cr.Cursor.Next(ctx) + output = &n + default: + return false + } + + meta := map[string]string{ + "name": "mongodb", + "type": string(models.NoSqlDB), + } + if cr.filter!=nil{ + meta["filter"] = fmt.Sprint(cr.filter) + meta["FindOptions"] = fmt.Sprint(cr.findOpts) + meta["operation"] = "Find.Next" + } else { + meta["pipeline"] = fmt.Sprint(cr.pipeline) + meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) + meta["operation"] = "Aggregate.Next" + + } + + mock, res := keploy.ProcessDep(cr.ctx, cr.log, meta, output) + + if mock { + if res[0] != nil { + output = res[0].(*bool) + } + } + return *output +} + +// Decode mocks mongo's Cursor.Decode in order to store and replay its output according SDK mode. +// +// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Cursor.Decode for information about Cursor.Decode. +func (cr *Cursor) Decode(v interface{}) error { + if keploy.GetModeFromContext(cr.ctx) == keploy.MODE_OFF { + err := cr.Cursor.Decode(v) + return err + } + var err error + var kerr = &keploy.KError{} + kctx, er := keploy.GetState(cr.ctx) + if er != nil { + return er + } + mode := kctx.Mode + switch mode { + case "test": + //dont run mongo query as it is stored in context + err = nil + case "capture": + err = cr.Cursor.Decode(v) + default: + return errors.New("integrations: Not in a valid sdk mode") + } + + meta := map[string]string{ + "name": "mongodb", + "type": string(models.NoSqlDB), + } + if cr.filter!=nil{ + meta["filter"] = fmt.Sprint(cr.filter) + meta["FindOptions"] = fmt.Sprint(cr.findOpts) + meta["operation"] = "Find.Decode" + } else { + meta["pipeline"] = fmt.Sprint(cr.pipeline) + meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) + meta["operation"] = "Aggregate.Decode" + + } + + if err != nil { + kerr = &keploy.KError{Err: err} + } + mock, res := keploy.ProcessDep(cr.ctx, cr.log, meta, v, kerr) + + if mock { + var mockErr error + x := res[1].(*keploy.KError) + if x.Err != nil { + mockErr = x.Err + } + return mockErr + } + return err +} + +// // Find creates and returns the instance of pointer to Cursor which have overridden methods of mongo.Cursor. +// // Actual Collection.Find is called only in "capture" or "off" mode. +// // +// // For information about Collection.Find, See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Collection.Find. +// func (c *Collection) Find(ctx context.Context, filter interface{}, +// opts ...*options.FindOptions) (*Cursor, error) { +// if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { +// cursor, err := c.Collection.Find(ctx, filter, opts...) +// return &Cursor{ +// Cursor: *cursor, +// filter: filter, +// ctx: ctx, +// log: c.log, +// }, err +// } + +// derivedOpts := []options.FindOptions{} +// for _, j := range opts { +// derivedOpts = append(derivedOpts, *j) +// } +// kctx, er := keploy.GetState(ctx) +// if er != nil { +// return &Cursor{ +// filter: filter, +// findOpts: derivedOpts, +// log: c.log, +// ctx: ctx, +// }, er +// } +// mode := kctx.Mode +// var ( +// cursor *mongo.Cursor +// err error +// ) +// switch mode { +// case "test": +// //don't call method in test mode +// return &Cursor{ +// filter: filter, +// findOpts: derivedOpts, +// log: c.log, +// ctx: ctx, +// }, err +// case "capture": +// cursor, err = c.Collection.Find(ctx, filter, opts...) +// return &Cursor{ +// Cursor: *cursor, +// filter: filter, +// findOpts: derivedOpts, +// log: c.log, +// ctx: ctx, +// }, err +// default: +// c.log.Error("integrations: Not in a valid sdk mode") +// return &Cursor{ +// filter: filter, +// findOpts: derivedOpts, +// log: c.log, +// ctx: ctx, +// }, errors.New("integrations: Not in a valid sdk mode") +// } + +// } \ No newline at end of file diff --git a/integrations/kmongo/delete.go b/integrations/kmongo/delete.go new file mode 100644 index 0000000..b848c02 --- /dev/null +++ b/integrations/kmongo/delete.go @@ -0,0 +1,129 @@ +package kmongo + +import ( + "context" + "fmt" + + "github.com/keploy/go-sdk/keploy" + "go.keploy.io/server/pkg/models" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +// DeleteOne method mocks Collection.DeleteOne of mongo. +// +// See https://pkg.go.dev/go.mongodb.org/mongo-driver@v1.8.0/mongo#Collection.DeleteOne for +// information about Collection.DeleteOne. +func (c *Collection) DeleteOne(ctx context.Context, filter interface{}, + opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) { + if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { + output, err := c.Collection.DeleteOne(ctx, filter, opts...) + return output, err + } + + var ( + output = &mongo.DeleteResult{} + err error + kerr = &keploy.KError{} + data []interface{} + ) + data = append(data, filter) + for _, j := range opts { + data = append(data, j) + } + o, e := c.getOutput(ctx, "DeleteOne", data) + if o != nil { + output = o.(*mongo.DeleteResult) + } + err = e + + derivedOpts := []options.DeleteOptions{} + for _, j := range opts { + derivedOpts = append(derivedOpts, *j) + } + meta := map[string]string{ + "name": "mongodb", + "type": string(models.NoSqlDB), + "operation": "DeleteOne", + "filter": fmt.Sprint(filter), + "DeleteOptions": fmt.Sprint(derivedOpts), + } + + if err != nil { + kerr = &keploy.KError{Err: err} + output = &mongo.DeleteResult{} + } + mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) + + if mock { + var mockOutput *mongo.DeleteResult + var mockErr error + if res[0] != nil { + mockOutput = res[0].(*mongo.DeleteResult) + } + x := res[1].(*keploy.KError) + if x.Err != nil { + mockErr = x.Err + } + return mockOutput, mockErr + } + return output, err +} + +// DeleteMany method mocks Collection.DeleteMany of mongo inorder to call it only in "capture" or "off" mode. +// +// See https://pkg.go.dev/go.mongodb.org/mongo-driver@v1.8.0/mongo#Collection.DeleteMany for information about Collection.DeleteMany. +func (c *Collection) DeleteMany(ctx context.Context, filter interface{}, + opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) { + if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { + output, err := c.Collection.DeleteMany(ctx, filter, opts...) + return output, err + } + var ( + output *mongo.DeleteResult = &mongo.DeleteResult{} + err error + kerr = &keploy.KError{} + data []interface{} + ) + data = append(data, filter) + for _, j := range opts { + data = append(data, j) + } + o, e := c.getOutput(ctx, "DeleteMany", data) + if o != nil { + output = o.(*mongo.DeleteResult) + } + err = e + + derivedOpts := []options.DeleteOptions{} + for _, j := range opts { + derivedOpts = append(derivedOpts, *j) + } + meta := map[string]string{ + "name": "mongodb", + "type": string(models.NoSqlDB), + "operation": "DeleteMany", + "filter": fmt.Sprint(filter), + "DeleteOptions": fmt.Sprint(derivedOpts), + } + + if err != nil { + kerr = &keploy.KError{Err: err} + output = &mongo.DeleteResult{} + } + mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) + + if mock { + var mockOutput *mongo.DeleteResult + var mockErr error + if res[0] != nil { + mockOutput = res[0].(*mongo.DeleteResult) + } + x := res[1].(*keploy.KError) + if x.Err != nil { + mockErr = x.Err + } + return mockOutput, mockErr + } + return output, err +} \ No newline at end of file diff --git a/integrations/kmongo/find.go b/integrations/kmongo/find.go new file mode 100644 index 0000000..898686b --- /dev/null +++ b/integrations/kmongo/find.go @@ -0,0 +1,110 @@ +package kmongo + +import ( + "context" + "errors" + + "github.com/keploy/go-sdk/keploy" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +// FindOne method creates and returns pointer of SingleResult which containes mongo.SingleResult +// in order to mock its method. It mocks Collection.FindOne method explained above in integrations.NewCollections. +// +// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Collection.FindOne for information about Collection.FindOne. +func (c *Collection) FindOne(ctx context.Context, filter interface{}, opts ...*options.FindOneOptions) *SingleResult { + + derivedOpts := []options.FindOneOptions{} + for _, j := range opts { + derivedOpts = append(derivedOpts, *j) + } + var singleResult = &SingleResult{ + filter: filter, + opts: derivedOpts, + log: c.log, + ctx: ctx, + } + + if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { + sr := c.Collection.FindOne(ctx, filter, opts...) + if sr!=nil{ + singleResult.SingleResult = *sr + } + return singleResult + } + + kctx, err := keploy.GetState(ctx) + if err != nil { + return singleResult + } + mode := kctx.Mode + var sr *mongo.SingleResult + + switch mode { + case "test": + return singleResult + case "capture": + sr = c.Collection.FindOne(ctx, filter, opts...) + if sr!=nil{ + singleResult.SingleResult = *sr + } + default: + return singleResult + } + + return singleResult +} + +// Find creates and returns the instance of pointer to keploy Cursor struct which have overridden methods of mongo.Cursor. +// Actual Collection.Find is called only in "capture" or "off" mode. +// +// For information about Collection.Find, See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Collection.Find. +func (c *Collection) Find(ctx context.Context, filter interface{}, + opts ...*options.FindOptions) (*Cursor, error) { + + derivedOpts := []options.FindOptions{} + for _, j := range opts { + derivedOpts = append(derivedOpts, *j) + } + var result = &Cursor{ + filter : filter, + findOpts: derivedOpts, + log : c.log, + ctx : ctx, + } + + if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { + cursor, err := c.Collection.Find(ctx, filter, opts...) + if cursor!=nil{ + result.Cursor = *cursor + } + return result, err + } + + kctx, er := keploy.GetState(ctx) + if er != nil { + return result, er + } + mode := kctx.Mode + var ( + cursor *mongo.Cursor + err error + ) + + switch mode { + case "test": + //don't call method in test mode + return result, err + case "capture": + cursor, err = c.Collection.Find(ctx, filter, opts...) + if cursor!=nil{ + result.Cursor = *cursor + } + return result, err + default: + // c.log.Error("integrations: Not in a valid sdk mode") + return result, errors.New("integrations: Not in a valid sdk mode") + } + +} diff --git a/integrations/kmongo/insert.go b/integrations/kmongo/insert.go new file mode 100644 index 0000000..a059370 --- /dev/null +++ b/integrations/kmongo/insert.go @@ -0,0 +1,127 @@ +package kmongo + +import ( + "context" + "fmt" + + "github.com/keploy/go-sdk/keploy" + "go.keploy.io/server/pkg/models" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +// InsertOne method mocks Collection.InsertOne of mongo.Collection. Actual method isn't called in test mode only as stated in integrations.NewCollection. +// +// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Collection.InsertOne for information about Collection.InsertOne. +func (c *Collection) InsertOne(ctx context.Context, document interface{}, + opts ...*options.InsertOneOptions) (*mongo.InsertOneResult, error) { + if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { + output, err := c.Collection.InsertOne(ctx, document, opts...) + return output, err + } + var ( + output = &mongo.InsertOneResult{} + err error + kerr = &keploy.KError{} + data []interface{} + ) + data = append(data, document) + for _, j := range opts { + data = append(data, j) + } + o, e := c.getOutput(ctx, "InsertOne", data) + if o != nil { + output = o.(*mongo.InsertOneResult) + } + err = e + + derivedOpts := []options.InsertOneOptions{} + for _, j := range opts { + derivedOpts = append(derivedOpts, *j) + } + meta := map[string]string{ + "name": "mongodb", + "type": string(models.NoSqlDB), + "operation": "InsertOne", + "document": fmt.Sprint(document), + "InsertOneOptions": fmt.Sprint(derivedOpts), + } + + if err != nil { + kerr = &keploy.KError{Err: err} + output = &mongo.InsertOneResult{} + } + mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) + + if mock { + var mockOutput *mongo.InsertOneResult + var mockErr error + if res[0] != nil { + mockOutput = res[0].(*mongo.InsertOneResult) + } + x := res[1].(*keploy.KError) + if x.Err != nil { + mockErr = x.Err + } + return mockOutput, mockErr + } + return output, err +} + +// InsertMany method mocks Collection.InsertMany of mongo. +// +// For information about Collection.InsertMany, visit https://pkg.go.dev/go.mongodb.org/mongo-driver@v1.8.0/mongo#Collection.InsertMany. +func (c *Collection) InsertMany(ctx context.Context, documents []interface{}, + opts ...*options.InsertManyOptions) (*mongo.InsertManyResult, error) { + if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { + output, err := c.Collection.InsertMany(ctx, documents, opts...) + return output, err + } + var ( + output = &mongo.InsertManyResult{} + err error + kerr *keploy.KError = &keploy.KError{} + data []interface{} + ) + data = append(data, documents) + for _, j := range opts { + data = append(data, j) + } + o, e := c.getOutput(ctx, "InsertMany", data) + if o != nil { + output = o.(*mongo.InsertManyResult) + } + err = e + + derivedOpts := []options.InsertManyOptions{} + for _, j := range opts { + derivedOpts = append(derivedOpts, *j) + } + meta := map[string]string{ + "name": "mongodb", + "type": string(models.NoSqlDB), + "operation": "InsertMany", + "documents": fmt.Sprint(documents), + "InsertManyOptions": fmt.Sprint(derivedOpts), + } + + if err != nil { + kerr = &keploy.KError{Err: err} + output = &mongo.InsertManyResult{} + } + mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) + + if mock { + var mockOutput *mongo.InsertManyResult + var mockErr error + if res[0] != nil { + mockOutput = res[0].(*mongo.InsertManyResult) + } + x := res[1].(*keploy.KError) + if x.Err != nil { + mockErr = x.Err + } + return mockOutput, mockErr + } + return output, err +} diff --git a/integrations/kmongo/mdb.go b/integrations/kmongo/mdb.go index 6b23a56..0546219 100644 --- a/integrations/kmongo/mdb.go +++ b/integrations/kmongo/mdb.go @@ -42,914 +42,6 @@ type Collection struct { log *zap.Logger } -// SingleResult countains instance of mongo.SingleResult to mock its methods so that: -// - In "capture" mode, stores the encoded output(generated by mocked methods of mongo.SingleResult) into keploy's Context Deps array. -// - In "test" mode, decodes its stored encoded output which are present in the keploy's Context Deps array without calling mocked methods of mongo.SingleResult. -// - In "off" mode, returns the output generated after calling mocked method of mongo.SingleResult. -type SingleResult struct { - mongo.SingleResult - filter interface{} - opts []options.FindOneOptions - ctx context.Context - log *zap.Logger -} - -// Err mocks mongo's SingleResult Err() which will called in "capture" or "off" mode as stated above in SingleResult. -// -// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#SingleResult.Err for more information about SingleResult.Err. -func (msr *SingleResult) Err() error { - if keploy.GetModeFromContext(msr.ctx) == keploy.MODE_OFF { - err := msr.SingleResult.Err() - return err - } - var err error - var kerr *keploy.KError = &keploy.KError{} - kctx, er := keploy.GetState(msr.ctx) - if er != nil { - return er - } - mode := kctx.Mode - switch mode { - case "test": - //don't run mongo query as it is stored in context - case "capture": - err = msr.SingleResult.Err() - default: - return errors.New("integrations: Not in a valid sdk mode") - } - - meta := map[string]string{ - "name": "mongodb", - "type": string(models.NoSqlDB), - "operation": "FindOne.Err", - "filter": fmt.Sprint(msr.filter), - "FindOneOptions": fmt.Sprint(msr.opts), - } - - if err != nil { - kerr = &keploy.KError{Err: err} - } - mock, res := keploy.ProcessDep(msr.ctx, msr.log, meta, kerr) - - if mock { - var mockErr error - x := res[0].(*keploy.KError) - if x.Err != nil { - mockErr = x.Err - } - return mockErr - } - return err -} - -// Decode mocks mongo's SingleResult.Decode which will called in "capture" or "off" mode as stated above in SingleResult. -// -// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#SingleResult.Decode for more information about SingleResult.Decode. -func (msr *SingleResult) Decode(v interface{}) error { - if keploy.GetModeFromContext(msr.ctx) == keploy.MODE_OFF { - err := msr.SingleResult.Decode(v) - return err - } - var err error - var kerr = &keploy.KError{} - kctx, er := keploy.GetState(msr.ctx) - if er != nil { - return er - } - mode := kctx.Mode - switch mode { - case "test": - //dont run mongo query as it is stored in context - case "capture": - err = msr.SingleResult.Decode(v) - default: - return errors.New("integrations: Not in a valid sdk mode") - } - - meta := map[string]string{ - "name": "mongodb", - "type": string(models.NoSqlDB), - "operation": "FindOne.Decode", - "filter": fmt.Sprint(msr.filter), - "FindOneOptions": fmt.Sprint(msr.opts), - } - - if err != nil { - kerr = &keploy.KError{Err: err} - } - mock, res := keploy.ProcessDep(msr.ctx, msr.log, meta, v, kerr) - - if mock { - var mockErr error - // rv := reflect.ValueOf(v) - // rv.Elem().Set(reflect.ValueOf(res[0]).Elem()) - - x := res[1].(*keploy.KError) - if x.Err != nil { - mockErr = x.Err - } - return mockErr - } - return err -} - -// FindOne method creates and returns pointer of SingleResult which containes mongo.SingleResult -// in order to mock its method. It mocks Collection.FindOne method explained above in integrations.NewCollections. -// -// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Collection.FindOne for information about Collection.FindOne. -func (c *Collection) FindOne(ctx context.Context, filter interface{}, opts ...*options.FindOneOptions) *SingleResult { - if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { - sr := c.Collection.FindOne(ctx, filter, opts...) - return &SingleResult{ - SingleResult: *sr, - filter: filter, - log: c.log, - ctx: ctx, - } - } - derivedOpts := []options.FindOneOptions{} - for _, j := range opts { - derivedOpts = append(derivedOpts, *j) - } - kctx, er := keploy.GetState(ctx) - if er != nil { - return &SingleResult{ - filter: filter, - opts: derivedOpts, - log: c.log, - ctx: ctx, - } - } - mode := kctx.Mode - var sr *mongo.SingleResult - switch mode { - case "test": - return &SingleResult{ - filter: filter, - opts: derivedOpts, - log: c.log, - ctx: ctx, - } - case "capture": - sr = c.Collection.FindOne(ctx, filter, opts...) - default: - return &SingleResult{ - filter: filter, - opts: derivedOpts, - log: c.log, - ctx: ctx, - } - } - - return &SingleResult{ - SingleResult: *sr, - filter: filter, - opts: derivedOpts, - log: c.log, - ctx: ctx, - } -} - -// InsertOne method mocks Collection.InsertOne of mongo.Collection. Actual method isn't called in test mode only as stated in integrations.NewCollection. -// -// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Collection.InsertOne for information about Collection.InsertOne. -func (c *Collection) InsertOne(ctx context.Context, document interface{}, - opts ...*options.InsertOneOptions) (*mongo.InsertOneResult, error) { - if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { - output, err := c.Collection.InsertOne(ctx, document, opts...) - return output, err - } - var output = &mongo.InsertOneResult{} - var err error - var kerr = &keploy.KError{} - var data []interface{} - data = append(data, document) - for _, j := range opts { - data = append(data, j) - } - o, e := c.getOutput(ctx, "InsertOne", data) - if o != nil { - output = o.(*mongo.InsertOneResult) - } - err = e - - derivedOpts := []options.InsertOneOptions{} - for _, j := range opts { - derivedOpts = append(derivedOpts, *j) - } - meta := map[string]string{ - "name": "mongodb", - "type": string(models.NoSqlDB), - "operation": "InsertOne", - "document": fmt.Sprint(document), - "InsertOneOptions": fmt.Sprint(derivedOpts), - } - - if err != nil { - kerr = &keploy.KError{Err: err} - output = &mongo.InsertOneResult{} - } - mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) - - if mock { - var mockOutput *mongo.InsertOneResult - var mockErr error - if res[0] != nil { - mockOutput = res[0].(*mongo.InsertOneResult) - } - x := res[1].(*keploy.KError) - if x.Err != nil { - mockErr = x.Err - } - return mockOutput, mockErr - } - return output, err -} - -// Cursor contains emedded mongo.Cursor in order to override its methods. -type Cursor struct { - mongo.Cursor - filter interface{} - pipeline interface{} - findOpts []options.FindOptions - aggregateOpts []options.AggregateOptions - ctx context.Context - log *zap.Logger -} - -// Err mocks mongo's Cursor.Err in order to store and replay its output according SDK mode. -// -// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Cursor.Err for information about Cursor.Err. -func (cr *Cursor) Err() error { - if keploy.GetModeFromContext(cr.ctx) == keploy.MODE_OFF { - err := cr.Cursor.Err() - return err - } - var err error - var kerr = &keploy.KError{} - kctx, er := keploy.GetState(cr.ctx) - if er != nil { - return er - } - mode := kctx.Mode - switch mode { - case "test": - //dont run mongo query as it is stored in context - err = nil - case "capture": - err = cr.Cursor.Err() - default: - return errors.New("integrations: Not in a valid sdk mode") - } - - meta := map[string]string{ - "name": "mongodb", - "type": string(models.NoSqlDB), - } - if cr.filter!=nil{ - meta["filter"] = fmt.Sprint(cr.filter) - meta["FindOptions"] = fmt.Sprint(cr.findOpts) - meta["operation"] = "Find.Err" - } else { - meta["pipeline"] = fmt.Sprint(cr.pipeline) - meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) - meta["operation"] = "Aggregate.Err" - - } - - if err != nil { - kerr = &keploy.KError{Err: err} - } - mock, res := keploy.ProcessDep(cr.ctx, cr.log, meta, kerr) - - if mock { - var mockErr error - x := res[0].(*keploy.KError) - if x.Err != nil { - mockErr = x.Err - } - return mockErr - } - return err -} - -// Close mocks mongo's Cursor.Close in order to store and replay its output according SDK mode. -// -// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Cursor.Close for information about Cursor.Close. -func (cr *Cursor) Close(ctx context.Context) error { - if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { - err := cr.Cursor.Close(ctx) - return err - } - var err error - var kerr = &keploy.KError{} - kctx, er := keploy.GetState(cr.ctx) - if er != nil { - return er - } - mode := kctx.Mode - switch mode { - case "test": - //dont run mongo query as it is stored in context - err = nil - case "capture": - err = cr.Cursor.Close(ctx) - default: - return errors.New("integrations: Not in a valid sdk mode") - } - - meta := map[string]string{ - "name": "mongodb", - "type": string(models.NoSqlDB), - } - if cr.filter!=nil{ - meta["filter"] = fmt.Sprint(cr.filter) - meta["FindOptions"] = fmt.Sprint(cr.findOpts) - meta["operation"] = "Find.Err" - } else { - meta["pipeline"] = fmt.Sprint(cr.pipeline) - meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) - meta["operation"] = "Aggregate.Err" - - } - - if err != nil { - kerr = &keploy.KError{Err: err} - } - mock, res := keploy.ProcessDep(cr.ctx, cr.log, meta, kerr) - - if mock { - var mockErr error - x := res[0].(*keploy.KError) - if x.Err != nil { - mockErr = x.Err - } - return mockErr - } - return err -} - -// TryNext mocks mongo's Cursor.TryNext in order to store and replay its output according SDK mode. -// -// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Cursor.TryNext for information about Cursor.TryNext. -func (cr *Cursor) TryNext(ctx context.Context) bool { - if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { - return cr.Cursor.TryNext(ctx) - } - kctx, er := keploy.GetState(cr.ctx) - if er != nil { - return false - } - var output *bool - mode := kctx.Mode - switch mode { - case "test": - //dont run mongo query as it is stored in context - n := false - output = &n - case "capture": - n := cr.Cursor.TryNext(ctx) - output = &n - default: - return false - } - - meta := map[string]string{ - "name": "mongodb", - "type": string(models.NoSqlDB), - } - if cr.filter!=nil{ - meta["filter"] = fmt.Sprint(cr.filter) - meta["FindOptions"] = fmt.Sprint(cr.findOpts) - meta["operation"] = "Find.Err" - } else { - meta["pipeline"] = fmt.Sprint(cr.pipeline) - meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) - meta["operation"] = "Aggregate.Err" - - } - - mock, res := keploy.ProcessDep(cr.ctx, cr.log, meta, output) - - if mock { - if res[0] != nil { - output = res[0].(*bool) - } - } - return *output -} - -// All mocks mongo's Cursor.All in order to store and replay its output according SDK mode. -// -// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Cursor.All for information about Cursor.All. -func (cr *Cursor) All(ctx context.Context, results interface{}) error { - if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { - err := cr.Cursor.All(ctx, results) - return err - } - var err error - var kerr = &keploy.KError{} - kctx, er := keploy.GetState(cr.ctx) - if er != nil { - return er - } - mode := kctx.Mode - switch mode { - case "test": - //dont run mongo query as it is stored in context - err = nil - case "capture": - err = cr.Cursor.All(ctx, results) - default: - return errors.New("integrations: Not in a valid sdk mode") - } - - meta := map[string]string{ - "name": "mongodb", - "type": string(models.NoSqlDB), - } - if cr.filter!=nil{ - meta["filter"] = fmt.Sprint(cr.filter) - meta["FindOptions"] = fmt.Sprint(cr.findOpts) - meta["operation"] = "Find.Err" - } else { - meta["pipeline"] = fmt.Sprint(cr.pipeline) - meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) - meta["operation"] = "Aggregate.Err" - - } - - if err != nil { - kerr = &keploy.KError{Err: err} - } - mock, res := keploy.ProcessDep(cr.ctx, cr.log, meta, results, kerr) - - if mock { - var mockErr error - x := res[1].(*keploy.KError) - if x.Err != nil { - mockErr = x.Err - } - return mockErr - } - return err -} - -// Next mocks mongo's Cursor.Next in order to store and replay its output according SDK mode. -// -// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Cursor.Next for information about Cursor.Next. -func (cr *Cursor) Next(ctx context.Context) bool { - if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { - return cr.Cursor.Next(ctx) - } - kctx, er := keploy.GetState(cr.ctx) - if er != nil { - return false - } - var output *bool - mode := kctx.Mode - switch mode { - case "test": - //dont run mongo query as it is stored in context - n := false - output = &n - case "capture": - n := cr.Cursor.Next(ctx) - output = &n - default: - return false - } - - meta := map[string]string{ - "name": "mongodb", - "type": string(models.NoSqlDB), - } - if cr.filter!=nil{ - meta["filter"] = fmt.Sprint(cr.filter) - meta["FindOptions"] = fmt.Sprint(cr.findOpts) - meta["operation"] = "Find.Err" - } else { - meta["pipeline"] = fmt.Sprint(cr.pipeline) - meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) - meta["operation"] = "Aggregate.Err" - - } - - mock, res := keploy.ProcessDep(cr.ctx, cr.log, meta, output) - - if mock { - if res[0] != nil { - output = res[0].(*bool) - } - } - return *output -} - -// Decode mocks mongo's Cursor.Decode in order to store and replay its output according SDK mode. -// -// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Cursor.Decode for information about Cursor.Decode. -func (cr *Cursor) Decode(v interface{}) error { - if keploy.GetModeFromContext(cr.ctx) == keploy.MODE_OFF { - err := cr.Cursor.Decode(v) - return err - } - var err error - var kerr = &keploy.KError{} - kctx, er := keploy.GetState(cr.ctx) - if er != nil { - return er - } - mode := kctx.Mode - switch mode { - case "test": - //dont run mongo query as it is stored in context - err = nil - case "capture": - err = cr.Cursor.Decode(v) - default: - return errors.New("integrations: Not in a valid sdk mode") - } - - meta := map[string]string{ - "name": "mongodb", - "type": string(models.NoSqlDB), - } - if cr.filter!=nil{ - meta["filter"] = fmt.Sprint(cr.filter) - meta["FindOptions"] = fmt.Sprint(cr.findOpts) - meta["operation"] = "Find.Err" - } else { - meta["pipeline"] = fmt.Sprint(cr.pipeline) - meta["AggregateOptions"] = fmt.Sprint(cr.aggregateOpts) - meta["operation"] = "Aggregate.Err" - - } - - if err != nil { - kerr = &keploy.KError{Err: err} - } - mock, res := keploy.ProcessDep(cr.ctx, cr.log, meta, v, kerr) - - if mock { - var mockErr error - x := res[1].(*keploy.KError) - if x.Err != nil { - mockErr = x.Err - } - return mockErr - } - return err -} - -// Find creates and returns the instance of pointer to Cursor which have overridden methods of mongo.Cursor. -// Actual Collection.Find is called only in "capture" or "off" mode. -// -// For information about Collection.Find, See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Collection.Find. -func (c *Collection) Find(ctx context.Context, filter interface{}, - opts ...*options.FindOptions) (*Cursor, error) { - if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { - cursor, err := c.Collection.Find(ctx, filter, opts...) - return &Cursor{ - Cursor: *cursor, - filter: filter, - ctx: ctx, - log: c.log, - }, err - } - - derivedOpts := []options.FindOptions{} - for _, j := range opts { - derivedOpts = append(derivedOpts, *j) - } - kctx, er := keploy.GetState(ctx) - if er != nil { - return &Cursor{ - filter: filter, - findOpts: derivedOpts, - log: c.log, - ctx: ctx, - }, er - } - mode := kctx.Mode - var ( - cursor *mongo.Cursor - err error - ) - switch mode { - case "test": - //don't call method in test mode - return &Cursor{ - filter: filter, - findOpts: derivedOpts, - log: c.log, - ctx: ctx, - }, err - case "capture": - cursor, err = c.Collection.Find(ctx, filter, opts...) - return &Cursor{ - Cursor: *cursor, - filter: filter, - findOpts: derivedOpts, - log: c.log, - ctx: ctx, - }, err - default: - c.log.Error("integrations: Not in a valid sdk mode") - return &Cursor{ - filter: filter, - findOpts: derivedOpts, - log: c.log, - ctx: ctx, - }, errors.New("integrations: Not in a valid sdk mode") - } - -} - -// InsertMany method mocks Collection.InsertMany of mongo. -// -// For information about Collection.InsertMany, visit https://pkg.go.dev/go.mongodb.org/mongo-driver@v1.8.0/mongo#Collection.InsertMany. -func (c *Collection) InsertMany(ctx context.Context, documents []interface{}, - opts ...*options.InsertManyOptions) (*mongo.InsertManyResult, error) { - if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { - output, err := c.Collection.InsertMany(ctx, documents, opts...) - return output, err - } - var output = &mongo.InsertManyResult{} - var err error - var kerr *keploy.KError = &keploy.KError{} - var data []interface{} - data = append(data, documents) - for _, j := range opts { - data = append(data, j) - } - o, e := c.getOutput(ctx, "InsertMany", data) - if o != nil { - output = o.(*mongo.InsertManyResult) - } - err = e - - derivedOpts := []options.InsertManyOptions{} - for _, j := range opts { - derivedOpts = append(derivedOpts, *j) - } - meta := map[string]string{ - "name": "mongodb", - "type": string(models.NoSqlDB), - "operation": "InsertMany", - "documents": fmt.Sprint(documents), - "InsertManyOptions": fmt.Sprint(derivedOpts), - } - - if err != nil { - kerr = &keploy.KError{Err: err} - output = &mongo.InsertManyResult{} - } - mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) - - if mock { - var mockOutput *mongo.InsertManyResult - var mockErr error - if res[0] != nil { - mockOutput = res[0].(*mongo.InsertManyResult) - } - x := res[1].(*keploy.KError) - if x.Err != nil { - mockErr = x.Err - } - return mockOutput, mockErr - } - return output, err -} - -// UpdateOne method mocks Collection.UpdateOne of mongo. -// -// For information about Collection.UpdateOne, refer to https://pkg.go.dev/go.mongodb.org/mongo-driver@v1.8.0/mongo#Collection.UpdateOne. -func (c *Collection) UpdateOne(ctx context.Context, filter interface{}, update interface{}, - opts ...*options.UpdateOptions) (*mongo.UpdateResult, error) { - if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { - output, err := c.Collection.UpdateOne(ctx, filter, update, opts...) - return output, err - } - var output = &mongo.UpdateResult{} - var err error - var kerr = &keploy.KError{} - var data []interface{} - data = append(data, filter) - data = append(data, update) - for _, j := range opts { - data = append(data, j) - } - o, e := c.getOutput(ctx, "UpdateOne", data) - if o != nil { - output = o.(*mongo.UpdateResult) - } - err = e - - derivedOpts := []options.UpdateOptions{} - for _, j := range opts { - derivedOpts = append(derivedOpts, *j) - } - meta := map[string]string{ - "name": "mongodb", - "type": string(models.NoSqlDB), - "operation": "UpdateOne", - "filter": fmt.Sprint(filter), - "update": fmt.Sprint(update), - "UpdateOptions": fmt.Sprint(derivedOpts), - } - - if err != nil { - kerr = &keploy.KError{Err: err} - output = &mongo.UpdateResult{} - } - mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) - - if mock { - var mockOutput *mongo.UpdateResult - var mockErr error - if res[0] != nil { - mockOutput = res[0].(*mongo.UpdateResult) - } - x := res[1].(*keploy.KError) - if x.Err != nil { - mockErr = x.Err - } - return mockOutput, mockErr - } - return output, err -} - -// UpdateMany method mocks Collection.UpdateMany of mongo. -// -// For information about Collection.UpdateMany, go to https://pkg.go.dev/go.mongodb.org/mongo-driver@v1.8.0/mongo#Collection.UpdateMany. -func (c *Collection) UpdateMany(ctx context.Context, filter interface{}, update interface{}, - opts ...*options.UpdateOptions) (*mongo.UpdateResult, error) { - if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { - output, err := c.Collection.UpdateMany(ctx, filter, update, opts...) - return output, err - } - var output = &mongo.UpdateResult{} - var err error - var kerr *keploy.KError = &keploy.KError{} - var data []interface{} - data = append(data, filter) - data = append(data, update) - for _, j := range opts { - data = append(data, j) - } - o, e := c.getOutput(ctx, "UpdateMany", data) - if o != nil { - output = o.(*mongo.UpdateResult) - } - err = e - - derivedOpts := []options.UpdateOptions{} - for _, j := range opts { - derivedOpts = append(derivedOpts, *j) - } - meta := map[string]string{ - "name": "mongodb", - "type": string(models.NoSqlDB), - "operation": "UpdateMany", - "filter": fmt.Sprint(filter), - "update": fmt.Sprint(update), - "UpdateOptions": fmt.Sprint(derivedOpts), - } - - if err != nil { - kerr = &keploy.KError{Err: err} - output = &mongo.UpdateResult{} - } - mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) - - if mock { - var mockOutput *mongo.UpdateResult - var mockErr error - if res[0] != nil { - mockOutput = res[0].(*mongo.UpdateResult) - } - x := res[1].(*keploy.KError) - if x.Err != nil { - mockErr = x.Err - } - return mockOutput, mockErr - } - return output, err -} - -// DeleteOne method mocks Collection.DeleteOne of mongo. -// -// See https://pkg.go.dev/go.mongodb.org/mongo-driver@v1.8.0/mongo#Collection.DeleteOne for -// information about Collection.DeleteOne. -func (c *Collection) DeleteOne(ctx context.Context, filter interface{}, - opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) { - if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { - output, err := c.Collection.DeleteOne(ctx, filter, opts...) - return output, err - } - var output = &mongo.DeleteResult{} - var err error - var kerr = &keploy.KError{} - var data []interface{} - data = append(data, filter) - for _, j := range opts { - data = append(data, j) - } - o, e := c.getOutput(ctx, "DeleteOne", data) - if o != nil { - output = o.(*mongo.DeleteResult) - } - err = e - - derivedOpts := []options.DeleteOptions{} - for _, j := range opts { - derivedOpts = append(derivedOpts, *j) - } - meta := map[string]string{ - "name": "mongodb", - "type": string(models.NoSqlDB), - "operation": "DeleteOne", - "filter": fmt.Sprint(filter), - "DeleteOptions": fmt.Sprint(derivedOpts), - } - - if err != nil { - kerr = &keploy.KError{Err: err} - output = &mongo.DeleteResult{} - } - mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) - - if mock { - var mockOutput *mongo.DeleteResult - var mockErr error - if res[0] != nil { - mockOutput = res[0].(*mongo.DeleteResult) - } - x := res[1].(*keploy.KError) - if x.Err != nil { - mockErr = x.Err - } - return mockOutput, mockErr - } - return output, err -} - -// DeleteMany method mocks Collection.DeleteMany of mongo inorder to call it only in "capture" or "off" mode. -// -// See https://pkg.go.dev/go.mongodb.org/mongo-driver@v1.8.0/mongo#Collection.DeleteMany for information about Collection.DeleteMany. -func (c *Collection) DeleteMany(ctx context.Context, filter interface{}, - opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) { - if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { - output, err := c.Collection.DeleteMany(ctx, filter, opts...) - return output, err - } - var output *mongo.DeleteResult = &mongo.DeleteResult{} - var err error - var kerr = &keploy.KError{} - var data []interface{} - data = append(data, filter) - for _, j := range opts { - data = append(data, j) - } - o, e := c.getOutput(ctx, "DeleteMany", data) - if o != nil { - output = o.(*mongo.DeleteResult) - } - err = e - - derivedOpts := []options.DeleteOptions{} - for _, j := range opts { - derivedOpts = append(derivedOpts, *j) - } - meta := map[string]string{ - "name": "mongodb", - "type": string(models.NoSqlDB), - "operation": "DeleteMany", - "filter": fmt.Sprint(filter), - "DeleteOptions": fmt.Sprint(derivedOpts), - } - - if err != nil { - kerr = &keploy.KError{Err: err} - output = &mongo.DeleteResult{} - } - mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) - - if mock { - var mockOutput *mongo.DeleteResult - var mockErr error - if res[0] != nil { - mockOutput = res[0].(*mongo.DeleteResult) - } - x := res[1].(*keploy.KError) - if x.Err != nil { - mockErr = x.Err - } - return mockOutput, mockErr - } - return output, err -} - // Distinct method mocks Collection.Distinct of mongo inorder to call it only in "capture" or "off" mode. // // See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#Collection.Distinct for more info about Distinct. diff --git a/integrations/kmongo/singleResult.go b/integrations/kmongo/singleResult.go new file mode 100644 index 0000000..217006a --- /dev/null +++ b/integrations/kmongo/singleResult.go @@ -0,0 +1,124 @@ +package kmongo + +import ( + "context" + "errors" + "fmt" + + "github.com/keploy/go-sdk/keploy" + "go.keploy.io/server/pkg/models" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "go.uber.org/zap" +) + +// SingleResult countains instance of mongo.SingleResult to mock its methods so that: +// - In "capture" mode, stores the encoded output(generated by mocked methods of mongo.SingleResult) into keploy's Context Deps array. +// - In "test" mode, decodes its stored encoded output which are present in the keploy's Context Deps array without calling mocked methods of mongo.SingleResult. +// - In "off" mode, returns the output generated after calling mocked method of mongo.SingleResult. +type SingleResult struct { + mongo.SingleResult + filter interface{} + opts []options.FindOneOptions + ctx context.Context + log *zap.Logger +} + +// Err mocks mongo's SingleResult Err() which will called in "capture" or "off" mode as stated above in SingleResult. +// +// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#SingleResult.Err for more information about SingleResult.Err. +func (msr *SingleResult) Err() error { + if keploy.GetModeFromContext(msr.ctx) == keploy.MODE_OFF { + err := msr.SingleResult.Err() + return err + } + var err error + var kerr *keploy.KError = &keploy.KError{} + kctx, er := keploy.GetState(msr.ctx) + if er != nil { + return er + } + mode := kctx.Mode + switch mode { + case "test": + //don't run mongo query as it is stored in context + case "capture": + err = msr.SingleResult.Err() + default: + return errors.New("integrations: Not in a valid sdk mode") + } + + meta := map[string]string{ + "name": "mongodb", + "type": string(models.NoSqlDB), + "operation": "FindOne.Err", + "filter": fmt.Sprint(msr.filter), + "FindOneOptions": fmt.Sprint(msr.opts), + } + + if err != nil { + kerr = &keploy.KError{Err: err} + } + mock, res := keploy.ProcessDep(msr.ctx, msr.log, meta, kerr) + + if mock { + var mockErr error + x := res[0].(*keploy.KError) + if x.Err != nil { + mockErr = x.Err + } + return mockErr + } + return err +} + +// Decode mocks mongo's SingleResult.Decode which will called in "capture" or "off" mode as stated above in SingleResult. +// +// See https://pkg.go.dev/go.mongodb.org/mongo-driver/mongo#SingleResult.Decode for more information about SingleResult.Decode. +func (msr *SingleResult) Decode(v interface{}) error { + if keploy.GetModeFromContext(msr.ctx) == keploy.MODE_OFF { + err := msr.SingleResult.Decode(v) + return err + } + var err error + var kerr = &keploy.KError{} + kctx, er := keploy.GetState(msr.ctx) + if er != nil { + return er + } + mode := kctx.Mode + switch mode { + case "test": + //dont run mongo query as it is stored in context + case "capture": + err = msr.SingleResult.Decode(v) + default: + return errors.New("integrations: Not in a valid sdk mode") + } + + meta := map[string]string{ + "name": "mongodb", + "type": string(models.NoSqlDB), + "operation": "FindOne.Decode", + "filter": fmt.Sprint(msr.filter), + "FindOneOptions": fmt.Sprint(msr.opts), + } + + if err != nil { + kerr = &keploy.KError{Err: err} + } + mock, res := keploy.ProcessDep(msr.ctx, msr.log, meta, v, kerr) + + if mock { + var mockErr error + // rv := reflect.ValueOf(v) + // rv.Elem().Set(reflect.ValueOf(res[0]).Elem()) + + x := res[1].(*keploy.KError) + if x.Err != nil { + mockErr = x.Err + } + return mockErr + } + return err +} diff --git a/integrations/kmongo/update.go b/integrations/kmongo/update.go new file mode 100644 index 0000000..611d0e6 --- /dev/null +++ b/integrations/kmongo/update.go @@ -0,0 +1,135 @@ +package kmongo + +import ( + "context" + "fmt" + + "github.com/keploy/go-sdk/keploy" + "go.keploy.io/server/pkg/models" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +// UpdateOne method mocks Collection.UpdateOne of mongo. +// +// For information about Collection.UpdateOne, refer to https://pkg.go.dev/go.mongodb.org/mongo-driver@v1.8.0/mongo#Collection.UpdateOne. +func (c *Collection) UpdateOne(ctx context.Context, filter interface{}, update interface{}, + opts ...*options.UpdateOptions) (*mongo.UpdateResult, error) { + + if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { + output, err := c.Collection.UpdateOne(ctx, filter, update, opts...) + return output, err + } + var ( + output = &mongo.UpdateResult{} + err error + kerr = &keploy.KError{} + data []interface{} + ) + data = append(data, filter) + data = append(data, update) + for _, j := range opts { + data = append(data, j) + } + o, e := c.getOutput(ctx, "UpdateOne", data) + if o != nil { + output = o.(*mongo.UpdateResult) + } + err = e + + derivedOpts := []options.UpdateOptions{} + for _, j := range opts { + derivedOpts = append(derivedOpts, *j) + } + meta := map[string]string{ + "name": "mongodb", + "type": string(models.NoSqlDB), + "operation": "UpdateOne", + "filter": fmt.Sprint(filter), + "update": fmt.Sprint(update), + "UpdateOptions": fmt.Sprint(derivedOpts), + } + + if err != nil { + kerr = &keploy.KError{Err: err} + output = &mongo.UpdateResult{} + } + mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) + + if mock { + var mockOutput *mongo.UpdateResult + var mockErr error + if res[0] != nil { + mockOutput = res[0].(*mongo.UpdateResult) + } + x := res[1].(*keploy.KError) + if x.Err != nil { + mockErr = x.Err + } + return mockOutput, mockErr + } + return output, err +} + +// UpdateMany method mocks Collection.UpdateMany of mongo. +// +// For information about Collection.UpdateMany, go to https://pkg.go.dev/go.mongodb.org/mongo-driver@v1.8.0/mongo#Collection.UpdateMany. +func (c *Collection) UpdateMany(ctx context.Context, filter interface{}, update interface{}, + opts ...*options.UpdateOptions) (*mongo.UpdateResult, error) { + + if keploy.GetModeFromContext(ctx) == keploy.MODE_OFF { + output, err := c.Collection.UpdateMany(ctx, filter, update, opts...) + return output, err + } + + var ( + output = &mongo.UpdateResult{} + err error + kerr *keploy.KError = &keploy.KError{} + data []interface{} + ) + data = append(data, filter) + data = append(data, update) + for _, j := range opts { + data = append(data, j) + } + + o, e := c.getOutput(ctx, "UpdateMany", data) + if o != nil { + output = o.(*mongo.UpdateResult) + } + err = e + + derivedOpts := []options.UpdateOptions{} + for _, j := range opts { + derivedOpts = append(derivedOpts, *j) + } + meta := map[string]string{ + "name": "mongodb", + "type": string(models.NoSqlDB), + "operation": "UpdateMany", + "filter": fmt.Sprint(filter), + "update": fmt.Sprint(update), + "UpdateOptions": fmt.Sprint(derivedOpts), + } + + if err != nil { + kerr = &keploy.KError{Err: err} + output = &mongo.UpdateResult{} + } + mock, res := keploy.ProcessDep(ctx, c.log, meta, output, kerr) + + if mock { + var mockOutput *mongo.UpdateResult + var mockErr error + if res[0] != nil { + mockOutput = res[0].(*mongo.UpdateResult) + } + x := res[1].(*keploy.KError) + if x.Err != nil { + mockErr = x.Err + } + return mockOutput, mockErr + } + return output, err +} diff --git a/keploy/keploy.go b/keploy/keploy.go index 1d627f1..2cb8cf4 100644 --- a/keploy/keploy.go +++ b/keploy/keploy.go @@ -387,7 +387,6 @@ func (k *Keploy) denoise (id string, tcs regression.TestCaseReq){ return } k.setKey(r) - k.Log.Debug("header before denoise: ", zap.Any("", r.Header)) r.Header.Set("Content-Type", "application/json") _, err = k.client.Do(r) From f53605c970be75077fe982cb479199247520a051 Mon Sep 17 00:00:00 2001 From: re-Tick Date: Fri, 4 Mar 2022 21:40:51 +0530 Subject: [PATCH 04/10] feat(kmux): adds a middleware function for gorilla/mux multiplexer #66 --- README.md | 27 +++++++++++ go.mod | 1 + go.sum | 2 + integrations/kmux/mux.go | 100 +++++++++++++++++++++++++++++++++++++++ keploy/keploy.go | 4 +- 5 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 integrations/kmux/mux.go diff --git a/README.md b/README.md index 0fce552..fb11c96 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,33 @@ kwebgo.WebGoV4(k , router) router.Start() ``` +### 5. Gorilla/Mux +```go +r := mux.NewRouter() +kmux.Mux(k, r) +``` +#### Example +```go +import( + "github.com/keploy/go-sdk/integrations/kmux" + "net/http" +) + +r := mux.NewRouter() +port := "8080" +k := keploy.New(keploy.Config{ + App: keploy.AppConfig{ + Name: "my-app", + Port: port, + }, + Server: keploy.ServerConfig{ + URL: "http://localhost:8081/api", + }, +}) +kmux.Mux(k, r) +http.ListenAndServe(":"+port, r) +``` + ## Supported Databases ### 1. MongoDB ```go diff --git a/go.mod b/go.mod index bd82b0d..05406c7 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( github.com/golang/protobuf v1.4.3 // indirect github.com/golang/snappy v0.0.1 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/mux v1.8.0 github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.9 // indirect github.com/klauspost/compress v1.13.6 // indirect diff --git a/go.sum b/go.sum index 66d7263..6492a2a 100644 --- a/go.sum +++ b/go.sum @@ -93,6 +93,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= diff --git a/integrations/kmux/mux.go b/integrations/kmux/mux.go new file mode 100644 index 0000000..e99713f --- /dev/null +++ b/integrations/kmux/mux.go @@ -0,0 +1,100 @@ +package kmux + +import ( + "bytes" + "context" + "io" + "io/ioutil" + "net/http" + + "github.com/gorilla/mux" + "github.com/keploy/go-sdk/keploy" + "go.keploy.io/server/pkg/models" + "go.uber.org/zap" +) + +// Mux adds keploy instrumentation for Mux router. +// It should be ideally used after other instrumentation libraries like APMs. +// +// k is the Keploy instance +// +// w is the mux router instance +func Mux(k *keploy.Keploy, w *mux.Router) { + if keploy.GetMode() == keploy.MODE_OFF { + return + } + w.Use(mw(k)) +} + +func captureRespMux(w http.ResponseWriter, r *http.Request, next http.Handler) models.HttpResp { + resBody := new(bytes.Buffer) + mw := io.MultiWriter(w, resBody) + writer := &keploy.BodyDumpResponseWriter{ + Writer: mw, + ResponseWriter: w, + Status: http.StatusOK, + } + w = writer + + next.ServeHTTP(w, r) + return models.HttpResp{ + //Status + + StatusCode: writer.Status, + Header: w.Header(), + Body: resBody.String(), + } +} + +func mw(k *keploy.Keploy) func( http.Handler) http.Handler { + if k == nil { + return func(next http.Handler) http.Handler{ + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + next.ServeHTTP(w, r) + }) + } + } + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + id := r.Header.Get("KEPLOY_TEST_ID") + if id != "" { + // id is only present during simulation + // run it similar to how testcases would run + ctx := context.WithValue(r.Context(), keploy.KCTX, &keploy.Context{ + Mode: "test", + TestID: id, + Deps: k.GetDependencies(id), + }) + + r = r.WithContext(ctx) + resp := captureRespMux(w, r, next) + k.PutResp(id, resp) + return + } + ctx := context.WithValue(r.Context(), keploy.KCTX, &keploy.Context{ + Mode: "capture", + }) + + r = r.WithContext(ctx) + + // Request + var reqBody []byte + var err error + if r.Body != nil { // Read + reqBody, err = ioutil.ReadAll(r.Body) + if err != nil { + // TODO right way to log errors + k.Log.Error("Unable to read request body", zap.Error(err)) + return + } + } + r.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody)) // Reset + + resp := captureRespMux(w, r, next) + // params := webgo.Context(r).Params() + params := mux.Vars(r) + keploy.CaptureTestcase(k, r, reqBody, resp, params) + + }) + } +} \ No newline at end of file diff --git a/keploy/keploy.go b/keploy/keploy.go index 2cb8cf4..fb9b165 100644 --- a/keploy/keploy.go +++ b/keploy/keploy.go @@ -117,8 +117,8 @@ type Keploy struct { Log *zap.Logger client *http.Client deps sync.Map - //Deps map[string][]models.Dependency - resp sync.Map + //Deps map[string][]models.Dependency + resp sync.Map //Resp map[string]models.HttpResp } From e427a5ccd667bba25009898f1698cd0393e715fc Mon Sep 17 00:00:00 2001 From: re-Tick Date: Tue, 8 Mar 2022 17:22:07 +0530 Subject: [PATCH 05/10] fix(khttpClient): adds an interceptor for http client to mock the outputs of external http calls --- README.md | 134 +++++++--- integrations/doc_test.go | 237 ++++++++++++------ integrations/khttpclient/httpClient.go | 322 ++++++------------------- 3 files changed, 332 insertions(+), 361 deletions(-) diff --git a/README.md b/README.md index fb11c96..20daa9e 100644 --- a/README.md +++ b/README.md @@ -232,53 +232,121 @@ Following operations are supported:
- GetItemWithContext - PutItemWithContext ### 3. SQL Driver +Keploy inplements most of the sql driver's interface for mocking the outputs of sql queries. Its compatible with gORM. +**Note**: sql methods which have request context as parameter can be supported because outputs are replayed or captured to context. +Here is an example - ```go -import( - "github.com/keploy/go-sdk/integrations/ksql" - "github.com/lib/pq" -) + import ( + "github.com/keploy/go-sdk/integrations/ksql" + "github.com/lib/pq" + ) + func main(){ + // Register keploy sql driver to database/sql package. + driver := ksql.Driver{Driver: pq.Driver{}} + sql.Register("keploy", &driver) + + pSQL_URI := fmt.Sprintf("host=%s user=%s dbname=%s sslmode=disable password=%s port=%s", "localhost", "postgres", "Book_Keeper", "8789", "5432") + // keploy driver will internally open the connection using dataSourceName string parameter + db, err := sql.Open("keploy", pSQL_URI) + if err!=nil{ + log.Fatal(err) + } else { + fmt.Println("Successfully connected to postgres") + } + defer db.Close -func init(){ - driver := ksql.Driver{Driver: pq.Driver{}} - sql.Register("keploy", &driver) -} + r:=gin.New() + kgin.GinV1(kApp, r) + r.GET("/gin/:color/*type", func(c *gin.Context) { + // ctx parameter of PingContext should be request context. + err = db.PingContext(r.Context()) + if err!=nil{ + log.Fatal(err) + } + id := 47 + result, err := db.ExecContext(ctx, "UPDATE balances SET balance = balance + 10 WHERE user_id = ?", id) + if err != nil { + log.Fatal(err) + } + })) + } ``` -Its compatible with gORM. Here is an example - +**Note**: To integerate with gORM set DisableAutomaticPing of gorm.Config to true. Also pass request context to methods as params. +Example for gORM: ```go - pSQL_URI := fmt.Sprintf("host=%s user=%s dbname=%s sslmode=disable password=%s port=%s", "localhost", "postgres", "Book_Keeper", "8789", "5432") - // set DisableAutomaticPing to true for capturing and replaying the outputs of querries stored in requests context. - pSQL_DB, err := gorm.Open(postgres.New(postgres.Config{DriverName: "keploy", DSN: pSQL_URI}), &gorm.Config{ DisableAutomaticPing: true }) - if err!=nil{ - log.Fatal(err) - } else { - fmt.Println("Successfully connected to postgres") + func main(){ + // Register keploy sql driver to database/sql package. + driver := ksql.Driver{Driver: pq.Driver{}} + sql.Register("keploy", &driver) + + pSQL_URI := fmt.Sprintf("host=%s user=%s dbname=%s sslmode=disable password=%s port=%s", "localhost", "postgres", "Book_Keeper", "8789", "5432") + + // set DisableAutomaticPing to true so that . + pSQL_DB, err := gorm.Open( postgres.New(postgres.Config{ + DriverName: "keploy", + DSN: pSQL_URI + }), &gorm.Config{ + DisableAutomaticPing: true + }) + r:=gin.New() + kgin.GinV1(kApp, r) + r.GET("/gin/:color/*type", func(c *gin.Context) { + // set the context of *gorm.DB with request's context of http Handler function before queries. + pSQL_DB = pSQL_DB.WithContext(r.Context()) + // Find + var ( + people []Book + ) + x := pSQL_DB.Find(&people) + })) } - r:=gin.New() - kgin.GinV1(kApp, r) - r.GET("/gin/:color/*type", func(c *gin.Context) { - // set the context of *gorm.DB with request's context of http Handler function before queries. - pSQL_DB = pSQL_DB.WithContext(r.Context()) - // Find - var ( - people []Book - ) - x := pSQL_DB.Find(&people) - })) ``` ## Supported Clients ### net/http ```go -khttpclient.NewHttpClient(&http.Client{}) +interceptor := khttpclient.NewInterceptor(http.DefaultTransport) +client := http.Client{ + Transport: interceptor, +} ``` #### Example ```go import("github.com/keploy/go-sdk/integrations/khttpclient") -func(w http.ResponseWriter, r *http.Request){ - client := khttpclient.NewHttpClient(&http.Client{}) -// ensure to add request context to all outgoing http requests - client.SetCtxHttpClient(r.Context()) - resp, err := client.Get("https://example.com") +func main(){ + // initialize a gorilla mux + r := mux.NewRouter() + // keploy config + port := "8080" + kApp := keploy.New(keploy.Config{ + App: keploy.AppConfig{ + Name: "Mux-Demo-app", + Port: port, + }, + Server: keploy.ServerConfig{ + URL: "http://localhost:8081/api", + }, + }) + // configure mux for integeration with keploy + kmux.Mux(kApp, r) + // configure http client with keploy's interceptor + interceptor := khttpclient.NewInterceptor(http.DefaultTransport) + client := http.Client{ + Transport: interceptor, + } + + r.HandleFunc("/mux/{category}/{params}",func (w http.ResponseWriter, r *http.Request) { + // SetCtxHttpClient is called before mocked http.Client's Get method. + interceptor.SetContext(r.Context()) + // make get request to external http service + resp, err := client.Get("https://example.com") + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + fmt.Println("BODY : ", body) + }) } ``` **Note**: ensure to add pass request context to all external requests like http requests, db calls, etc. diff --git a/integrations/doc_test.go b/integrations/doc_test.go index 9e6eb9a..e254f97 100644 --- a/integrations/doc_test.go +++ b/integrations/doc_test.go @@ -12,12 +12,14 @@ import ( "github.com/bnkamalesh/webgo/v4" "github.com/gin-gonic/gin" + "github.com/gorilla/mux" "github.com/keploy/go-sdk/integrations/kecho/v4" "github.com/keploy/go-sdk/integrations/kgin/v1" "github.com/keploy/go-sdk/integrations/kgrpc" "github.com/keploy/go-sdk/integrations/khttpclient" "github.com/keploy/go-sdk/integrations/kmongo" + "github.com/keploy/go-sdk/integrations/kmux" "github.com/keploy/go-sdk/integrations/kwebgo/v4" "github.com/keploy/go-sdk/keploy" "github.com/labstack/echo/v4" @@ -443,95 +445,182 @@ func ExampleWithClientStreamInterceptor() { defer conn.Close() } -func ExampleNewHttpClient() { - r := &http.Request{} // Here, r is for demo. You should use your handler's request as r. - tr := &http.Transport{ - MaxIdleConns: 10, - IdleConnTimeout: 30 * time.Second, - DisableCompression: true, - } - client := khttpclient.NewHttpClient(&http.Client{ - Transport: tr, +func ExampleNewInterceptor() { + // initialize a gorilla mux + r := mux.NewRouter() + // keploy config + port := "8080" + kApp := keploy.New(keploy.Config{ + App: keploy.AppConfig{ + Name: "Mux-Demo-app", + Port: port, + }, + Server: keploy.ServerConfig{ + URL: "http://localhost:8081/api", + }, }) - - // SetCtxHttpClient is called before mocked http.Client's Get method. - client.SetCtxHttpClient(r.Context()) - resp, err := client.Get("https://example.com") - if err != nil { - log.Fatal(err) + // configure mux for integeration with keploy + kmux.Mux(kApp, r) + // configure http client with keploy's interceptor + interceptor := khttpclient.NewInterceptor(http.DefaultTransport) + _ = http.Client{ + Transport: interceptor, } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - fmt.Println("BODY : ", body) + } func ExampleHttpClient_SetCtxHttpClient() { - r := &http.Request{} // Here, r is for demo. You should use your handler's request as r. - client := khttpclient.NewHttpClient(&http.Client{}) - - // SetCtxHttpClient is called before mocked http.Client's Get method. - client.SetCtxHttpClient(r.Context()) - resp, err := client.Get("https://example.com") - if err != nil { - log.Fatal(err) - } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - fmt.Println("BODY : ", body) + // initialize a gorilla mux + r := mux.NewRouter() + // keploy config + port := "8080" + kApp := keploy.New(keploy.Config{ + App: keploy.AppConfig{ + Name: "Mux-Demo-app", + Port: port, + }, + Server: keploy.ServerConfig{ + URL: "http://localhost:8081/api", + }, + }) + // configure mux for integeration with keploy + kmux.Mux(kApp, r) + // configure http client with keploy's interceptor + interceptor := khttpclient.NewInterceptor(http.DefaultTransport) + client := http.Client{ + Transport: interceptor, + } + + r.HandleFunc("/mux/{category}/{params}",func (w http.ResponseWriter, r *http.Request) { + // SetCtxHttpClient is called before mocked http.Client's Get method. + interceptor.SetContext(r.Context()) + // make get request to external http service + resp, err := client.Get("https://example.com") + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + fmt.Println("BODY : ", body) + }) } func ExampleHttpClient_Get() { - r := &http.Request{} // Here, r is for demo. You should use your handler's request as r. - client := khttpclient.NewHttpClient(&http.Client{}) + // initialize a gorilla mux + r := mux.NewRouter() + // keploy config + port := "8080" + kApp := keploy.New(keploy.Config{ + App: keploy.AppConfig{ + Name: "Mux-Demo-app", + Port: port, + }, + Server: keploy.ServerConfig{ + URL: "http://localhost:8081/api", + }, + }) + // configure mux for integeration with keploy + kmux.Mux(kApp, r) + // configure http client with keploy's interceptor + interceptor := khttpclient.NewInterceptor(http.DefaultTransport) + client := http.Client{ + Transport: interceptor, + } + + r.HandleFunc("/mux/{category}/{params}",func (w http.ResponseWriter, r *http.Request) { + // SetCtxHttpClient is called before mocked http.Client's Get method. + interceptor.SetContext(r.Context()) + // make get request to external http service + resp, err := client.Get("https://example.com") + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + fmt.Println("BODY : ", body) + }) - // SetCtxHttpClient is called before mocked http.Client's Get method. - client.SetCtxHttpClient(r.Context()) - resp, err := client.Get("https://example.com") - if err != nil { - log.Fatal(err) - } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - fmt.Println("BODY : ", body) } func ExampleHttpClient_Do() { - r := &http.Request{} // Here, r is for demo. You should use your handler's request as r. - client := khttpclient.NewHttpClient(&http.Client{}) + // initialize a gorilla mux + r := mux.NewRouter() + // keploy config + port := "8080" + kApp := keploy.New(keploy.Config{ + App: keploy.AppConfig{ + Name: "Mux-Demo-app", + Port: port, + }, + Server: keploy.ServerConfig{ + URL: "http://localhost:8081/api", + }, + }) + // configure mux for integeration with keploy + kmux.Mux(kApp, r) + // configure http client with keploy's interceptor + interceptor := khttpclient.NewInterceptor(http.DefaultTransport) + client := http.Client{ + Transport: interceptor, + } + + r.HandleFunc("/mux/{category}/{params}",func (w http.ResponseWriter, r *http.Request) { + // SetCtxHttpClient is called before mocked http.Client's Get method. + interceptor.SetContext(r.Context()) + // make get request to external http service using http.Client.Do + req, err := http.NewRequestWithContext(r.Context(), "GET", "https://example.com", nil) + if err != nil { + log.Fatal(err) + } + resp, err := client.Do(req) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + fmt.Println("BODY : ", body) + }) - // SetCtxHttpClient is called before mocked http.Client's Do method. - client.SetCtxHttpClient(r.Context()) - req, err := http.NewRequestWithContext(r.Context(), "GET", "http://localhost:6060/getdocs?name=name&value=Ash", nil) - if err != nil { - log.Fatal("http client in webgo-v4 main.go") - } - resp, err := client.Do(req) - if err != nil { - log.Fatal(err) - } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - fmt.Println("BODY : ", body) } func ExampleHttpClient_Post() { - r := &http.Request{} // Here, r is for demo. You should use your handler's request as r. - client := khttpclient.NewHttpClient(&http.Client{}) - - // SetCtxHttpClient is called before mocked http.Client's Post method. - client.SetCtxHttpClient(r.Context()) - postBody, _ := json.Marshal(map[string]interface{}{ - "name": "Toby", - "age": 21, - "city": "New York", + // initialize a gorilla mux + r := mux.NewRouter() + // keploy config + port := "8080" + kApp := keploy.New(keploy.Config{ + App: keploy.AppConfig{ + Name: "Mux-Demo-app", + Port: port, + }, + Server: keploy.ServerConfig{ + URL: "http://localhost:8081/api", + }, + }) + // configure mux for integeration with keploy + kmux.Mux(kApp, r) + // configure http client with keploy's interceptor + interceptor := khttpclient.NewInterceptor(http.DefaultTransport) + client := http.Client{ + Transport: interceptor, + } + + r.HandleFunc("/mux/{category}/{params}",func (w http.ResponseWriter, r *http.Request) { + // SetCtxHttpClient is called before mocked http.Client's Get method. + interceptor.SetContext(r.Context()) + // make POST request to external http service using http.Client.POST method. + postBody, _ := json.Marshal(map[string]interface{}{ + "name": "Toby", + "age": 21, + "city": "New York", + }) + responseBody := bytes.NewBuffer(postBody) + resp, err := client.Post("https://example.com", "application/json", responseBody) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + fmt.Println("BODY : ", body) }) - responseBody := bytes.NewBuffer(postBody) - resp, err := http.Post("http://localhost:6060/createone", "application/json", responseBody) - - if err != nil { - log.Fatal(err) - } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - fmt.Println("BODY : ", body) } diff --git a/integrations/khttpclient/httpClient.go b/integrations/khttpclient/httpClient.go index c0ce7e3..dfae349 100644 --- a/integrations/khttpclient/httpClient.go +++ b/integrations/khttpclient/httpClient.go @@ -3,58 +3,20 @@ package khttpclient import ( "bytes" "context" + "strconv" + "go.keploy.io/server/pkg/models" - // "crypto/tls" "encoding/gob" - // "encoding/json" "errors" "fmt" "io" "io/ioutil" "net/http" - "net/url" - - // "strings" - "github.com/keploy/go-sdk/keploy" "go.uber.org/zap" ) -// NewHttpClient is used to embed the http.Client pointer in order to mock its methods. -// The purpose is to capture or replay its method's outputs according to "KEPLOY_SDK_MODE". -// It returns nil if client parameter is nil. -// -// Note: Always call SetCtxHttpClient method of *integrations.HttpClient before using http-client's method. This should be done so that -// request's context can be modified for storing or retrieving outputs of http methods. -func NewHttpClient(client *http.Client) *HttpClient { - if client == nil { - return nil - } - gob.Register(ReadCloser{}) - logger, _ := zap.NewProduction() - defer func() { - _ = logger.Sync() // flushes buffer, if any - }() - return &HttpClient{Client: client, log: logger} -} - -// HttpClient used to mock http.Client methods in order to store or retrieve outputs from request's context. -type HttpClient struct { - *http.Client - log *zap.Logger - ctx context.Context -} - -// SetCtxHttpClient is used to set integrations.HttpClient.ctx to http.Request.Context(). -// It should be called before calling any http.Client method so that, their -// outputs can be stored or retrieved from http.Request.Context() according to "KEPLOY_SDK_MODE". -// -// ctx parameter should be the context of http.Request. -func (cl *HttpClient) SetCtxHttpClient(ctx context.Context) { - cl.ctx = ctx -} - // ReadCloser is used so that gob could encode-decode http.Response. type ReadCloser struct { *bytes.Reader @@ -80,233 +42,79 @@ func (rc *ReadCloser) MarshalBinary() ([]byte, error) { return nil, nil } -func requestString(req *http.Request) string { - return fmt.Sprint("Method: ", req.Method, ", URL: ", req.URL, ", Proto: ", req.Proto, ", ProtoMajor: ", req.ProtoMajor, ", ProtoMinor: ", req.ProtoMinor, ", Header: ", req.Header, ", Body: ", req.Body, ", ContentLength: ", req.ContentLength, ", TransferEncoding: ", req.TransferEncoding, ", Close: ", req.Close, ", Host: ", req.Host, ", Form: ", req.Form, ", PostForm: ", req.PostForm, ", MultipartForm: ", req.MultipartForm, ", Trailer: ", req.Trailer, ", RemoteAddr: ", req.RemoteAddr, ", RequestURI: ", req.RequestURI, ", TLS: ", req.TLS, ", Response: ", req.Response, ", Context: ", req.Context()) -} - -// Do is used to override http.Client's Do method. More about this net/http method: https://pkg.go.dev/net/http#Client.Do. -func (cl *HttpClient) Do(req *http.Request) (*http.Response, error) { - if keploy.GetModeFromContext(cl.ctx) == keploy.MODE_OFF { - resp, err := cl.Client.Do(req) - return resp, err - } - var ( - err error - kerr *keploy.KError = &keploy.KError{} - resp *http.Response = &http.Response{} - ) - kctx, er := keploy.GetState(cl.ctx) - if er != nil { - return nil, er - } - mode := kctx.Mode - body := requestString(req) - meta := map[string]string{ - "name": "http-client", - "type": string(models.HttpClient), - "operation": "Do", - "Request": body, - } - switch mode { - case "test": - //don't call http.Client.Do method - case "capture": - resp, err = cl.Client.Do(req) - default: - return nil, errors.New("integrations: Not in a valid sdk mode") - } - - if err != nil { - kerr = &keploy.KError{Err: err} - } - - resp.Body = &ReadCloser{Body: resp.Body} - if resp.Request != nil { - resp.Request.Body = &ReadCloser{Body: resp.Request.Body} - } - mock, res := keploy.ProcessDep(cl.ctx, cl.log, meta, resp, kerr) - if mock { - var mockErr error - x := res[1].(*keploy.KError) - if x.Err != nil { - mockErr = x.Err - } - return resp, mockErr - } - return resp, err +type Interceptor struct{ + core http.RoundTripper + log *zap.Logger + kctx *keploy.Context } -// Get mocks the http.Client.Get method of net/http package. More about this net/http method: https://pkg.go.dev/net/http#Client.Get. -func (cl *HttpClient) Get(url string) (*http.Response, error) { - if keploy.GetModeFromContext(cl.ctx) == keploy.MODE_OFF { - resp, err := cl.Client.Get(url) - return resp, err - } - var ( - err error - kerr *keploy.KError = &keploy.KError{} - resp *http.Response = &http.Response{} - ) - kctx, er := keploy.GetState(cl.ctx) - if er != nil { - return nil, er - } - mode := kctx.Mode - meta := map[string]string{ - "name": "http-client", - "type": string(models.HttpClient), - "operation": "Get", - "URL": url, - } - switch mode { - case "test": - //don't call http.Client.Get method - case "capture": - resp, err = cl.Client.Get(url) - default: - return nil, errors.New("integrations: Not in a valid sdk mode") - } - - if err != nil { - kerr = &keploy.KError{Err: err} - } +// NewInterceptor constructs and returns the pointer to Interceptor. Interceptor is used +// to intercept every http client calls and store their responses into keploy context. +func NewInterceptor (core http.RoundTripper) *Interceptor{ + // Initialize a logger + logger, _ := zap.NewProduction() + defer func() { + _ = logger.Sync() // flushes buffer, if any + }() - resp.Body = &ReadCloser{Body: resp.Body} - if resp.Request != nil { - resp.Request.Body = &ReadCloser{Body: resp.Request.Body} - } + // Register the ReadCloser type to gob encoder + gob.Register(ReadCloser{}) - mock, res := keploy.ProcessDep(cl.ctx, cl.log, meta, resp, kerr) - if mock { - var mockErr error - x := res[1].(*keploy.KError) - if x.Err != nil { - mockErr = x.Err - } - return resp, mockErr + return &Interceptor{ + core: core, + log: logger, } - return resp, err } -// Post mocks the http.Client.Post method. More about this net/http method: https://pkg.go.dev/net/http#Client.Post. -func (cl *HttpClient) Post(url, contentType string, body io.Reader) (*http.Response, error) { - if keploy.GetModeFromContext(cl.ctx) == keploy.MODE_OFF { - resp, err := cl.Client.Post(url, contentType, body) - return resp, err - } - var ( - err error - kerr *keploy.KError = &keploy.KError{} - resp *http.Response = &http.Response{} - ) - kctx, er := keploy.GetState(cl.ctx) - if er != nil { - return nil, er - } - mode := kctx.Mode - meta := map[string]string{ - "name": "http-client", - "type": string(models.HttpClient), - "operation": "Post", - "URL": url, - "ContentType": contentType, - "body": fmt.Sprint(body), - } - switch mode { - case "test": - //don't call http.Client.Post method - case "capture": - resp, err = cl.Client.Post(url, contentType, body) - default: - return nil, errors.New("integrations: Not in a valid sdk mode") - } - - if err != nil { - kerr = &keploy.KError{Err: err} - } - - resp.Body = &ReadCloser{Body: resp.Body} - if resp.Request != nil { - resp.Request.Body = &ReadCloser{Body: resp.Request.Body} - } - - mock, res := keploy.ProcessDep(cl.ctx, cl.log, meta, resp, kerr) - if mock { - var mockErr error - x := res[1].(*keploy.KError) - if x.Err != nil { - mockErr = x.Err - } - return resp, mockErr - } - return resp, err +// SetContext is used to store the keploy context from request context into the Interceptor +// kctx field. +func (i *Interceptor) SetContext(requestContext context.Context) { + // ctx := context.TODO() + if kctx,err := keploy.GetState(requestContext); err==nil{ + i.kctx = kctx + i.log.Debug("http client keploy interceptor's context has been set to : ", zap.Any("keploy.Context ", i.kctx)) + } +} + +// setRequestContext returns the context with keploy context as value. It is called only +// when kctx field of Interceptor is not null. +func (i *Interceptor) setRequestContext(ctx context.Context) context.Context{ + rctx := context.WithValue(ctx, keploy.KCTX, i.kctx) + return rctx } -// Head mocks http.Client.Head method. More about this net/http method: https://pkg.go.dev/net/http#Client.Head. -func (cl *HttpClient) Head(url string) (*http.Response, error) { - if keploy.GetModeFromContext(cl.ctx) == keploy.MODE_OFF { - resp, err := cl.Client.Head(url) - return resp, err - } - var ( - err error - kerr *keploy.KError = &keploy.KError{} - resp *http.Response = &http.Response{} - ) - kctx, er := keploy.GetState(cl.ctx) - if er != nil { - return nil, er - } - mode := kctx.Mode - meta := map[string]string{ - "name": "http-client", - "type": string(models.HttpClient), - "operation": "Head", - "URL": url, - } - switch mode { - case "test": - //don't call http.Client.Head method - case "capture": - resp, err = cl.Client.Head(url) - default: - return nil, errors.New("integrations: Not in a valid sdk mode") - } - - if err != nil { - kerr = &keploy.KError{Err: err} - } - - resp.Body = &ReadCloser{Body: resp.Body} - if resp.Request != nil { - resp.Request.Body = &ReadCloser{Body: resp.Request.Body} - } - - mock, res := keploy.ProcessDep(cl.ctx, cl.log, meta, resp, kerr) - if mock { - var mockErr error - x := res[1].(*keploy.KError) - if x.Err != nil { - mockErr = x.Err +// RoundTrip is the custom method which is called before making http client calls to +// capture or replay the outputs of external http service. +func (i Interceptor) RoundTrip(r *http.Request) (*http.Response, error) { + + var reqBody []byte + if r.Body != nil { // Read + var err error + reqBody, err = ioutil.ReadAll(r.Body) + if err != nil { + // TODO right way to log errors + i.log.Error("Unable to read request body", zap.Error(err)) + return nil,err } - return resp, mockErr } - return resp, err -} + r.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody)) // Reset -// PostForm Method mocks net/http Client's method. About mocked method: https://pkg.go.dev/net/http#Client.PostForm. -func (cl *HttpClient) PostForm(url string, data url.Values) (*http.Response, error) { - if keploy.GetModeFromContext(cl.ctx) == keploy.MODE_OFF { - resp, err := cl.Client.PostForm(url, data) - return resp, err + // adds the keploy context stored in Interceptor's ctx field into the http client request context. + if i.kctx!=nil{ + ctx := i.setRequestContext(r.Context()) + r = r.WithContext(ctx) + } + + if keploy.GetModeFromContext(r.Context()) == keploy.MODE_OFF { + return i.core.RoundTrip(r) } var ( err error kerr *keploy.KError = &keploy.KError{} resp *http.Response = &http.Response{} ) - kctx, er := keploy.GetState(cl.ctx) + kctx, er := keploy.GetState(r.Context()) if er != nil { return nil, er } @@ -314,15 +122,20 @@ func (cl *HttpClient) PostForm(url string, data url.Values) (*http.Response, err meta := map[string]string{ "name": "http-client", "type": string(models.HttpClient), - "operation": "PostForm", - "URL": url, - "Data": fmt.Sprint(data), + "operation": r.Method, + "URL": r.URL.String(), + "Header": fmt.Sprint(r.Header), + "Body": string(reqBody), + "Proto": r.Proto, + "ProtoMajor":strconv.Itoa(r.ProtoMajor), + "ProtoMinor":strconv.Itoa(r.ProtoMinor), } switch mode { case "test": //don't call http.Client.PostForm method case "capture": - resp, err = cl.Client.PostForm(url, data) + // resp, err = cl.Client.PostForm(url, data) + resp, err = i.core.RoundTrip(r) default: return nil, errors.New("integrations: Not in a valid sdk mode") } @@ -336,7 +149,7 @@ func (cl *HttpClient) PostForm(url string, data url.Values) (*http.Response, err resp.Request.Body = &ReadCloser{Body: resp.Request.Body} } - mock, res := keploy.ProcessDep(cl.ctx, cl.log, meta, resp, kerr) + mock, res := keploy.ProcessDep(r.Context(), i.log, meta, resp, kerr) if mock { var mockErr error x := res[1].(*keploy.KError) @@ -346,4 +159,5 @@ func (cl *HttpClient) PostForm(url string, data url.Values) (*http.Response, err return resp, mockErr } return resp, err -} + + } \ No newline at end of file From 89b595e908f25af1b9bebbb05870d4b2894f01c8 Mon Sep 17 00:00:00 2001 From: re-Tick Date: Tue, 8 Mar 2022 17:39:52 +0530 Subject: [PATCH 06/10] docs: fixed typo in sql driver's example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 20daa9e..12fb733 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,7 @@ Here is an example - log.Fatal(err) } id := 47 - result, err := db.ExecContext(ctx, "UPDATE balances SET balance = balance + 10 WHERE user_id = ?", id) + result, err := db.ExecContext(r.Context(), "UPDATE balances SET balance = balance + 10 WHERE user_id = ?", id) if err != nil { log.Fatal(err) } From 6739da2360d427bc6f72573a0f5624e9c93b7064 Mon Sep 17 00:00:00 2001 From: re-Tick Date: Mon, 14 Mar 2022 11:00:49 +0530 Subject: [PATCH 07/10] fix(khttpClient): register types to gob and fixes UnmarshalBinay method of ReadCloser to decode gcp response in UnmarshalBinary method, the b byte array contents are copied to a temp byte array to prevent alteration in the given parameter b byte array --- integrations/khttpclient/httpClient.go | 77 ++++++++++++++------------ 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/integrations/khttpclient/httpClient.go b/integrations/khttpclient/httpClient.go index dfae349..8c8346a 100644 --- a/integrations/khttpclient/httpClient.go +++ b/integrations/khttpclient/httpClient.go @@ -3,18 +3,19 @@ package khttpclient import ( "bytes" "context" - "strconv" - - "go.keploy.io/server/pkg/models" - + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rsa" "encoding/gob" "errors" "fmt" + "github.com/keploy/go-sdk/keploy" + "go.keploy.io/server/pkg/models" + "go.uber.org/zap" "io" "io/ioutil" "net/http" - "github.com/keploy/go-sdk/keploy" - "go.uber.org/zap" + "strconv" ) // ReadCloser is used so that gob could encode-decode http.Response. @@ -28,7 +29,11 @@ func (rc ReadCloser) Close() error { } func (rc *ReadCloser) UnmarshalBinary(b []byte) error { - rc.Reader = bytes.NewReader(b) + + // copy the byte array elements into copyByteArr. See https://www.reddit.com/r/golang/comments/tddjdd/gob_is_appending_gibberish_to_my_object/ + copyByteArr := make([]byte, len(b)) + copy(copyByteArr, b) + rc.Reader = bytes.NewReader(copyByteArr) return nil } @@ -42,28 +47,29 @@ func (rc *ReadCloser) MarshalBinary() ([]byte, error) { return nil, nil } - -type Interceptor struct{ +type Interceptor struct { core http.RoundTripper - log *zap.Logger + log *zap.Logger kctx *keploy.Context } // NewInterceptor constructs and returns the pointer to Interceptor. Interceptor is used // to intercept every http client calls and store their responses into keploy context. -func NewInterceptor (core http.RoundTripper) *Interceptor{ +func NewInterceptor(core http.RoundTripper) *Interceptor { // Initialize a logger logger, _ := zap.NewProduction() defer func() { _ = logger.Sync() // flushes buffer, if any }() - // Register the ReadCloser type to gob encoder + // Register types to gob encoder gob.Register(ReadCloser{}) - + gob.Register(elliptic.P256()) + gob.Register(ecdsa.PublicKey{}) + gob.Register(rsa.PublicKey{}) return &Interceptor{ core: core, - log: logger, + log: logger, } } @@ -71,15 +77,15 @@ func NewInterceptor (core http.RoundTripper) *Interceptor{ // kctx field. func (i *Interceptor) SetContext(requestContext context.Context) { // ctx := context.TODO() - if kctx,err := keploy.GetState(requestContext); err==nil{ + if kctx, err := keploy.GetState(requestContext); err == nil { i.kctx = kctx i.log.Debug("http client keploy interceptor's context has been set to : ", zap.Any("keploy.Context ", i.kctx)) } -} +} -// setRequestContext returns the context with keploy context as value. It is called only -// when kctx field of Interceptor is not null. -func (i *Interceptor) setRequestContext(ctx context.Context) context.Context{ +// setRequestContext returns the context with keploy context as value. It is called only +// when kctx field of Interceptor is not null. +func (i *Interceptor) setRequestContext(ctx context.Context) context.Context { rctx := context.WithValue(ctx, keploy.KCTX, i.kctx) return rctx } @@ -87,7 +93,7 @@ func (i *Interceptor) setRequestContext(ctx context.Context) context.Context{ // RoundTrip is the custom method which is called before making http client calls to // capture or replay the outputs of external http service. func (i Interceptor) RoundTrip(r *http.Request) (*http.Response, error) { - + // Read the request body to store in meta var reqBody []byte if r.Body != nil { // Read var err error @@ -95,17 +101,17 @@ func (i Interceptor) RoundTrip(r *http.Request) (*http.Response, error) { if err != nil { // TODO right way to log errors i.log.Error("Unable to read request body", zap.Error(err)) - return nil,err + return nil, err } } r.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody)) // Reset // adds the keploy context stored in Interceptor's ctx field into the http client request context. - if i.kctx!=nil{ + if i.kctx != nil { ctx := i.setRequestContext(r.Context()) - r = r.WithContext(ctx) + r = r.WithContext(ctx) } - + if keploy.GetModeFromContext(r.Context()) == keploy.MODE_OFF { return i.core.RoundTrip(r) } @@ -120,21 +126,20 @@ func (i Interceptor) RoundTrip(r *http.Request) (*http.Response, error) { } mode := kctx.Mode meta := map[string]string{ - "name": "http-client", - "type": string(models.HttpClient), - "operation": r.Method, - "URL": r.URL.String(), - "Header": fmt.Sprint(r.Header), - "Body": string(reqBody), - "Proto": r.Proto, - "ProtoMajor":strconv.Itoa(r.ProtoMajor), - "ProtoMinor":strconv.Itoa(r.ProtoMinor), + "name": "http-client", + "type": string(models.HttpClient), + "operation": r.Method, + "URL": r.URL.String(), + "Header": fmt.Sprint(r.Header), + "Body": string(reqBody), + "Proto": r.Proto, + "ProtoMajor": strconv.Itoa(r.ProtoMajor), + "ProtoMinor": strconv.Itoa(r.ProtoMinor), } switch mode { case "test": - //don't call http.Client.PostForm method + //don't call i.core.RoundTrip method case "capture": - // resp, err = cl.Client.PostForm(url, data) resp, err = i.core.RoundTrip(r) default: return nil, errors.New("integrations: Not in a valid sdk mode") @@ -160,4 +165,4 @@ func (i Interceptor) RoundTrip(r *http.Request) (*http.Response, error) { } return resp, err - } \ No newline at end of file +} From 534dbfc6b47b54eab0a226d99721f6a5755e86b6 Mon Sep 17 00:00:00 2001 From: re-Tick Date: Mon, 14 Mar 2022 16:13:46 +0530 Subject: [PATCH 08/10] docs(gcp): adds an example in readme for a gcp compute --- README.md | 51 +++++++++++++++++++++++--- integrations/khttpclient/httpClient.go | 2 +- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 12fb733..371de46 100644 --- a/README.md +++ b/README.md @@ -335,11 +335,12 @@ func main(){ Transport: interceptor, } - r.HandleFunc("/mux/{category}/{params}",func (w http.ResponseWriter, r *http.Request) { - // SetCtxHttpClient is called before mocked http.Client's Get method. + r.HandleFunc("/mux/httpGet",func (w http.ResponseWriter, r *http.Request) { + // SetContext should always be called once in a http handler before http.Client's Get or Post or Head or PostForm method. + // Passing requests context as parameter. interceptor.SetContext(r.Context()) - // make get request to external http service - resp, err := client.Get("https://example.com") + // make Get, Post, etc request to external http service + resp, err := client.Get("https://example.com/getDocs") if err != nil { log.Fatal(err) } @@ -347,9 +348,49 @@ func main(){ body, err := io.ReadAll(resp.Body) fmt.Println("BODY : ", body) }) + r.HandleFunc("/mux/httpDo", func(w http.ResponseWriter, r *http.Request){ + putBody, _ := json.Marshal(map[string]interface{}{ + "name": "Ash", + "age": 21, + "city": "Palet town", + }) + PutBody := bytes.NewBuffer(putBody) + // Use handler request's context or SetContext before http.Client.Do method call + req,err := http.NewRequestWithContext(r.Context(), http.MethodPut, "https://example.com/updateDocs", PutBody) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + if err!=nil{ + log.Fatal(err) + } + resp,err := cl.Do(req) + if err!=nil{ + log.Fatal(err) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err!=nil{ + log.Fatal(err) + } + fmt.Println(" response Body: ", string(body)) + + }) + + // gcp compute API integeration + client, err := google.DefaultClient(c.Request.Context(), compute.ComputeScope) + if err != nil { + fmt.Println(err) + } + // add keploy interceptor to gcp httpClient + intercept := khttpclient.NewInterceptor(client.Transport) + client.Transport = intercept + + r.HandleFunc("/mux/gcpDo", func(w http.ResponseWriter, r *http.Request){ + computeService, err := compute.NewService(r.Context(), option.WithHTTPClient(client), option.WithCredentialsFile("/Users/abc/auth.json")) + zoneListCall := computeService.Zones.List(project) + zoneList, err := zoneListCall.Do() + }) } ``` -**Note**: ensure to add pass request context to all external requests like http requests, db calls, etc. +**Note**: ensure to pass request context to all external requests like http requests, db calls, etc. ### gRPC ```go diff --git a/integrations/khttpclient/httpClient.go b/integrations/khttpclient/httpClient.go index 8c8346a..49297d1 100644 --- a/integrations/khttpclient/httpClient.go +++ b/integrations/khttpclient/httpClient.go @@ -107,7 +107,7 @@ func (i Interceptor) RoundTrip(r *http.Request) (*http.Response, error) { r.Body = ioutil.NopCloser(bytes.NewBuffer(reqBody)) // Reset // adds the keploy context stored in Interceptor's ctx field into the http client request context. - if i.kctx != nil { + if _, err := keploy.GetState(r.Context()); err != nil && i.kctx != nil { ctx := i.setRequestContext(r.Context()) r = r.WithContext(ctx) } From d1796bc223ce401b8e8f5e5b3d457bc7d617f647 Mon Sep 17 00:00:00 2001 From: re-Tick Date: Thu, 17 Mar 2022 15:23:10 +0530 Subject: [PATCH 09/10] fix(ksql): run the mocked outputs of sql queries when database is down #70 --- integrations/ksql/driver.go | 34 ++++++++++++++++++++++++----- integrations/ksql/queryerContext.go | 31 ++++++++++++++++---------- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/integrations/ksql/driver.go b/integrations/ksql/driver.go index 21f945e..de1c1a4 100644 --- a/integrations/ksql/driver.go +++ b/integrations/ksql/driver.go @@ -17,9 +17,10 @@ import ( // Driver wraps the sql driver to overrides Open method of driver.Driver. type Driver struct { driver.Driver + // Mode string } -// Open returns wrapped driver.Conn in order to mock outputs of sql Querries. +// Open returns wrapped driver.Conn in order to mock outputs of sql Querries. // // dsn is a string in driver specific format used as connection URI. func (ksql *Driver) Open(dsn string) (driver.Conn, error) { @@ -28,6 +29,12 @@ func (ksql *Driver) Open(dsn string) (driver.Conn, error) { err error ) conn, err := ksql.Driver.Open(dsn) + + // if ksql.Mode == "test" { + if keploy.GetMode() == keploy.MODE_TEST { + err = nil + conn = Conn{} + } if err != nil { return nil, err } @@ -35,25 +42,42 @@ func (ksql *Driver) Open(dsn string) (driver.Conn, error) { defer func() { _ = logger.Sync() // flushes buffer, if any }() - res = Conn{conn: conn, log: logger} + res = Conn{conn: conn, log: logger} // mode: ksql.Mode + return res, err } -// Conn is used to override driver.Conn interface methods to mock the outputs of the querries. +// Conn is used to override driver.Conn interface methods to mock the outputs of the querries. type Conn struct { + // mode string conn driver.Conn - log *zap.Logger + log *zap.Logger } func (c Conn) Begin() (driver.Tx, error) { + // if c.mode == "test" { + if keploy.GetMode() == keploy.MODE_TEST { + + return Tx{}, nil + } return c.conn.Begin() } func (c Conn) Close() error { + // if c.mode == "test" { + if keploy.GetMode() == keploy.MODE_TEST { + + return nil + } return c.conn.Close() } func (c Conn) Prepare(query string) (driver.Stmt, error) { + // if c.mode == "test" { + if keploy.GetMode() == keploy.MODE_TEST { + + return Stmt{}, nil + } return c.conn.Prepare(query) } @@ -65,7 +89,7 @@ func (c Conn) OpenConnector(name string) (driver.Connector, error) { return nil, errors.New("mocked Driver.Conn var not implements DriverContext interface") } -// Ping is the mocked method of sql/driver's Ping. +// Ping is the mocked method of sql/driver's Ping. func (c Conn) Ping(ctx context.Context) error { pc, ok := c.conn.(driver.Pinger) if !ok { diff --git a/integrations/ksql/queryerContext.go b/integrations/ksql/queryerContext.go index aff343c..2fd1631 100644 --- a/integrations/ksql/queryerContext.go +++ b/integrations/ksql/queryerContext.go @@ -20,8 +20,9 @@ type Rows struct { driver.Rows query string args []driver.NamedValue - log *zap.Logger + log *zap.Logger } + // Columns mocks the output of Columns method of your SQL driver. func (r Rows) Columns() []string { if keploy.GetModeFromContext(r.ctx) == keploy.MODE_OFF { @@ -49,13 +50,14 @@ func (r Rows) Columns() []string { "type": string(models.SqlDB), "operation": "QueryContext.Columns", } - + mock, _ := keploy.ProcessDep(r.ctx, r.log, meta, output) if mock { return *output } return *output } + // Close mocks the output of Close method of your SQL driver. func (r Rows) Close() error { if keploy.GetModeFromContext(r.ctx) == keploy.MODE_OFF { @@ -72,7 +74,11 @@ func (r Rows) Close() error { mode := kctx.Mode switch mode { case "test": - // don't run + // don't run actual rows.Close + // ignore the rows.Close which is not done manually. + if kctx.Deps == nil || len(kctx.Deps) == 0 || len(kctx.Deps[0].Data) != 1 { + return nil + } case "capture": err = r.Rows.Close() default: @@ -86,7 +92,7 @@ func (r Rows) Close() error { if err != nil { kerr = &keploy.KError{Err: err} } - + mock, res := keploy.ProcessDep(r.ctx, r.log, meta, kerr) if mock { var mockErr error @@ -142,6 +148,7 @@ func (d *Value) GobEncode() ([]byte, error) { return json.Marshal(res) } + // GobDecode decodes Values using gob Decoder. func (d *Value) GobDecode(b []byte) error { var res *[][]byte = &[][]byte{} @@ -218,7 +225,7 @@ func (r Rows) Next(dest []driver.Value) error { var ( err error kerr *keploy.KError = &keploy.KError{} - output *Value = &Value{Value: dest} + output *Value = &Value{Value: dest} ) kctx, er := keploy.GetState(r.ctx) if er != nil { @@ -269,13 +276,13 @@ func (c Conn) QueryContext(ctx context.Context, query string, args []driver.Name return queryerContext.QueryContext(ctx, query, args) } var ( - err error - kerr *keploy.KError = &keploy.KError{} - driverRows *Rows = &Rows{ - ctx : ctx, - query : query, - args : args, - log : c.log, + err error + kerr *keploy.KError = &keploy.KError{} + driverRows *Rows = &Rows{ + ctx: ctx, + query: query, + args: args, + log: c.log, } rows driver.Rows ) From 01b13c7b8989e158a64921bb215c9d4f914ad0a2 Mon Sep 17 00:00:00 2001 From: re-Tick Date: Fri, 18 Mar 2022 16:08:24 +0530 Subject: [PATCH 10/10] fix(ksql): keploy sql driver methods returns the sql/driver errors instead of KError.Err This is done because sql package compares errors by using == or errors.Is so, errors should be same instances. --- go.mod | 10 +++++----- go.sum | 21 +++++++++++++++++++++ integrations/ksql/connBeginTx.go | 4 ++++ integrations/ksql/connPrepareContext.go | 10 +++++++--- integrations/ksql/driver.go | 1 + integrations/ksql/error.go | 21 +++++++++++++++++++++ integrations/ksql/execerContext.go | 10 ++++++---- integrations/ksql/queryerContext.go | 3 +++ 8 files changed, 68 insertions(+), 12 deletions(-) create mode 100644 integrations/ksql/error.go diff --git a/go.mod b/go.mod index 05406c7..03a5190 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/bnkamalesh/webgo/v6 v6.2.2 github.com/creasty/defaults v1.5.2 github.com/gin-gonic/gin v1.7.7 - github.com/go-playground/validator/v10 v10.4.1 + github.com/go-playground/validator/v10 v10.10.1 github.com/labstack/echo/v4 v4.6.1 go.mongodb.org/mongo-driver v1.8.1 go.uber.org/zap v1.19.1 @@ -25,8 +25,8 @@ require ( github.com/agnivade/levenshtein v1.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-chi/render v1.0.1 // indirect - github.com/go-playground/locales v0.13.0 // indirect - github.com/go-playground/universal-translator v0.17.0 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-stack/stack v1.8.0 // indirect github.com/golang/protobuf v1.4.3 // indirect github.com/golang/snappy v0.0.1 // indirect @@ -36,7 +36,7 @@ require ( github.com/json-iterator/go v1.1.9 // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/labstack/gommon v0.3.0 // indirect - github.com/leodido/go-urn v1.2.0 // indirect + github.com/leodido/go-urn v1.2.1 // indirect github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect @@ -52,7 +52,7 @@ require ( github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect + golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect diff --git a/go.sum b/go.sum index 6492a2a..f94b0a9 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,7 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creasty/defaults v1.5.2 h1:/VfB6uxpyp6h0fr7SPp7n8WJBoV8jfxQXPCnkVSjyls= github.com/creasty/defaults v1.5.2/go.mod h1:FPZ+Y0WNrbqOVw+c6av63eyHUAl6pMHZwqLPvXUZGfY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -57,10 +58,16 @@ github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBY github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig= +github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= @@ -110,15 +117,20 @@ github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQ github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.6.1 h1:OMVsrnNFzYlGSdaiYGHbgWQnr+JM7NG+B9suCPie14M= github.com/labstack/echo/v4 v4.6.1/go.mod h1:RnjgMWNDB9g/HucVWhQYNQP9PvbYf6adqftqryo7s9k= github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc= github.com/matryer/moq v0.2.3/go.mod h1:9RtPYjTnH1bSBIkpvtHkFN7nbWAnO7oRpdJkEIn6UtE= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -136,6 +148,7 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -143,6 +156,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= @@ -200,6 +215,8 @@ golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -219,6 +236,7 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -246,6 +264,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -303,6 +322,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/integrations/ksql/connBeginTx.go b/integrations/ksql/connBeginTx.go index abf2eb3..d38c0e3 100644 --- a/integrations/ksql/connBeginTx.go +++ b/integrations/ksql/connBeginTx.go @@ -55,10 +55,12 @@ func (t Tx) Commit() error { if x.Err != nil { mockErr = x.Err } + mockErr = convertKError(mockErr) return mockErr } return err } + // Rollback mocks the outputs of Rollback method present driver's Tx interface. func (t Tx) Rollback() error { if keploy.GetModeFromContext(t.ctx) == keploy.MODE_OFF { @@ -96,6 +98,7 @@ func (t Tx) Rollback() error { if x.Err != nil { mockErr = x.Err } + mockErr = convertKError(mockErr) return mockErr } return err @@ -149,6 +152,7 @@ func (c Conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, er if x.Err != nil { mockErr = x.Err } + mockErr = convertKError(mockErr) return drTx, mockErr } return drTx, err diff --git a/integrations/ksql/connPrepareContext.go b/integrations/ksql/connPrepareContext.go index de2b990..b551ab8 100644 --- a/integrations/ksql/connPrepareContext.go +++ b/integrations/ksql/connPrepareContext.go @@ -16,7 +16,7 @@ type Stmt struct { driver.Stmt ctx context.Context query string - log *zap.Logger + log *zap.Logger } func (s Stmt) Exec(args []driver.Value) (driver.Result, error) { @@ -67,6 +67,7 @@ func (s Stmt) Exec(args []driver.Value) (driver.Result, error) { if x.Err != nil { mockErr = x.Err } + mockErr = convertKError(mockErr) return drResult, mockErr } return result, err @@ -78,7 +79,7 @@ func (s Stmt) Query(args []driver.Value) (driver.Rows, error) { var ( err error kerr *keploy.KError = &keploy.KError{} - drRows *Rows = &Rows{ + drRows *Rows = &Rows{ ctx: s.ctx, query: s.query, } @@ -115,6 +116,7 @@ func (s Stmt) Query(args []driver.Value) (driver.Rows, error) { if x.Err != nil { mockErr = x.Err } + mockErr = convertKError(mockErr) return drRows, mockErr } return drRows, err @@ -188,6 +190,7 @@ func (s Stmt) Close() error { if x.Err != nil { mockErr = x.Err } + mockErr = convertKError(mockErr) return mockErr } return err @@ -205,7 +208,7 @@ func (c Conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, er var ( err error kerr *keploy.KError = &keploy.KError{} - drStmt *Stmt = &Stmt{ + drStmt *Stmt = &Stmt{ log: c.log, ctx: ctx, query: query, @@ -242,6 +245,7 @@ func (c Conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, er if x.Err != nil { mockErr = x.Err } + mockErr = convertKError(mockErr) return drStmt, mockErr } return drStmt, err diff --git a/integrations/ksql/driver.go b/integrations/ksql/driver.go index de1c1a4..793c59c 100644 --- a/integrations/ksql/driver.go +++ b/integrations/ksql/driver.go @@ -131,6 +131,7 @@ func (c Conn) Ping(ctx context.Context) error { if x.Err != nil { mockErr = x.Err } + mockErr = convertKError(mockErr) return mockErr } return err diff --git a/integrations/ksql/error.go b/integrations/ksql/error.go new file mode 100644 index 0000000..277b3e7 --- /dev/null +++ b/integrations/ksql/error.go @@ -0,0 +1,21 @@ +package ksql + +import "database/sql/driver" + +func convertKError(err error) error { + if err == nil { + return nil + } + // return the sql/driver error which is matching the parameter error string + str := err.Error() + switch str { + case driver.ErrBadConn.Error(): + return driver.ErrBadConn + case driver.ErrRemoveArgument.Error(): + return driver.ErrRemoveArgument + case driver.ErrSkip.Error(): + return driver.ErrSkip + default: + return err + } +} diff --git a/integrations/ksql/execerContext.go b/integrations/ksql/execerContext.go index de6e036..473477d 100644 --- a/integrations/ksql/execerContext.go +++ b/integrations/ksql/execerContext.go @@ -19,6 +19,7 @@ type Result struct { RowsAff int64 RError string } + func (r Result) LastInsertId() (int64, error) { return r.LastInserted, errors.New(r.LError) } @@ -38,9 +39,9 @@ func (c Conn) ExecContext(ctx context.Context, query string, args []driver.Named return execerContext.ExecContext(ctx, query, args) } var ( - err error - kerr *keploy.KError = &keploy.KError{} - result driver.Result + err error + kerr *keploy.KError = &keploy.KError{} + result driver.Result driverResult *Result = &Result{} ) kctx, er := keploy.GetState(ctx) @@ -87,7 +88,8 @@ func (c Conn) ExecContext(ctx context.Context, query string, args []driver.Named if x.Err != nil { mockErr = x.Err } + mockErr = convertKError(mockErr) return driverResult, mockErr } return result, err -} \ No newline at end of file +} diff --git a/integrations/ksql/queryerContext.go b/integrations/ksql/queryerContext.go index 2fd1631..87f80e9 100644 --- a/integrations/ksql/queryerContext.go +++ b/integrations/ksql/queryerContext.go @@ -100,6 +100,7 @@ func (r Rows) Close() error { if x.Err != nil { mockErr = x.Err } + mockErr = convertKError(mockErr) return mockErr } return err @@ -261,6 +262,7 @@ func (r Rows) Next(dest []driver.Value) error { dest[i] = output.Value[i] } } + mockErr = convertKError(mockErr) return mockErr } return err @@ -317,6 +319,7 @@ func (c Conn) QueryContext(ctx context.Context, query string, args []driver.Name if x.Err != nil { mockErr = x.Err } + mockErr = convertKError(mockErr) return driverRows, mockErr } return driverRows, err