Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scenario Values & Reading from Response #178

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
72 changes: 71 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,10 @@ Mock definition:
"not_started (default state)",
"another_state_name"
],
"newState": "new_stat_neme"
"newState": "new_stat_neme",
"values": {
"value_name": "string (value value)"
}
},
"proxyBaseURL": "string (original URL endpoint)",
"delay": "string (response delay in s,ms)",
Expand Down Expand Up @@ -284,6 +287,71 @@ You can extract information from the request body too, using a dot notation path
- request.body."*key*" (support for `application/json`, `application/xml` and `application/x-www-form-urlencoded` requests)
- request.body."*deep*"."*key*" (support for `application/json`, `application/xml` requests)

**Response data:** you can reference any value you have assigned to the response
in order to mirror the same value in the Callback or as a Scenario value (see below).

- response.headers."*key*"
- response.cookies."*key*"
- response.body."*key*"

**Scenario values:**
You can store a value in the scenario at one state,
and then reference it later. For instance, if you need a random
value returned with the first request, and then referenced in a
future one. For example:

Mock definition 1 response and control blocks (state: not_started)
The value generated by `fake.SimplePassword` is added to the response,
but is also stored for future use in the `scenario.random` variable.

```
response:
statusCode: 200
headers:
Content-Type:
- application/json
body: >-
{
"random": "{{fake.SimplePassword}}"
}

control:
scenario:
name: test_scene
values:
var1: '001'
var2: '002'
random: "{{response.body.random}}"
newState: loaded
requiredState:
- 'not_started'
```

Mock definition 2, response and control blocks (state: loaded)
This is the second state in the series, and here the response
can return the same random value generated in state 1, by
referencing `scenario.random`

```
response:
statusCode: 200
headers:
Content-Type:
- application/json
body: >-
{
"value_1": "{{scenario.var1}}",
"random_from_scenario": "{{scenario.random}}"
}

control:
scenario:
name: test_scene
newState: not_started
requiredState:
- 'loaded'
```

Quick overview of the path syntax available to extract values form the request: [https://github.com/tidwall/gjson#path-syntax](https://github.com/tidwall/gjson#path-syntax)

You can also use "regex" and "concat" commands to complement GJson query:
Expand Down Expand Up @@ -631,6 +699,8 @@ You can always disable this behavior adding the following flag `-server-statisti
- Improved logging with levels thanks to [@jcdietrich](https://github.com/jcdietrich) [@jdietrich-tc](https://github.com/jdietrich-tc)
- Support for Regular Expressions for QueryStringParameters [@jcdietrich](https://github.com/jcdietrich) [@jdietrich-tc](https://github.com/jdietrich-tc)
- Support for URI and Description tags [@jcdietrich](https://github.com/jcdietrich) [@jdietrich-tc](https://github.com/jdietrich-tc)
- Support for response.* tags [@jcdietrich](https://github.com/jcdietrich) [@jdietrich-tc](https://github.com/jdietrich-tc)
- Support for storing and recalling scenario values (scenario.* tags) [@jcdietrich](https://github.com/jcdietrich) [@jdietrich-tc](https://github.com/jdietrich-tc)

### Contributing

Expand Down
9 changes: 8 additions & 1 deletion internal/config/parser/json_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package parser
import (
"encoding/json"
"github.com/jmartin82/mmock/v3/pkg/mock"
"github.com/jmartin82/mmock/v3/internal/config/logger"
"path/filepath"
"strings"
)

var log = logger.Log

// JSONReader struct created to read json config files
type JSONReader struct {
}
Expand All @@ -20,8 +23,12 @@ func (jp JSONReader) CanParse(filename string) bool {
func (jp JSONReader) Parse(buf []byte) (mock.Definition, error) {
m := mock.Definition{}
err := json.Unmarshal(buf, &m)

if err != nil {
return mock.Definition{}, err
log.Errorf("JSONReader Parse error: %v", err)
log.Errorf("JSONReader attempted to parse: %v", string(buf))
return mock.Definition{}, err
}

return m, nil
}
419 changes: 419 additions & 0 deletions internal/console/bindata.go

Large diffs are not rendered by default.

14 changes: 12 additions & 2 deletions internal/server/dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,17 @@ func (di *Dispatcher) ServeHTTP(w http.ResponseWriter, req *http.Request) {
//set new scenario
if mock.Control.Scenario.NewState != "" {
statistics.TrackScenarioFeature()
di.Scenario.SetState(mock.Control.Scenario.Name, mock.Control.Scenario.NewState)

di.Scenario.SetState(
mock.Control.Scenario.Name,
mock.Control.Scenario.NewState)

if len(mock.Control.Scenario.Values) != 0 {
log.Debugf("Setting Scenario values: %v", mock.Control.Scenario.Values)
di.Scenario.SetStateValues(
mock.Control.Scenario.Name,
mock.Control.Scenario.Values)
}
}

if mock.Control.WebHookURL != "" {
Expand Down Expand Up @@ -148,7 +158,7 @@ func (di *Dispatcher) getMatchingResult(request *mock.Request) (*mock.Definition
statistics.TrackProxyFeature()
response = getProxyResponse(request, mock)
} else {
di.Evaluator.Eval(request, mock)
di.Evaluator.Eval(request, mock, di.Scenario)
response = &mock.Response
}

Expand Down
1 change: 1 addition & 0 deletions pkg/match/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ func (mm Request) matchScenarioState(scenario *mock.Scenario) bool {
currentState := mm.scenario.GetState(scenario.Name)
for _, r := range scenario.RequiredState {
if strings.ToLower(r) == currentState {
log.Debugf("Scenario %v has matched %v state.", scenario.Name, currentState)
return true
}
}
Expand Down
50 changes: 49 additions & 1 deletion pkg/match/scenario_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,34 @@ import (
type ScenearioStorer interface {
SetState(name, status string)
GetState(name string) string
SetStateValues(name string, values map[string]string)
SetStateValue(name, valueName, value string)
GetStateValues(name string) map[string]string
GetStateValue(name, valueName string) (string, bool)
Reset(name string) bool
ResetAll()
SetPaused(newstate bool)
GetPaused() bool
List() string
}

type ScenearioStore struct {
}

func NewInMemoryScenarioStore() *InMemoryScenarioStore {
status := make(map[string]string)
return &InMemoryScenarioStore{status: status}
values := make(map[string]map[string]string)
return &InMemoryScenarioStore{
status: status,
values: values,
}
}

type InMemoryScenarioStore struct {
status map[string]string
values map[string]map[string]string
paused bool
*ScenearioStore
}

func (sm *InMemoryScenarioStore) List() string {
Expand All @@ -33,13 +46,15 @@ func (sm *InMemoryScenarioStore) List() string {
func (sm *InMemoryScenarioStore) Reset(name string) bool {
if _, f := sm.status[strings.ToLower(name)]; f {
sm.status[strings.ToLower(name)] = "not_started"
sm.values[strings.ToLower(name)] = make(map[string]string)
return true
}
return false
}

func (sm *InMemoryScenarioStore) ResetAll() {
sm.status = make(map[string]string)
sm.values = make(map[string](map[string]string))
sm.paused = false
}

Expand All @@ -57,6 +72,39 @@ func (sm *InMemoryScenarioStore) GetState(name string) string {
return "not_started"
}

func (sm *InMemoryScenarioStore) SetStateValues(name string, values map[string]string) {
if sm.paused {
return
}
sm.values[strings.ToLower(name)] = values
}

func (sm *InMemoryScenarioStore) SetStateValue(name, valueName, value string) {
if sm.paused {
return
}
if sm.values[strings.ToLower(name)] == nil {
sm.values[strings.ToLower(name)] = make(map[string]string)
}
sm.values[strings.ToLower(name)][strings.ToLower(valueName)] = value
}

func (sm *InMemoryScenarioStore) GetStateValues(name string) map[string]string {
if v, f := sm.values[strings.ToLower(name)]; f {
return v
}

return make(map[string]string)
}

func (sm *InMemoryScenarioStore) GetStateValue(name, valueName string) (string, bool) {
if v, f := sm.values[strings.ToLower(name)][strings.ToLower(valueName)]; f {
return v, true
}

return "", false
}

func (sm *InMemoryScenarioStore) GetPaused() bool {
return sm.paused
}
Expand Down
16 changes: 16 additions & 0 deletions pkg/match/spy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ func (dsm DummyScenarioManager) GetState(name string) string {
return ""
}

func (dsm DummyScenarioManager) GetStateValues(name string) map[string]string {
return make(map[string]string)
}

func (dsm DummyScenarioManager) GetStateValue(name, valueName string) (string, bool) {
return "", false
}

func (dsm DummyScenarioManager) SetStateValues(name string, values map[string]string) {
return
}

func (dsm DummyScenarioManager) SetStateValue(name, valueName, value string) {
return
}

func (dsm DummyScenarioManager) GetPaused() bool {
return false
}
Expand Down
9 changes: 6 additions & 3 deletions pkg/mock/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (

type Values map[string][]string

type ScenarioValues map[string]string

type Cookies map[string]string

type HttpHeaders struct {
Expand Down Expand Up @@ -46,9 +48,10 @@ type Callback struct {
}

type Scenario struct {
Name string `json:"name"`
RequiredState []string `json:"requiredState"`
NewState string `json:"newState"`
Name string `json:"name"`
Values ScenarioValues `json:"values"`
RequiredState []string `json:"requiredState"`
NewState string `json:"newState"`
}

type Delay struct {
Expand Down
5 changes: 5 additions & 0 deletions pkg/mock/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ import (
"io/ioutil"
"net/http"
"strings"
"github.com/jmartin82/mmock/v3/internal/config/logger"
)

var log = logger.Log

// HTTP is and adaptor beteewn the http and mock config.
type HTTP struct {
}
Expand Down Expand Up @@ -70,6 +73,8 @@ func getHostAndPort(req *http.Request) (string, string) {
// WriteHTTPResponseFromDefinition read a mock response and write a http response.
func (t HTTP) WriteHTTPResponseFromDefinition(fr *Response, w http.ResponseWriter) {

log.Debugf("WriteHTTPResponseFromDefinition fr: %v", fr)

for header, values := range fr.Headers {
for _, value := range values {
w.Header().Add(header, value)
Expand Down
64 changes: 64 additions & 0 deletions pkg/vars/dummyScenarioStorer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package vars

import (
"github.com/jmartin82/mmock/v3/pkg/mock"
)

type DummyScenarioStorer struct {
Name string
Values mock.ScenarioValues
}

func NewDummyScenarioStorer(name string, values mock.ScenarioValues) DummyScenarioStorer {
result := DummyScenarioStorer{
Name: name,
Values: values,
}

return result
}

func (dss DummyScenarioStorer) SetState(name, status string) {
return
}

func (dss DummyScenarioStorer) GetState(name string) string {
return ""
}

func (dss DummyScenarioStorer) SetStateValues(name string, values map[string]string) {
return

}

func (dss DummyScenarioStorer) SetStateValue(name, valueName, value string) {
return
}

func (dss DummyScenarioStorer) GetStateValues(name string) map[string]string {
return make(map[string]string)
}

func (dss DummyScenarioStorer) GetStateValue(name, valueName string) (string, bool) {
return "", false
}

func (dss DummyScenarioStorer) Reset(name string) bool {
return true
}

func (dss DummyScenarioStorer) ResetAll() {
return
}

func (dss DummyScenarioStorer) SetPaused(newstate bool) {
return
}

func (dss DummyScenarioStorer) GetPaused() bool {
return false
}

func (dss DummyScenarioStorer) List() string {
return ""
}
Loading