Skip to content

Commit

Permalink
Revert "TT-1188 mdcb testing framework / Gw refactor (TykTechnologies…
Browse files Browse the repository at this point in the history
…#3438)" (TykTechnologies#3506)

This reverts commit 8ef52cb.
  • Loading branch information
furkansenharputlu authored Mar 25, 2021
1 parent 8ef52cb commit e8f89bf
Show file tree
Hide file tree
Showing 140 changed files with 4,064 additions and 4,942 deletions.
182 changes: 67 additions & 115 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ Main points of the test framework are:
- All tests run HTTP requests though the full HTTP stack, same as user will do
- Test definition logic separated from test runner.
- Official mocks for the Dashboard, RPC, and Bundler
- Most of the time, you will need a gateway instance to use the gateway functions and properties

Framework located inside "github.com/TykTechnologies/tyk/test" package.
See its API docs https://godoc.org/github.com/TykTechnologies/tyk/test
Expand All @@ -34,31 +33,45 @@ Let’s learn by example:

```go
func genAuthHeader(username, password string) string {
toEncode := strings.Join([]string{username, password}, ":")
encodedPass := base64.StdEncoding.EncodeToString([]byte(toEncode))
return fmt.Sprintf("Basic %s", encodedPass)
toEncode := strings.Join([]string{username, password}, ":")
encodedPass := base64.StdEncoding.EncodeToString([]byte(toEncode))
return fmt.Sprintf("Basic %s", encodedPass)
}

func TestBasicAuth(t *testing.T) {
ts := StartTest(nil)
defer ts.Close()

session := ts.testPrepareBasicAuth(false)

validPassword := map[string]string{"Authorization": genAuthHeader("user", "password")}
wrongPassword := map[string]string{"Authorization": genAuthHeader("user", "wrong")}
wrongFormat := map[string]string{"Authorization": genAuthHeader("user", "password:more")}
malformed := map[string]string{"Authorization": "not base64"}

ts.Run(t, []test.TestCase{
// Create base auth based key
{Method: "POST", Path: "/tyk/keys/defaultuser", Data: session, AdminAuth: true, Code: 200},
{Method: "GET", Path: "/", Code: 401, BodyMatch: `Authorization field missing`},
{Method: "GET", Path: "/", Headers: validPassword, Code: 200},
{Method: "GET", Path: "/", Headers: wrongPassword, Code: 401},
{Method: "GET", Path: "/", Headers: wrongFormat, Code: 400, BodyMatch: `Attempted access with malformed header, values not in basic auth format`},
{Method: "GET", Path: "/", Headers: malformed, Code: 400, BodyMatch: `Attempted access with malformed header, auth data not encoded correctly`},
}...)
// Start the test server
ts := newTykTestServer()
defer ts.Close()

// Configure and load API definition
buildAndLoadAPI(func(spec *APISpec) {
spec.UseBasicAuth = true
spec.UseKeylessAccess = false
spec.Proxy.ListenPath = "/"
spec.OrgID = "default"
})

// Prepare data which will be used in tests

session := createStandardSession()
session.BasicAuthData.Password = "password"
session.AccessRights = map[string]user.AccessDefinition{"test": {APIID: "test", Versions: []string{"v1"}}}

validPassword := map[string]string{"Authorization": genAuthHeader("user", "password")}
wrongPassword := map[string]string{"Authorization": genAuthHeader("user", "wrong")}
wrongFormat := map[string]string{"Authorization": genAuthHeader("user", "password:more")}
malformed := map[string]string{"Authorization": "not base64"}

// Running tests one by one, based on our definition
ts.Run(t, []test.TestCase{
// Create base auth based key
{Method: "POST", Path: "/tyk/keys/defaultuser", Data: session, AdminAuth: true, Code: 200},
{Method: "GET", Path: "/", Code: 401, BodyMatch: `Authorization field missing`},
{Method: "GET", Path: "/", Headers: validPassword, Code: 200},
{Method: "GET", Path: "/", Headers: wrongPassword, Code: 401},
{Method: "GET", Path: "/", Headers: wrongFormat, Code: 400, BodyMatch: `Attempted access with malformed header, values not in basic auth format`},
{Method: "GET", Path: "/", Headers: malformed, Code: 400, BodyMatch: `Attempted access with malformed header, auth data not encoded correctly`},
}...)
}
```

Expand Down Expand Up @@ -108,59 +121,34 @@ Now lets review tests written with a new framework piece by piece.
One of the core ideas, is that tests should be as close as possible to real users. In order to implement it, framework provides you a way to programmatically start and stop full Gateway HTTP stack using `tykTestServer` object, like this:

```go
ts := StartTest(nil)
ts := newTykTestServer()
defer ts.Close()
```

## About StartTest
StarTest() is a function that will and setup a gateway instance, it receive as parameters:
When you create a new server, it initialize gateway itself, starts listener on random port, setup required global variables and etc. It is very similar to what happens when you start gateway process, but in this case you can start and stop it on demand.

- genConf(*config.Config): is a function to override the gateway config set by default, if you don't want to override any config then you must sent a nil value. Example:
```
conf := func(confi *config.Config) {
confi.EventHandlers = eventsConf.EventHandlers
}
ts := StartTest(conf)
defer ts.Close()
```
- testConfig: you can configure server behavior using few variable, like setting control API on a separate port, by providing `TestConfig` object. Here is the list of all possible arguments:
You can configure server behavior using few variable, like setting control API on a separate port, by providing `tykTestServerConfig` object, to `newTykTestServer` as argument. Here is the list of all possible arguments:

```go
ts := gateway.StartTest(nil, gateway.TestConfig{
// Run control API on a separate port
sepatateControlAPI: true,
// Add delay after each test case, if you code depend on timing
// Bad practice, but sometimes needed
delay: 10 * time.Millisecond,
// Emulate that Gateway restarted using SIGUSR2
hotReload: true,
// Emulate that listener will
overrideDefaults, true,
// if you want to skip the redis cleaning process
SkipEmptyRedis: false
})
defer ts.Close()
ts := newTykTestServer(tykTestServerConfig{
// Run control API on a separate port
sepatateControlAPI: true,
// Add delay after each test case, if you code depend on timing
// Bad practice, but sometimes needed
delay: 10 * time.Millisecond,
// Emulate that Gateway restarted using SIGUSR2
hotReload: true,
// Emulate that listener will
overrideDefaults, true,
})
```

StartTest() returns a Test object that contains:

- URL: where the gateway is reachable
- testRunner: the HttpTestRunner to use to consume and test endpoints
- config: a TestConfig object on how to run the test, we pass this as param to `StartTest()` when starting the test
- Gw: a full gateway instance, from which we can call any gateway function, get the current gateway config or even modify it
- HttpHandler: the HTTP server

When you create a new server, it initialize gateway itself, starts listener on random port, setup required global variables and etc. It is very similar to what happens when you start gateway process, but in this case you can start, stop and reload it on demand.

To close the server simply call `Test#Close` method, which will ensure that all the listeners will be properly closed.
To close the server simply call `tykTestServer#Close` method, which will ensure that all the listeners will be properly closed.

### Loading and configuring APIs

```go
ts := gateway.StartTest(nil)
defer ts.Close()

ts.Gw.buildAndLoadAPI(func(spec *APISpec) {
buildAndLoadAPI(func(spec *APISpec) {
spec.UseBasicAuth = true
spec.UseKeylessAccess = false
spec.Proxy.ListenPath = "/"
Expand All @@ -177,25 +165,19 @@ You can also call it without arguments at all, in this case it will load default
In fact, this function is mashup of 2 lower level functions: `buildAPI` and `loadAPI`, both returning `[]*APISpec` array. In some cases you may need to build API template, and with some smaller modifications load it on demand in different tests. So it can look like:

```go
ts := gateway.StartTest(nil)
defer ts.Close()

spec := buildAPI(<fn>)
...
spec.SomeField = "Case1"
ts.Gw.loadAPI(spec)
loadAPI(spec)
...
spec.SomeField = "Case2"
ts.Gw.loadAPI(spec)
loadAPI(spec)
```

Updating variables inside API version can be tricky, because API version object is inside `Versions` map, and direct manipulations with map value is prohibited. To simplify this process, there is special helper `updateAPIVersion`, which can be used like this:

```go
ts := gateway.StartTest(nil)
defer ts.Close()

ts.Gw.updateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
updateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
v.Paths.BlackList = []string{"/blacklist/literal", "/blacklist/{id}/test"}
v.UseExtendedPaths = false
})
Expand All @@ -204,10 +186,7 @@ ts.Gw.updateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
In some cases updating API definition via Go structures can be a bit complex, and you may want to update API definition directly via JSON unmarshaling:

```go
ts := gateway.StartTest(nil)
defer ts.Close()

ts.Gw.updateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
updateAPIVersion(spec, "v1", func(v *apidef.VersionInfo) {
json.Unmarshal([]byte(`[
{
"path": "/ignored/literal",
Expand Down Expand Up @@ -264,27 +243,13 @@ type TestCase struct {
}
```

`Test` provides a test runner, which generate HTTP requests based on specification and does assertions. Most of the time you going to use `tykTestServer#Run(t *testing.T, test.TestCase...) (*http.Response, error)`function. Note that it use variadic number of arguments, so if you need to pass multiple test cases, pass it like in example above: `[]test.TestCase{<tc1>,<tc2>}...`, with 3 dots in the end.
`tykTestServer` provides a test runner, which generate HTTP requests based on specification and does assertions. Most of the time you going to use `tykTestServer#Run(t *testing.T, test.TestCase...) (*http.Response, error)`function. Note that it use variadic number of arguments, so if you need to pass multiple test cases, pass it like in example above: `[]test.TestCase{<tc1>,<tc2>}...`, with 3 dots in the end.

Additionally there is `RunEx` function, with exactly same definition, but internally it runs test cases multiple times (4 right now) with different combinations of `overrideDefaults` and `hotReload` options. This can be handy if you need to test functionality that tightly depends hot reload functionality, like reloading APIs, loading plugin bundles or listener itself.

Both `Run` and `RunEx` also return response and error of the last test case, in case if you need it.
### Changing config variables
In lot of cases tests depend on various config variables. You can can update them directly on the gateway config object `Gw.GetConfig` doing:

```go
ts := gateway.StartTest(nil)
defer ts.Close()

// Obtains current config
currentConfig := ts.Gw.GetConfig()
// perform changes
currentConfig..HTTPProfile = true
// set new config
ts.Gw.SetConfig(currentConfig)
// in some cases will be needed a reload
ts.Gw.DoReload()
```
In lot of cases tests depend on various config variables. You can can update them directly on `config.Global` object, and restore default config using `resetTestConfig` function.

```go
config.Global.HttpServerOptions.OverrideDefaults = true
Expand Down Expand Up @@ -354,12 +319,12 @@ def MyAuthHook(request, session, metadata, spec):
}

func TestPython(t *testing.T) {
ts := gateway.StartTest(nil)
defer ts.Close()
ts := newTykTestServer()
defer ts.Close()

bundleID := ts.registerBundle("python_with_auth_check", pythonBundleWithAuthCheck)
bundleID := registerBundle("python_with_auth_check", pythonBundleWithAuthCheck)

ts.Gw.buildAndLoadAPI(func(spec *APISpec) {
buildAndLoadAPI(func(spec *APISpec) {
spec.UseKeylessAccess = false
spec.EnableCoProcessAuth = true
spec.CustomMiddlewareBundle = bundleID
Expand All @@ -371,10 +336,7 @@ func TestPython(t *testing.T) {
### Creating user sessions
You can create a user session, similar to API, by calling `createSession` function:
```go
ts := gateway.StartTest(nil)
defer ts.Close()

key := ts.Gw.createSession(func(s *user.SessionState) {
key := createSession(func(s *user.SessionState) {
s.QuotaMax = 2
})
```
Expand All @@ -386,14 +348,11 @@ If you need to create session object without adding it to database, for example
If you need to create custom upstream test server, for example if you need custom TLS settings for Mutual TLS testing, the easiest way is to use standard Go `net/http/httptest` package and override `spec.Proxy.TargetURL` API URL to test server.

```go
ts := gateway.StartTest(nil)
defer ts.Close()

upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// custom logic
}))

ts.Gw.buildAndLoadAPI(func(spec *APISpec) {
buildAndLoadAPI(func(spec *APISpec) {
spec.Proxy.TargetURL = upstream.URL
})
```
Expand All @@ -402,8 +361,6 @@ ts.Gw.buildAndLoadAPI(func(spec *APISpec) {
There is no any specific object to mock the dashboard (yet), but since Dashboard is a standard HTTP server, you can use approach similar to described in **Custom upstream mock** section:

```go


dashboard := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/system/apis" {
w.Write([]byte(`{"Status": "OK", "Nonce": "1", "Message": [{"api_definition": {}}]}`))
Expand All @@ -412,14 +369,9 @@ dashboard := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *
}
}))

conf := func(confi *config.Config) {
confi.Global.UseDBAppConfigs = true
confi.Global.AllowInsecureConfigs = true
confi.Global.DBAppConfOptions.ConnectionString = dashboard.URL
}

ts := gateway.StartTest(conf)
defer ts.Close()
config.Global.UseDBAppConfigs = true
config.Global.AllowInsecureConfigs = true
config.Global.DBAppConfOptions.ConnectionString = dashboard.URL
```

### Mocking RPC (Hybrid)
Expand Down
14 changes: 8 additions & 6 deletions checkup/checkup.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ const (
minRecordsBufferSize = 1000
)

func Run(c *config.Config) {
legacyRateLimiters(*c)
allowInsecureConfigs(*c)
healthCheck(*c)
func Run(c config.Config) {
legacyRateLimiters(c)
allowInsecureConfigs(c)
healthCheck(c)
fileDescriptors()
cpus()
defaultSecrets(*c)
defaultSecrets(c)
defaultAnalytics(c)
}

Expand Down Expand Up @@ -94,7 +94,7 @@ func defaultSecrets(c config.Config) {
}
}

func defaultAnalytics(c *config.Config) {
func defaultAnalytics(c config.Config) {
if !c.EnableAnalytics {
return
}
Expand All @@ -118,4 +118,6 @@ func defaultAnalytics(c *config.Config) {
Warning("AnalyticsConfig.StorageExpirationTime is 0, defaulting to 60s")
c.AnalyticsConfig.StorageExpirationTime = 60
}

config.SetGlobal(c)
}
18 changes: 14 additions & 4 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,6 @@ var (
CheckInterval: dnsCacheDefaultCheckInterval,
MultipleIPsHandleStrategy: NoCacheStrategy,
},
HealthCheckEndpointName: "hello",
CoProcessOptions: CoProcessConfig{
EnableCoProcess: false,
},
}
)

Expand Down Expand Up @@ -597,6 +593,20 @@ type TykEventHandler interface {
HandleEvent(EventMessage)
}

func init() {
SetGlobal(Config{})
}

func Global() Config {
return global.Load().(Config)
}

func SetGlobal(conf Config) {
globalMu.Lock()
defer globalMu.Unlock()
global.Store(conf)
}

func WriteConf(path string, conf *Config) error {
bs, err := json.MarshalIndent(conf, "", " ")
if err != nil {
Expand Down
15 changes: 4 additions & 11 deletions coprocess/coprocess_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,10 @@ func TestCoProcessDispatch(t *testing.T) {
}

func TestCoProcessDispatchEvent(t *testing.T) {
ts := gateway.StartTest()
defer ts.Close()

spec := ts.Gw.LoadSampleAPI().LoadSampleAPI(basicCoProcessDef)
spec := gateway.LoadSampleAPI(basicCoProcessDef)
remote, _ := url.Parse(spec.Proxy.TargetURL)

proxy := gateway.TykNewSingleHostReverseProxy(remote, spec)
baseMid := gateway.BaseMiddleware{Spec: spec, Proxy: proxy, Gw: ts.Gw}
baseMid := gateway.BaseMiddleware{Spec: spec, Proxy: proxy}

meta := gateway.EventKeyFailureMeta{
EventMetaDefault: gateway.EventMetaDefault{Message: "Auth Failure"},
Expand Down Expand Up @@ -96,6 +92,7 @@ func TestCoProcessReload(t *testing.T) {
}

/* Serialization, CP Objects */

func TestCoProcessSerialization(t *testing.T) {
object := &coprocess.Object{
HookType: coprocess.HookType_Pre,
Expand Down Expand Up @@ -141,11 +138,7 @@ func buildCoProcessChain(spec *gateway.APISpec, hookName string, hookType coproc
remote, _ := url.Parse(spec.Proxy.TargetURL)
proxy := gateway.TykNewSingleHostReverseProxy(remote, spec)
proxyHandler := gateway.ProxyHandler(proxy, spec)

ts := gateway.StartTest(nil)
defer ts.Close()

baseMid := gateway.BaseMiddleware{Spec: spec, Proxy: proxy, Gw: ts.Gw} // TODO
baseMid := gateway.BaseMiddleware{Spec: spec, Proxy: proxy} // TODO
mw := gateway.CreateCoProcessMiddleware(hookName, hookType, driver, baseMid)
return alice.New(mw).Then(proxyHandler)
}
Expand Down
Loading

0 comments on commit e8f89bf

Please sign in to comment.