Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Simply declare a list of URLs along with their expected response values, and Emb
emberfall [flags]

Flags:
-c, --config string Path to config file. - to read from stdin (default "-")
-c, --tests string Path to tests config file. - to read from stdin (default "-")
-h, --help help for emberfall
-u, --url string Regular expression to include only tests with a matching url
-v, --version version for emberfall
Expand All @@ -22,8 +22,8 @@ Flags:
## Configuring Tests

The YAML tests config can be provided in two ways:
- as a file: `emberfall --config path/to/config.yaml`
- piped to stdin: `echo $EMBERFALL_CONFIG | emberfall --config -`
- as a file: `emberfall --tests path/to/config.yaml`
- piped to stdin: `echo $EMBERFALL_CONFIG | emberfall --tests -`

Tests are defined in a simple YAML document with the following schema:
```yaml
Expand Down Expand Up @@ -131,15 +131,15 @@ go install ./...

### Running Tests

Define tests in a YAML file like show above, and run emberfall: `emberfall --config path/to/config.yaml`
Define tests in a YAML file like show above, and run emberfall: `emberfall --tests path/to/config.yaml`

## As a Github Action


```yaml
uses: "aquia-inc/emberfall@main"
with:
version: 0.3.2
version: 0.4.0
config: # string: YAML tests config inlined
file: # string: path/to/tests
```
Expand All @@ -153,7 +153,7 @@ This is helpful for either short tests or for testing Emberfall integration with
```yaml
uses: "aquia-inc/emberfall@main"
with:
version: 0.3.2
version: 0.4.0
config: |
---
tests:
Expand All @@ -173,6 +173,6 @@ For longer tests it's best to place those in their own file like so
```yaml
uses: "aquia-inc/emberfall@main"
with:
version: 0.3.2
version: 0.4.0
file: path/to/tests.yml
```
31 changes: 8 additions & 23 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,13 @@ www.aquia.us
package cmd

import (
"fmt"
"os"
"strings"

"github.com/aquia-inc/emberfall/internal/engine"
"github.com/spf13/cobra"
)

var (
configPath string
urlPattern string
methodPattern string
)
var config *engine.Config

var rootCmd = &cobra.Command{
Use: "emberfall",
Expand Down Expand Up @@ -46,19 +40,9 @@ tests:
headers: object # optional, headers expected to be present in the response
# key:value pairs
`,
Version: "0.3.2",
Run: func(cmd *cobra.Command, args []string) {

configPath = strings.TrimSpace(configPath)
conf, err := engine.LoadConfig(configPath)
if err != nil {
fmt.Println(err)
os.Exit(1)
}

if !engine.Run(conf, urlPattern, methodPattern) {
os.Exit(2)
}
Version: "0.4.0",
RunE: func(cmd *cobra.Command, args []string) error {
return engine.Run(config)
},
}

Expand All @@ -70,8 +54,9 @@ func Execute() {
}

func init() {
config = &engine.Config{}
flags := rootCmd.Flags()
flags.StringVarP(&configPath, "config", "c", "-", "Path to config file. - to read from stdin")
flags.StringVarP(&urlPattern, "url", "u", "", "Regular expression to include only tests with a matching url")
flags.StringVarP(&methodPattern, "method", "m", "", "Regular expression to include only tests with a matching method")
flags.StringVarP(&config.TestsPath, "tests", "t", "-", "Path to tests configuration file. - to read from stdin")
flags.StringVarP(&config.UrlPattern, "url", "u", "", "Regular expression to include only tests with a matching url")
flags.StringVarP(&config.MethodPattern, "method", "m", "", "Regular expression to include only tests with a matching method")
}
24 changes: 11 additions & 13 deletions internal/engine/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,38 @@ import (
"gopkg.in/yaml.v3"
)

type config struct {
Tests []*test `yaml:"tests"`
type Config struct {
TestsPath, UrlPattern, MethodPattern string
Tests []*test `yaml:"tests"`
}

func LoadConfig(configPath string) (*config, error) {
func (c *Config) LoadTests() error {
var (
b []byte
err error
)

fmt.Printf("Reading config from %s\n", configPath)
fmt.Printf("Reading config from %s\n", c.TestsPath)
var stat fs.FileInfo

if configPath == "-" {
if c.TestsPath == "-" {
stat, err = os.Stdin.Stat()
if err != nil {
return nil, err
return err
}

if stat.Size() < 1 {
return nil, fmt.Errorf("no config provided")
return fmt.Errorf("no config provided")
}

b, err = io.ReadAll(os.Stdin)
} else {
b, err = os.ReadFile(configPath)
b, err = os.ReadFile(c.TestsPath)
}

if err != nil {
return nil, err
return err
}

conf := &config{}
err = yaml.Unmarshal(b, conf)

return conf, err
return yaml.Unmarshal(b, c)
}
29 changes: 18 additions & 11 deletions internal/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import (
"regexp"
)

// TODO: refactor to put all passed flag values into a single struct
func Run(cfg *config, urlPattern, methodPattern string) bool {
func Run(cfg *Config) error {
// reduce memory allocations by reusing as many
var (
client = &http.Client{}
Expand All @@ -22,19 +21,22 @@ func Run(cfg *config, urlPattern, methodPattern string) bool {
err error
)

if urlPattern != "" {
includedURLs, err = regexp.Compile(urlPattern)
err = cfg.LoadTests()
if err != nil {
return err
}

if cfg.UrlPattern != "" {
includedURLs, err = regexp.Compile(cfg.UrlPattern)
if err != nil {
fmt.Println(err)
return false
return err
}
}

if methodPattern != "" {
includedMethods, err = regexp.Compile(methodPattern)
if cfg.MethodPattern != "" {
includedMethods, err = regexp.Compile(cfg.MethodPattern)
if err != nil {
fmt.Println(err)
return false
return err
}
}

Expand Down Expand Up @@ -136,7 +138,12 @@ func Run(cfg *config, urlPattern, methodPattern string) bool {
} // end for

fmt.Printf("\n Ran: %d\n Failed: %d\nSkipped: %d\n", ran, failed, skipped)
return (failed == 0)

if failed > 0 {
return errors.New("tests failed")
}

return nil
}

func noRedirect(req *http.Request, via []*http.Request) error {
Expand Down
50 changes: 25 additions & 25 deletions tests/cli.bats
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ setup() {
@test "--version should be correct" {
run ./emberfall --version
assert_success
assert_output "emberfall version 0.3.2"
assert_output "emberfall version 0.4.0"
}

@test "no config SHOULD FAIL" {
Expand All @@ -17,121 +17,121 @@ setup() {
}

@test "SHOULD FAIL without following redirect" {
run ./emberfall --config ./tests/fail-no-follow.yml
run ./emberfall --tests ./tests/fail-no-follow.yml
assert_failure
assert_output --partial 'FAIL'
assert_output --partial 'expected status == 200 got 301'
}

@test "SHOULD PASS by following redirect" {
run ./emberfall --config ./tests/pass-follow.yml
run ./emberfall --tests ./tests/pass-follow.yml
assert_success
assert_output --partial 'PASS'
}

@test "SHOULD PASS with expected headers" {
run ./emberfall --config ./tests/pass-headers.yml
run ./emberfall --tests ./tests/pass-headers.yml
assert_success
assert_output --partial 'PASS'
}

@test "SHOULD FAIL with missing headers" {
run ./emberfall --config ./tests/fail-missing-headers.yml
run ./emberfall --tests ./tests/fail-missing-headers.yml
assert_failure
assert_output --partial 'FAIL'
assert_output --partial 'expected header x-no-exist was missing'
}

@test "SHOULD FAIL with bad url" {
run ./emberfall --config ./tests/fail-bad-url.yml
run ./emberfall --tests ./tests/fail-bad-url.yml
assert_failure
assert_output --partial 'no such host'
}

@test "SHOULD PASS with response JSON == request JSON" {
run ./emberfall --config ./tests/pass-req-res-json-match.yml
run ./emberfall --tests ./tests/pass-req-res-json-match.yml
assert_success
}

@test "SHOULD FAIL with response JSON != request JSON" {
run ./emberfall --config ./tests/fail-req-res-json-no-match.yml
run ./emberfall --tests ./tests/fail-req-res-json-no-match.yml
assert_failure
assert_output --partial 'expected body.json.data.foo == baz got bar'
}

@test "SHOULD PASS with response text" {
run ./emberfall --config ./tests/pass-req-res-text-match.yml
run ./emberfall --tests ./tests/pass-req-res-text-match.yml
assert_success
}

@test "SHOULD FAIL with response text no match" {
run ./emberfall --config ./tests/fail-req-res-text-no-match.yml
run ./emberfall --tests ./tests/fail-req-res-text-no-match.yml
assert_failure
assert_output --partial 'expected body.json.data == baz got bar'
}

@test "SHOULD PASS with 404 on interpolated path" {
run ./emberfall --config ./tests/pass-test-dependencies.yml
run ./emberfall --tests ./tests/pass-test-dependencies.yml
assert_success
assert_output --partial 'PASS : GET https://postman-echo.com/baz'
}

@test "SHOULD PASS float equals float" {
run ./emberfall --config ./tests/pass-numbers-float-equals-float.yml
run ./emberfall --tests ./tests/pass-numbers-float-equals-float.yml
assert_success
}

@test "SHOULD PASS int equals int" {
run ./emberfall --config ./tests/pass-numbers-int-equals-int.yml
run ./emberfall --tests ./tests/pass-numbers-int-equals-int.yml
assert_success
}

@test "SHOULD FAIL int equals int" {
run ./emberfall --config ./tests/fail-numbers-int-equals-int.yml
run ./emberfall --tests ./tests/fail-numbers-int-equals-int.yml
assert_failure
assert_output --partial 'expected body.json.data.num == 1 got 2'
}

@test "SHOULD FAIL int equals float" {
run ./emberfall --config ./tests/fail-numbers-int-equals-float.yml
run ./emberfall --tests ./tests/fail-numbers-int-equals-float.yml
assert_failure
assert_output --partial 'expected body.json.data.num == 1 got 1.1'
}

@test "SHOULD FAIL float equals float" {
run ./emberfall --config ./tests/fail-numbers-float-equals-float.yml
run ./emberfall --tests ./tests/fail-numbers-float-equals-float.yml
assert_failure
assert_output --partial 'expected body.json.data.num == 2.2 got 3.3'
}

@test "SHOULD FAIL string equals float" {
run ./emberfall --config ./tests/fail-numbers-string-equals-float.yml
run ./emberfall --tests ./tests/fail-numbers-string-equals-float.yml
assert_failure
assert_output --partial 'expected body.json.data.num == 1 got 1.1'
}

@test "SHOULD FAIL string equals int" {
run ./emberfall --config ./tests/fail-numbers-string-equals-int.yml
run ./emberfall --tests ./tests/fail-numbers-string-equals-int.yml
assert_failure
assert_output --partial 'expected body.json.data.num == 1 got 2'
}

@test "SHOULD FAIL but body response gets printed" {
run ./emberfall --config ./tests/fail-response-printed.yml
run ./emberfall --tests ./tests/fail-response-printed.yml
assert_failure
assert_output --partial '"status": 400'
assert_output --partial '"status":400'
}

@test "SHOULD PASS include exactly 200" {
run ./emberfall --config ./tests/include-exclude.yml --url 'status/200'
run ./emberfall --tests ./tests/include-exclude.yml --url 'status/200'
assert_success
assert_output --partial 'PASS : GET https://postman-echo.com/status/200'
assert_output --partial 'Ran: 1'
assert_output --partial 'Skipped: 4'
}

@test "SHOULD PASS include all 200 status" {
run ./emberfall --config ./tests/include-exclude.yml -u 'status/2\d{2}'
run ./emberfall --tests ./tests/include-exclude.yml -u 'status/2\d{2}'
assert_success
assert_output --partial 'PASS : GET https://postman-echo.com/status/200'
assert_output --partial 'PASS : GET https://postman-echo.com/status/201'
Expand All @@ -140,7 +140,7 @@ setup() {
}

@test "SHOULD PASS include all but 200" {
run ./emberfall --config ./tests/include-exclude.yml -u 'status/[13-5]\d{2}'
run ./emberfall --tests ./tests/include-exclude.yml -u 'status/[13-5]\d{2}'
assert_success
assert_output --partial 'PASS : GET https://postman-echo.com/status/301'
assert_output --partial 'PASS : GET https://postman-echo.com/status/302'
Expand All @@ -149,13 +149,13 @@ setup() {
}

@test "SHOULD FAIL include invalid regular expression" {
run ./emberfall --config ./tests/include-exclude.yml -u '[[200'
run ./emberfall --tests ./tests/include-exclude.yml -u '[[200'
assert_failure
assert_output --partial 'error parsing regexp: missing closing ]: `[[200`'
}

@test "SHOULD PASS include method POST" {
run ./emberfall --config ./tests/include-exclude.yml -m 'POST'
run ./emberfall --tests ./tests/include-exclude.yml -m 'POST'
assert_success
assert_output --partial "PASS : POST https://postman-echo.com/status/201"
assert_output --partial "Ran: 1"
Expand Down