diff --git a/go.mod b/go.mod index 07f68f811..d0b546bb8 100755 --- a/go.mod +++ b/go.mod @@ -68,7 +68,6 @@ require ( github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/term v0.18.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect k8s.io/kube-openapi v0.0.0-20230601164746-7562a1006961 // indirect sigs.k8s.io/yaml v1.4.0 // indirect @@ -123,6 +122,7 @@ require ( github.com/xdg-go/stringprep v1.0.4 github.com/yudai/gojsondiff v1.0.0 golang.org/x/sync v0.6.0 + golang.org/x/term v0.18.0 sigs.k8s.io/kustomize/kyaml v0.16.0 ) diff --git a/pkg/core/app/app.go b/pkg/core/app/app.go index d31ce0dcf..60e588c02 100644 --- a/pkg/core/app/app.go +++ b/pkg/core/app/app.go @@ -93,7 +93,7 @@ func (a *App) ContainerIPv4Addr() string { func (a *App) SetupDocker() error { var err error - cont, net, err := parseDockerCmd(a.cmd) + cont, net, err := ParseDockerCmd(a.cmd) if err != nil { utils.LogError(a.logger, err, "failed to parse container name from given docker command", zap.String("cmd", a.cmd)) return err diff --git a/pkg/core/app/util.go b/pkg/core/app/util.go index 4b568fc1d..cb9fb499a 100644 --- a/pkg/core/app/util.go +++ b/pkg/core/app/util.go @@ -47,7 +47,7 @@ func modifyDockerComposeCommand(appCmd, newComposeFile string) string { return fmt.Sprintf("%s -f %s", appCmd, newComposeFile) } -func parseDockerCmd(cmd string) (string, string, error) { +func ParseDockerCmd(cmd string) (string, string, error) { // Regular expression patterns containerNamePattern := `--name\s+([^\s]+)` networkNamePattern := `(--network|--net)\s+([^\s]+)` diff --git a/pkg/core/proxy/integrations/http/match.go b/pkg/core/proxy/integrations/http/match.go index 1624d4e8d..44ec58c2a 100644 --- a/pkg/core/proxy/integrations/http/match.go +++ b/pkg/core/proxy/integrations/http/match.go @@ -6,7 +6,6 @@ import ( "net/url" "github.com/agnivade/levenshtein" - "github.com/cloudflare/cfssl/log" "go.keploy.io/server/v2/pkg/core/proxy/integrations" "go.keploy.io/server/v2/pkg/core/proxy/integrations/util" "go.keploy.io/server/v2/pkg/models" @@ -144,7 +143,7 @@ func findBinaryMatch(mocks []*models.Mock, reqBuff []byte) int { shingles2 := util.CreateShingles(reqBuff, k) similarity := util.JaccardSimilarity(shingles1, shingles2) - log.Debugf("Jaccard Similarity:%f\n", similarity) + // log.Debugf("Jaccard Similarity:%f\n", similarity) if mxSim < similarity { mxSim = similarity diff --git a/pkg/platform/yaml/mockdb/db.go b/pkg/platform/yaml/mockdb/db.go index 087d2ef11..f3eb677c8 100644 --- a/pkg/platform/yaml/mockdb/db.go +++ b/pkg/platform/yaml/mockdb/db.go @@ -285,7 +285,6 @@ func (ys *MockYaml) filterByTimeStamp(_ context.Context, m []*models.Mock, after for _, mock := range m { if mock.Version != "api.keploy.io/v1beta1" && mock.Version != "api.keploy.io/v1beta2" { isNonKeploy = true - continue } if mock.Spec.ReqTimestampMock == (time.Time{}) || mock.Spec.ResTimestampMock == (time.Time{}) { logger.Debug("request or response timestamp of mock is missing") @@ -303,7 +302,7 @@ func (ys *MockYaml) filterByTimeStamp(_ context.Context, m []*models.Mock, after unfilteredMocks = append(unfilteredMocks, mock) } if isNonKeploy { - ys.Logger.Warn("Few mocks in the mock File are not recorded by keploy ignoring them") + ys.Logger.Debug("Few mocks in the mock File are not recorded by keploy ignoring them") } return filteredMocks, unfilteredMocks } diff --git a/pkg/service/replay/replay.go b/pkg/service/replay/replay.go index dee076e56..b14bc5f1a 100644 --- a/pkg/service/replay/replay.go +++ b/pkg/service/replay/replay.go @@ -26,7 +26,15 @@ var totalTests int var totalTestPassed int var totalTestFailed int -type replayer struct { +// emulator contains the struct instance that implements RequestEmulator interface. This is done for +// attaching the objects dynamically as plugins. +var emulator RequestEmulator + +func SetTestUtilInstance(instance RequestEmulator) { + emulator = instance +} + +type Replayer struct { logger *zap.Logger testDB TestDB mockDB MockDB @@ -37,7 +45,12 @@ type replayer struct { } func NewReplayer(logger *zap.Logger, testDB TestDB, mockDB MockDB, reportDB ReportDB, telemetry Telemetry, instrumentation Instrumentation, config config.Config) Service { - return &replayer{ + // set the request emulator for simulating test case requests, if not set + if emulator == nil { + SetTestUtilInstance(NewTestUtils(config.Test.APITimeout, logger)) + } + + return &Replayer{ logger: logger, testDB: testDB, mockDB: mockDB, @@ -48,7 +61,7 @@ func NewReplayer(logger *zap.Logger, testDB TestDB, mockDB MockDB, reportDB Repo } } -func (r *replayer) Start(ctx context.Context) error { +func (r *Replayer) Start(ctx context.Context) error { // creating error group to manage proper shutdown of all the go routines and to propagate the error to the caller g, ctx := errgroup.WithContext(ctx) @@ -152,7 +165,7 @@ func (r *replayer) Start(ctx context.Context) error { return nil } -func (r *replayer) BootReplay(ctx context.Context) (string, uint64, context.CancelFunc, error) { +func (r *Replayer) BootReplay(ctx context.Context) (string, uint64, context.CancelFunc, error) { var cancel context.CancelFunc @@ -194,11 +207,11 @@ func (r *replayer) BootReplay(ctx context.Context) (string, uint64, context.Canc return newTestRunID, appID, cancel, nil } -func (r *replayer) GetAllTestSetIDs(ctx context.Context) ([]string, error) { +func (r *Replayer) GetAllTestSetIDs(ctx context.Context) ([]string, error) { return r.testDB.GetAllTestSetIDs(ctx) } -func (r *replayer) RunTestSet(ctx context.Context, testSetID string, testRunID string, appID uint64, serveTest bool) (models.TestSetStatus, error) { +func (r *Replayer) RunTestSet(ctx context.Context, testSetID string, testRunID string, appID uint64, serveTest bool) (models.TestSetStatus, error) { // creating error group to manage proper shutdown of all the go routines and to propagate the error to the caller runTestSetErrGrp, runTestSetCtx := errgroup.WithContext(ctx) @@ -376,7 +389,26 @@ func (r *replayer) RunTestSet(ctx context.Context, testSetID string, testRunID s } started := time.Now().UTC() - resp, loopErr := r.SimulateRequest(runTestSetCtx, appID, testCase, testSetID) + + cmdType := utils.FindDockerCmd(r.config.Command) + + if cmdType == utils.Docker || cmdType == utils.DockerCompose { + + userIP, err := r.instrumentation.GetAppIP(ctx, appID) + if err != nil { + utils.LogError(r.logger, err, "failed to get the app ip") + break + } + + testCase.HTTPReq.URL, err = replaceHostToIP(testCase.HTTPReq.URL, userIP) + if err != nil { + utils.LogError(r.logger, err, "failed to replace host to docker container's IP") + break + } + r.logger.Debug("", zap.Any("replaced URL in case of docker env", testCase.HTTPReq.URL)) + } + + resp, loopErr := emulator.SimulateRequest(runTestSetCtx, appID, testCase, testSetID) if loopErr != nil { utils.LogError(r.logger, err, "failed to simulate request") break @@ -531,7 +563,7 @@ func (r *replayer) RunTestSet(ctx context.Context, testSetID string, testRunID s return testSetStatus, nil } -func (r *replayer) GetTestSetStatus(ctx context.Context, testRunID string, testSetID string) (models.TestSetStatus, error) { +func (r *Replayer) GetTestSetStatus(ctx context.Context, testRunID string, testSetID string) (models.TestSetStatus, error) { testReport, err := r.reportDB.GetReport(ctx, testRunID, testSetID) if err != nil { return models.TestSetStatusFailed, fmt.Errorf("failed to get report: %w", err) @@ -543,36 +575,7 @@ func (r *replayer) GetTestSetStatus(ctx context.Context, testRunID string, testS return status, nil } -func (r *replayer) SimulateRequest(ctx context.Context, appID uint64, tc *models.TestCase, testSetID string) (*models.HTTPResp, error) { - switch tc.Kind { - case models.HTTP: - r.logger.Debug("Before simulating the request", zap.Any("Test case", tc)) - cmdType := utils.FindDockerCmd(r.config.Command) - if cmdType == utils.Docker || cmdType == utils.DockerCompose { - var err error - - userIP, err := r.instrumentation.GetAppIP(ctx, appID) - if err != nil { - utils.LogError(r.logger, err, "failed to get the app ip") - return nil, err - } - - tc.HTTPReq.URL, err = replaceHostToIP(tc.HTTPReq.URL, userIP) - if err != nil { - utils.LogError(r.logger, err, "failed to replace host to docker container's IP") - } - r.logger.Debug("", zap.Any("replaced URL in case of docker env", tc.HTTPReq.URL)) - } - r.logger.Debug(fmt.Sprintf("the url of the testcase: %v", tc.HTTPReq.URL)) - resp, err := pkg.SimulateHTTP(ctx, *tc, testSetID, r.logger, r.config.Test.APITimeout) - r.logger.Debug("After simulating the request", zap.Any("test case id", tc.Name)) - r.logger.Debug("After GetResp of the request", zap.Any("test case id", tc.Name)) - return resp, err - } - return nil, nil -} - -func (r *replayer) compareResp(tc *models.TestCase, actualResponse *models.HTTPResp, testSetID string) (bool, *models.Result) { +func (r *Replayer) compareResp(tc *models.TestCase, actualResponse *models.HTTPResp, testSetID string) (bool, *models.Result) { noiseConfig := r.config.Test.GlobalNoise.Global if tsNoise, ok := r.config.Test.GlobalNoise.Testsets[testSetID]; ok { @@ -581,7 +584,7 @@ func (r *replayer) compareResp(tc *models.TestCase, actualResponse *models.HTTPR return match(tc, actualResponse, noiseConfig, r.config.Test.IgnoreOrdering, r.logger) } -func (r *replayer) printSummary(ctx context.Context, testRunResult bool) { +func (r *Replayer) printSummary(ctx context.Context, testRunResult bool) { if totalTests > 0 { testSuiteNames := make([]string, 0, len(completeTestReport)) for testSuiteName := range completeTestReport { @@ -644,11 +647,11 @@ func (r *replayer) printSummary(ctx context.Context, testRunResult bool) { } } -func (r *replayer) RunApplication(ctx context.Context, appID uint64, opts models.RunOptions) models.AppError { +func (r *Replayer) RunApplication(ctx context.Context, appID uint64, opts models.RunOptions) models.AppError { return r.instrumentation.Run(ctx, appID, opts) } -func (r *replayer) ProvideMocks(ctx context.Context) error { +func (r *Replayer) ProvideMocks(ctx context.Context) error { var stopReason string var hookCancel context.CancelFunc defer func() { diff --git a/pkg/service/replay/service.go b/pkg/service/replay/service.go index 897819eaf..079acb500 100644 --- a/pkg/service/replay/service.go +++ b/pkg/service/replay/service.go @@ -57,3 +57,9 @@ type Telemetry interface { TestRun(success int, failure int, testSets int, runStatus string) MockTestRun(utilizedMocks int) } + +// RequestEmulator is used to simulate the API requests to the user API. The requests are read from +// the recorded test case of the user app. +type RequestEmulator interface { + SimulateRequest(ctx context.Context, appID uint64, tc *models.TestCase, testSetID string) (*models.HTTPResp, error) +} diff --git a/pkg/service/replay/utils.go b/pkg/service/replay/utils.go index 133c4513e..fc98c56c1 100644 --- a/pkg/service/replay/utils.go +++ b/pkg/service/replay/utils.go @@ -1,11 +1,15 @@ package replay import ( + "context" "fmt" "net/url" "strings" "go.keploy.io/server/v2/config" + "go.keploy.io/server/v2/pkg" + "go.keploy.io/server/v2/pkg/models" + "go.uber.org/zap" ) type TestReportVerdict struct { @@ -44,3 +48,28 @@ func replaceHostToIP(currentURL string, ipAddress string) (string, error) { // Return the modified URL return parsedURL.String(), nil } + +type testUtils struct { + logger *zap.Logger + apiTimeout uint64 +} + +func NewTestUtils(apiTimeout uint64, logger *zap.Logger) RequestEmulator { + return &testUtils{ + logger: logger, + apiTimeout: apiTimeout, + } +} + +func (t *testUtils) SimulateRequest(ctx context.Context, _ uint64, tc *models.TestCase, testSetID string) (*models.HTTPResp, error) { + switch tc.Kind { + case models.HTTP: + t.logger.Debug("Before simulating the request", zap.Any("Test case", tc)) + t.logger.Debug(fmt.Sprintf("the url of the testcase: %v", tc.HTTPReq.URL)) + resp, err := pkg.SimulateHTTP(ctx, *tc, testSetID, t.logger, t.apiTimeout) + t.logger.Debug("After simulating the request", zap.Any("test case id", tc.Name)) + t.logger.Debug("After GetResp of the request", zap.Any("test case id", tc.Name)) + return resp, err + } + return nil, nil +}