3
0

Setup the API GW testing env

This commit is contained in:
Tomaž Jerman 2021-09-27 15:50:18 +02:00
parent 52e0e838e9
commit 6ceadf40fc
14 changed files with 481 additions and 16 deletions

View File

@ -29,6 +29,7 @@ type (
log *zap.Logger
reg *registry.Registry
routes []*route
router chi.Router
storer storer
reload chan bool
}
@ -75,6 +76,24 @@ func (s *apigw) Reload(ctx context.Context) {
}()
}
// ReloadHandler is a wrapper for route reloading logic, primarily needed for testing
func (s *apigw) ReloadHandler(ctx context.Context) {
routes, err := s.loadRoutes(ctx)
if err != nil {
s.log.Error("could not reload API Gateway routes", zap.Error(err))
return
}
s.log.Debug("reloading API Gateway routes and functions", zap.Int("count", len(routes)))
s.Init(ctx, routes...)
for _, route := range s.routes {
s.router.Handle(route.endpoint, route)
}
}
func (s *apigw) loadRoutes(ctx context.Context) (rr []*route, err error) {
routes, _, err := s.storer.SearchApigwRoutes(ctx, st.ApigwRouteFilter{
Enabled: true,
@ -116,7 +135,9 @@ func (s *apigw) Router(r chi.Router) {
ctx = context.Background()
)
r.HandleFunc("/", helperDefaultResponse(s.opts))
s.router = r
s.router.HandleFunc("/", helperDefaultResponse(s.opts))
routes, err := s.loadRoutes(ctx)
@ -128,27 +149,14 @@ func (s *apigw) Router(r chi.Router) {
s.Init(ctx, routes...)
for _, route := range s.routes {
r.Handle(route.endpoint, route)
s.router.Handle(route.endpoint, route)
}
go func() {
for {
select {
case <-s.reload:
routes, err := s.loadRoutes(ctx)
if err != nil {
s.log.Error("could not reload API Gateway routes", zap.Error(err))
return
}
s.log.Debug("reloading API Gateway routes and functions", zap.Int("count", len(routes)))
s.Init(ctx, routes...)
for _, route := range s.routes {
r.Handle(route.endpoint, route)
}
s.ReloadHandler(ctx)
case <-ctx.Done():
s.log.Debug("shutting down API Gateway service")

213
tests/apigw/main_test.go Normal file
View File

@ -0,0 +1,213 @@
package apigw
import (
"context"
"errors"
"os"
"path"
"testing"
"github.com/cortezaproject/corteza-server/app"
"github.com/cortezaproject/corteza-server/pkg/api/server"
"github.com/cortezaproject/corteza-server/pkg/apigw"
"github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/cli"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/csv"
"github.com/cortezaproject/corteza-server/pkg/envoy/directory"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
es "github.com/cortezaproject/corteza-server/pkg/envoy/store"
"github.com/cortezaproject/corteza-server/pkg/envoy/yaml"
"github.com/cortezaproject/corteza-server/pkg/eventbus"
"github.com/cortezaproject/corteza-server/pkg/id"
"github.com/cortezaproject/corteza-server/pkg/label"
ltype "github.com/cortezaproject/corteza-server/pkg/label/types"
"github.com/cortezaproject/corteza-server/pkg/logger"
"github.com/cortezaproject/corteza-server/pkg/options"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/store/sqlite3"
"github.com/cortezaproject/corteza-server/system/service"
sysTypes "github.com/cortezaproject/corteza-server/system/types"
"github.com/cortezaproject/corteza-server/tests/helpers"
"github.com/go-chi/chi"
_ "github.com/joho/godotenv/autoload"
"github.com/steinfletcher/apitest"
"github.com/stretchr/testify/require"
)
type (
helper struct {
t *testing.T
a *require.Assertions
cUser *sysTypes.User
roleID uint64
token string
}
)
var (
testApp *app.CortezaApp
r chi.Router
eventBus = eventbus.New()
)
func init() {
helpers.RecursiveDotEnvLoad()
}
func InitTestApp() {
if testApp == nil {
ctx := cli.Context()
testApp = helpers.NewIntegrationTestApp(ctx, func(app *app.CortezaApp) (err error) {
service.DefaultStore, err = sqlite3.ConnectInMemory(ctx)
if err != nil {
return err
}
eventbus.Set(eventBus)
return nil
})
}
if r == nil {
r = chi.NewRouter()
r.Use(server.BaseMiddleware(false, logger.Default())...)
helpers.BindAuthMiddleware(r)
// setup API GW routes
apigw.Setup(options.Apigw(), service.DefaultLogger, service.DefaultStore)
r.Route("/", apigw.Service().Router)
}
}
func TestMain(m *testing.M) {
InitTestApp()
os.Exit(m.Run())
}
func newHelper(t *testing.T) helper {
h := helper{
t: t,
a: require.New(t),
roleID: id.Next(),
cUser: &sysTypes.User{
ID: id.Next(),
},
}
var err error
h.token, err = auth.DefaultJwtHandler.Generate(context.Background(), h.cUser)
if err != nil {
panic(err)
}
h.cUser.SetRoles(h.roleID)
helpers.UpdateRBAC(h.roleID)
return h
}
func (h helper) MyRole() uint64 {
return h.roleID
}
// Returns context w/ security details
func (h helper) secCtx() context.Context {
return auth.SetIdentityToContext(context.Background(), h.cUser)
}
// apitest basics, initialize, set handler, add auth
func (h helper) apiInit() *apitest.APITest {
InitTestApp()
return apitest.
New().
Handler(r).
Intercept(helpers.ReqHeaderRawAuthBearer(h.token))
}
func setupScenario(t *testing.T) (context.Context, helper, store.Storer) {
ctx, h, s := setup(t)
loadScenario(ctx, s, t, h)
reloadRoutes(ctx)
return ctx, h, s
}
func setup(t *testing.T) (context.Context, helper, store.Storer) {
h := newHelper(t)
s := service.DefaultStore
u := &sysTypes.User{
ID: id.Next(),
}
u.SetRoles(auth.BypassRoles().IDs()...)
ctx := auth.SetIdentityToContext(context.Background(), u)
return ctx, h, s
}
func reloadRoutes(ctx context.Context) {
apigw.Service().ReloadHandler(ctx)
}
// Unwraps error before it passes it to the tester
func (h helper) noError(err error) {
for errors.Unwrap(err) != nil {
err = errors.Unwrap(err)
}
h.a.NoError(err)
}
func (h helper) setLabel(res label.LabeledResource, name, value string) {
h.a.NoError(store.UpsertLabel(h.secCtx(), service.DefaultStore, &ltype.Label{
Kind: res.LabelResourceKind(),
ResourceID: res.LabelResourceID(),
Name: name,
Value: value,
}))
}
func loadScenario(ctx context.Context, s store.Storer, t *testing.T, h helper) {
loadScenarioWithName(ctx, s, t, h, t.Name()[5:])
}
func loadScenarioWithName(ctx context.Context, s store.Storer, t *testing.T, h helper, scenario string) {
cleanup(ctx, h, s)
parseEnvoy(ctx, s, h, path.Join("testdata", scenario))
}
func cleanup(ctx context.Context, h helper, s store.Storer) {
h.noError(s.TruncateApigwFilters(ctx))
h.noError(s.TruncateApigwRoutes(ctx))
}
func parseEnvoy(ctx context.Context, s store.Storer, h helper, path string) {
nn, err := directory.Decode(
ctx,
path,
yaml.Decoder(),
csv.Decoder(),
)
if err != nil {
h.t.Fatalf("failed to decode scenario data: %v", err)
}
crs := resource.ComposeRecordShaper()
nn, err = resource.Shape(nn, crs)
h.a.NoError(err)
// import into the store
se := es.NewStoreEncoder(s, nil)
bld := envoy.NewBuilder(se)
g, err := bld.Build(ctx, nn...)
h.a.NoError(err)
err = envoy.Encode(ctx, g, se)
h.a.NoError(err)
}

View File

@ -0,0 +1,20 @@
package apigw
import (
"net/http"
"testing"
)
func Test_prefilter_header_failing(t *testing.T) {
var (
_, h, _ = setupScenario(t)
)
h.apiInit().
Get("/test").
Header("Accept", "application/json").
Header("Token", "brute-force-guess").
Expect(t).
Status(http.StatusBadRequest).
End()
}

View File

@ -0,0 +1,46 @@
package apigw
import (
"net/http"
"testing"
)
func Test_prefilter_header_n_routes(t *testing.T) {
var (
_, h, _ = setupScenario(t)
)
// First (a) route query validation
h.apiInit().
Get("/a").
Header("P", "a").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
End()
h.apiInit().
Get("/a").
Header("P", "b").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusBadRequest).
End()
// Second (b) route query validation
h.apiInit().
Get("/b").
Header("P", "b").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
End()
h.apiInit().
Get("/b").
Header("P", "a").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusBadRequest).
End()
}

View File

@ -0,0 +1,20 @@
package apigw
import (
"net/http"
"testing"
)
func Test_prefilter_header_passing(t *testing.T) {
var (
_, h, _ = setupScenario(t)
)
h.apiInit().
Get("/test").
Header("Accept", "application/json").
Header("Token", "super-secret-token").
Expect(t).
Status(http.StatusOK).
End()
}

View File

@ -0,0 +1,20 @@
package apigw
import (
"net/http"
"testing"
)
func Test_prefilter_query_failing(t *testing.T) {
var (
_, h, _ = setupScenario(t)
)
h.apiInit().
Get("/test").
Query("token", "brute-force-guess").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusBadRequest).
End()
}

View File

@ -0,0 +1,46 @@
package apigw
import (
"net/http"
"testing"
)
func Test_prefilter_query_n_routes(t *testing.T) {
var (
_, h, _ = setupScenario(t)
)
// First (a) route query validation
h.apiInit().
Get("/a").
Query("p", "a").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
End()
h.apiInit().
Get("/a").
Query("p", "b").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusBadRequest).
End()
// Second (b) route query validation
h.apiInit().
Get("/b").
Query("p", "b").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
End()
h.apiInit().
Get("/b").
Query("p", "a").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusBadRequest).
End()
}

View File

@ -0,0 +1,20 @@
package apigw
import (
"net/http"
"testing"
)
func Test_prefilter_query_passing(t *testing.T) {
var (
_, h, _ = setupScenario(t)
)
h.apiInit().
Get("/test").
Query("token", "super-secret-token").
Header("Accept", "application/json").
Expect(t).
Status(http.StatusOK).
End()
}

View File

@ -0,0 +1,9 @@
apigateway:
- endpoint: /test
method: GET
enabled: true
filters:
- ref: "header"
kind: "prefilter"
params:
expr: "Token == \"super-secret-token\""

View File

@ -0,0 +1,18 @@
apigateway:
- endpoint: /a
method: GET
enabled: true
filters:
- ref: "header"
kind: "prefilter"
params:
expr: "P == \"a\""
- endpoint: /b
method: GET
enabled: true
filters:
- ref: "header"
kind: "prefilter"
params:
expr: "P == \"b\""

View File

@ -0,0 +1,9 @@
apigateway:
- endpoint: /test
method: GET
enabled: true
filters:
- ref: "header"
kind: "prefilter"
params:
expr: "Token == \"super-secret-token\""

View File

@ -0,0 +1,9 @@
apigateway:
- endpoint: /test
method: GET
enabled: true
filters:
- ref: "queryParam"
kind: "prefilter"
params:
expr: "token == \"super-secret-token\""

View File

@ -0,0 +1,18 @@
apigateway:
- endpoint: /a
method: GET
enabled: true
filters:
- ref: "queryParam"
kind: "prefilter"
params:
expr: "p == \"a\""
- endpoint: /b
method: GET
enabled: true
filters:
- ref: "queryParam"
kind: "prefilter"
params:
expr: "p == \"b\""

View File

@ -0,0 +1,9 @@
apigateway:
- endpoint: /test
method: GET
enabled: true
filters:
- ref: "queryParam"
kind: "prefilter"
params:
expr: "token == \"super-secret-token\""