mirror of
https://github.com/MontFerret/ferret.git
synced 2024-12-16 11:37:36 +02:00
9b762d32ee
* Added default headers and cookies * wip * Added tests * Added default headers and cookies to HTTP driver * Removed unused struct prop
413 lines
7.5 KiB
Go
413 lines
7.5 KiB
Go
package runner
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/MontFerret/ferret/pkg/compiler"
|
|
"github.com/MontFerret/ferret/pkg/drivers"
|
|
"github.com/MontFerret/ferret/pkg/drivers/cdp"
|
|
"github.com/MontFerret/ferret/pkg/drivers/http"
|
|
"github.com/MontFerret/ferret/pkg/runtime"
|
|
|
|
"github.com/gobwas/glob"
|
|
"github.com/pkg/errors"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
type (
|
|
Settings struct {
|
|
StaticServerAddress string
|
|
DynamicServerAddress string
|
|
CDPAddress string
|
|
Dir string
|
|
Filter string
|
|
}
|
|
|
|
Result struct {
|
|
name string
|
|
duration time.Duration
|
|
err error
|
|
}
|
|
|
|
Summary struct {
|
|
passed int
|
|
failed int
|
|
duration time.Duration
|
|
}
|
|
|
|
Runner struct {
|
|
logger zerolog.Logger
|
|
settings Settings
|
|
}
|
|
)
|
|
|
|
func New(logger zerolog.Logger, settings Settings) *Runner {
|
|
return &Runner{
|
|
logger,
|
|
settings,
|
|
}
|
|
}
|
|
|
|
func (r *Runner) Run(ctx context.Context) error {
|
|
ctx = drivers.WithContext(
|
|
ctx,
|
|
cdp.NewDriver(cdp.WithAddress(r.settings.CDPAddress)),
|
|
)
|
|
|
|
ctx = drivers.WithContext(
|
|
ctx,
|
|
cdp.NewDriver(cdp.WithAddress(r.settings.CDPAddress),
|
|
cdp.WithCustomName("cdp_headers"),
|
|
cdp.WithHeader("Single_header", []string{"single_header_value"}),
|
|
cdp.WithHeaders(drivers.HTTPHeaders{
|
|
"Multi_set_header": []string{"multi_set_header_value"},
|
|
"Multi_set_header2": []string{"multi_set_header2_value"},
|
|
}),
|
|
),
|
|
)
|
|
|
|
ctx = drivers.WithContext(
|
|
ctx,
|
|
cdp.NewDriver(cdp.WithAddress(r.settings.CDPAddress),
|
|
cdp.WithCustomName("cdp_cookies"),
|
|
cdp.WithCookie(drivers.HTTPCookie{
|
|
Name: "single_cookie",
|
|
Value: "single_cookie_value",
|
|
Path: "/",
|
|
MaxAge: 0,
|
|
Secure: false,
|
|
HTTPOnly: false,
|
|
SameSite: 0,
|
|
}),
|
|
cdp.WithCookies([]drivers.HTTPCookie{
|
|
{
|
|
Name: "multi_set_cookie",
|
|
Value: "multi_set_cookie_value",
|
|
Path: "/",
|
|
MaxAge: 0,
|
|
Secure: false,
|
|
HTTPOnly: false,
|
|
SameSite: 0,
|
|
},
|
|
{
|
|
Name: "multi_set_cookie2",
|
|
Value: "multi_set_cookie2_value",
|
|
Path: "/",
|
|
MaxAge: 0,
|
|
Secure: false,
|
|
HTTPOnly: false,
|
|
SameSite: 0,
|
|
},
|
|
}),
|
|
),
|
|
)
|
|
|
|
ctx = drivers.WithContext(
|
|
ctx,
|
|
http.NewDriver(),
|
|
drivers.AsDefault(),
|
|
)
|
|
|
|
ctx = drivers.WithContext(
|
|
ctx,
|
|
http.NewDriver(
|
|
http.WithCustomName("http_headers"),
|
|
http.WithHeader("Single_header", []string{"single_header_value"}),
|
|
http.WithHeaders(drivers.HTTPHeaders{
|
|
"Multi_set_header": []string{"multi_set_header_value"},
|
|
"Multi_set_header2": []string{"multi_set_header2_value"},
|
|
}),
|
|
),
|
|
)
|
|
|
|
ctx = drivers.WithContext(
|
|
ctx,
|
|
http.NewDriver(
|
|
http.WithCustomName("http_cookies"),
|
|
http.WithCookie(drivers.HTTPCookie{
|
|
Name: "single_cookie",
|
|
Value: "single_cookie_value",
|
|
Path: "/",
|
|
MaxAge: 0,
|
|
Secure: false,
|
|
HTTPOnly: false,
|
|
SameSite: 0,
|
|
}),
|
|
http.WithCookies([]drivers.HTTPCookie{
|
|
{
|
|
Name: "multi_set_cookie",
|
|
Value: "multi_set_cookie_value",
|
|
Path: "/",
|
|
MaxAge: 0,
|
|
Secure: false,
|
|
HTTPOnly: false,
|
|
SameSite: 0,
|
|
},
|
|
{
|
|
Name: "multi_set_cookie2",
|
|
Value: "multi_set_cookie2_value",
|
|
Path: "/",
|
|
MaxAge: 0,
|
|
Secure: false,
|
|
HTTPOnly: false,
|
|
SameSite: 0,
|
|
},
|
|
}),
|
|
),
|
|
)
|
|
|
|
results, err := r.runQueries(ctx, r.settings.Dir)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sum := r.report(results)
|
|
|
|
var event *zerolog.Event
|
|
|
|
if sum.failed == 0 {
|
|
event = r.logger.Info()
|
|
} else {
|
|
event = r.logger.Error()
|
|
}
|
|
|
|
event.
|
|
Timestamp().
|
|
Int("passed", sum.passed).
|
|
Int("failed", sum.failed).
|
|
Str("duration", sum.duration.String()).
|
|
Msg("Completed")
|
|
|
|
if sum.failed > 0 {
|
|
return errors.New("failed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Runner) runQueries(ctx context.Context, dir string) ([]Result, error) {
|
|
results := make([]Result, 0, 50)
|
|
|
|
c := compiler.New()
|
|
|
|
// backward compatible
|
|
if err := Assertions(c); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ns := c.Namespace("T")
|
|
|
|
if err := Assertions(ns); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := HTTPHelpers(ns.Namespace("HTTP")); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var filter glob.Glob
|
|
var useFilter bool
|
|
|
|
if r.settings.Filter != "" {
|
|
f, err := glob.Compile(r.settings.Filter)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
filter = f
|
|
useFilter = true
|
|
}
|
|
|
|
err := r.traverseDir(ctx, dir, func(name string) error {
|
|
if useFilter {
|
|
if !filter.Match(name) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
b, err := ioutil.ReadFile(name)
|
|
|
|
if err != nil {
|
|
results = append(results, Result{
|
|
name: name,
|
|
err: errors.Wrap(err, "failed to read script file"),
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
r.logger.Info().Timestamp().Str("name", name).Msg("Running test")
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return context.Canceled
|
|
default:
|
|
result := r.runQuery(ctx, c, name, string(b))
|
|
|
|
if result.err == nil {
|
|
r.logger.Info().
|
|
Timestamp().
|
|
Str("file", result.name).
|
|
Str("duration", result.duration.String()).
|
|
Msg("Test passed")
|
|
} else {
|
|
r.logger.Error().
|
|
Timestamp().
|
|
Err(result.err).
|
|
Str("file", result.name).
|
|
Str("duration", result.duration.String()).
|
|
Msg("Test failed")
|
|
}
|
|
|
|
results = append(results, result)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func (r *Runner) runQuery(ctx context.Context, c *compiler.Compiler, name, script string) Result {
|
|
start := time.Now()
|
|
|
|
p, err := c.Compile(script)
|
|
|
|
if err != nil {
|
|
return Result{
|
|
name: name,
|
|
duration: time.Duration(0) * time.Millisecond,
|
|
err: errors.Wrap(err, "failed to compile query"),
|
|
}
|
|
}
|
|
|
|
mustFail := r.mustFail(name)
|
|
|
|
out, err := p.Run(
|
|
ctx,
|
|
runtime.WithLog(zerolog.ConsoleWriter{Out: os.Stdout}),
|
|
runtime.WithParam("static", r.settings.StaticServerAddress),
|
|
runtime.WithParam("dynamic", r.settings.DynamicServerAddress),
|
|
)
|
|
|
|
duration := time.Since(start)
|
|
|
|
if err != nil {
|
|
if mustFail {
|
|
return Result{
|
|
name: name,
|
|
duration: duration,
|
|
}
|
|
}
|
|
|
|
return Result{
|
|
name: name,
|
|
duration: duration,
|
|
err: errors.Wrap(err, "failed to execute query"),
|
|
}
|
|
}
|
|
|
|
if mustFail {
|
|
return Result{
|
|
name: name,
|
|
duration: duration,
|
|
err: errors.New("expected to fail"),
|
|
}
|
|
}
|
|
|
|
var result string
|
|
|
|
if err := json.Unmarshal(out, &result); err != nil {
|
|
return Result{
|
|
name: name,
|
|
duration: duration,
|
|
err: err,
|
|
}
|
|
}
|
|
|
|
if result == "" {
|
|
return Result{
|
|
name: name,
|
|
duration: duration,
|
|
}
|
|
}
|
|
|
|
return Result{
|
|
name: name,
|
|
duration: duration,
|
|
err: errors.New(result),
|
|
}
|
|
}
|
|
|
|
func (r *Runner) report(results []Result) Summary {
|
|
var failed int
|
|
var passed int
|
|
var sumDuration time.Duration
|
|
|
|
for _, res := range results {
|
|
if res.err != nil {
|
|
|
|
failed++
|
|
} else {
|
|
|
|
passed++
|
|
}
|
|
|
|
sumDuration += res.duration
|
|
}
|
|
|
|
return Summary{
|
|
passed: passed,
|
|
failed: failed,
|
|
duration: sumDuration,
|
|
}
|
|
}
|
|
|
|
func (r *Runner) traverseDir(ctx context.Context, dir string, iteratee func(name string) error) error {
|
|
files, err := ioutil.ReadDir(dir)
|
|
|
|
if err != nil {
|
|
r.logger.Error().
|
|
Timestamp().
|
|
Err(err).
|
|
Str("dir", dir).
|
|
Msg("failed to read scripts directory")
|
|
|
|
return err
|
|
}
|
|
|
|
for _, file := range files {
|
|
name := filepath.Join(dir, file.Name())
|
|
|
|
if file.IsDir() {
|
|
if err := r.traverseDir(ctx, name, iteratee); err != nil {
|
|
return err
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
if err := iteratee(name); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (r *Runner) mustFail(name string) bool {
|
|
return strings.HasSuffix(name, ".fail.fql")
|
|
}
|