mirror of
https://github.com/gabehf/sp9rk.git
synced 2026-03-12 16:50:26 -07:00
v0.0.1
This commit is contained in:
commit
c05e6d11e8
9 changed files with 1765 additions and 0 deletions
532
action/actions.go
Normal file
532
action/actions.go
Normal file
|
|
@ -0,0 +1,532 @@
|
|||
package action
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func CreateApplication(cfgPath string) func(ctx *cli.Context) error {
|
||||
return func(ctx *cli.Context) error {
|
||||
if ctx.NArg() != 1 {
|
||||
return errors.New("create app must have exactly one argument")
|
||||
}
|
||||
if !valid(ctx.Args().Get(0)) {
|
||||
return errors.New("application name must only contain letters, numbers, dashes and underscores")
|
||||
}
|
||||
if _, err := os.Stat(AppPath(cfgPath, ctx.Args().Get(0))); err == nil {
|
||||
return errors.New("application already exists")
|
||||
}
|
||||
err := WriteAppFiles(cfgPath, &AppInfo{
|
||||
Version: "1",
|
||||
Name: ctx.Args().Get(0),
|
||||
Description: ctx.String("description"),
|
||||
Host: ctx.String("host"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Created application %s\n", ctx.Args().Get(0))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func CreateRequest(cfgPath string) func(ctx *cli.Context) error {
|
||||
return func(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 1 || ctx.NArg() > 1 {
|
||||
return errors.New("create req must have exactly one argument")
|
||||
}
|
||||
|
||||
app, err := getApp(cfgPath, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqName := ctx.Args().Get(0)
|
||||
|
||||
if !valid(app) || !appExists(cfgPath, app) {
|
||||
return errors.New("application does not exist")
|
||||
}
|
||||
if !valid(reqName) {
|
||||
return errors.New("request name must only contain letters, numbers, dashes and underscores")
|
||||
}
|
||||
|
||||
path := ReqPath(cfgPath, app, reqName)
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return errors.New("request already exists")
|
||||
}
|
||||
|
||||
if err := WriteRequestFiles(cfgPath, app, &RequestInfo{
|
||||
Version: "1",
|
||||
Name: reqName,
|
||||
Description: ctx.String("description"),
|
||||
Method: ctx.String("method"),
|
||||
Path: ctx.String("path"),
|
||||
Headers: ctx.StringSlice("header"),
|
||||
Body: ctx.String("body"),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Created request %s\n", reqName)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func EditApplication(cfgPath string) func(ctx *cli.Context) error {
|
||||
return func(ctx *cli.Context) error {
|
||||
if ctx.NArg() != 1 {
|
||||
return errors.New("edit app must have exactly one argument")
|
||||
}
|
||||
if !valid(ctx.Args().Get(0)) {
|
||||
return errors.New("application name is invalid")
|
||||
}
|
||||
contents, err := os.ReadFile(AppInfoFilePath(cfgPath, ctx.Args().Get(0)))
|
||||
if err != nil {
|
||||
return errors.New("application does not exist")
|
||||
}
|
||||
appinfo := new(AppInfo)
|
||||
err = yaml.Unmarshal(contents, appinfo)
|
||||
if err != nil {
|
||||
return errors.New("failed to read application info")
|
||||
}
|
||||
if ctx.String("description") != "" {
|
||||
appinfo.Description = ctx.String("description")
|
||||
}
|
||||
if ctx.String("host") != "" {
|
||||
appinfo.Host = ctx.String("host")
|
||||
}
|
||||
err = WriteAppFiles(cfgPath, appinfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func EditRequest(cfgPath string) func(ctx *cli.Context) error {
|
||||
return func(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 1 || ctx.NArg() > 1 {
|
||||
return errors.New("create req must have exactly one argument")
|
||||
}
|
||||
|
||||
app, err := getApp(cfgPath, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqName := ctx.Args().Get(0)
|
||||
|
||||
if !valid(app) || !appExists(cfgPath, app) {
|
||||
return errors.New("application does not exist")
|
||||
}
|
||||
if !valid(reqName) {
|
||||
return errors.New("request name is invalid")
|
||||
}
|
||||
|
||||
path := ReqPath(cfgPath, app, reqName)
|
||||
contents, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return errors.New("request does not exists")
|
||||
}
|
||||
reqinfo := new(RequestInfo)
|
||||
err = yaml.Unmarshal(contents, reqinfo)
|
||||
if err != nil {
|
||||
return errors.New("request info is malformed or corrupted")
|
||||
}
|
||||
|
||||
if ctx.String("description") != "" {
|
||||
reqinfo.Description = ctx.String("description")
|
||||
}
|
||||
if ctx.String("method") != "" {
|
||||
reqinfo.Method = ctx.String("method")
|
||||
}
|
||||
if ctx.String("path") != "" {
|
||||
reqinfo.Path = ctx.String("path")
|
||||
}
|
||||
if ctx.String("body") != "" {
|
||||
reqinfo.Body = ctx.String("body")
|
||||
}
|
||||
if ctx.StringSlice("header") != nil {
|
||||
reqinfo.Headers = ctx.StringSlice("header")
|
||||
}
|
||||
|
||||
if err := WriteRequestFiles(cfgPath, app, reqinfo); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteApplication(cfgPath string) func(ctx *cli.Context) error {
|
||||
return func(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 1 || ctx.NArg() > 1 {
|
||||
return errors.New("delete app must have exactly one argument")
|
||||
}
|
||||
|
||||
app := ctx.Args().Get(0)
|
||||
|
||||
if !valid(app) {
|
||||
return errors.New("application name is invalid")
|
||||
}
|
||||
|
||||
_path := AppPath(cfgPath, app)
|
||||
|
||||
files, err := os.ReadDir(_path)
|
||||
if err != nil {
|
||||
return errors.New("application does not exist")
|
||||
}
|
||||
fmt.Printf(
|
||||
"You are about to delete the application %s and %d associated request(s).\nThis action cannot be undone.\n",
|
||||
app,
|
||||
len(files)-1, // -1 because .appinfo isnt a req
|
||||
)
|
||||
if ctx.Bool("confirm") || ConfirmPrompt() {
|
||||
err := os.RemoveAll(_path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Print("application " + app + " has been deleted")
|
||||
return nil
|
||||
}
|
||||
fmt.Println("delete aborted")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func DeleteRequest(cfgPath string) func(ctx *cli.Context) error {
|
||||
return func(ctx *cli.Context) error {
|
||||
|
||||
if ctx.NArg() < 1 || ctx.NArg() > 1 {
|
||||
return errors.New("delete request must have exactly one argument")
|
||||
}
|
||||
|
||||
app, err := getApp(cfgPath, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqName := ctx.Args().Get(0)
|
||||
|
||||
if !valid(reqName) {
|
||||
return errors.New("request name is invalid")
|
||||
}
|
||||
if !valid(app) || !appExists(cfgPath, app) {
|
||||
return errors.New("application does not exist")
|
||||
}
|
||||
|
||||
_path := ReqPath(cfgPath, app, reqName)
|
||||
|
||||
if _, err := os.Stat(_path); err != nil {
|
||||
return errors.New("request does not exist")
|
||||
}
|
||||
|
||||
fmt.Printf(
|
||||
"You are about to delete the request %s in application %s.\nThis action cannot be undone.\n",
|
||||
reqName,
|
||||
app,
|
||||
)
|
||||
if ctx.Bool("confirm") || ConfirmPrompt() {
|
||||
err := os.Remove(_path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Print("request " + reqName + " has been deleted")
|
||||
return nil
|
||||
}
|
||||
fmt.Println("delete aborted")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func Switch(cfgPath string) func(ctx *cli.Context) error {
|
||||
return func(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 1 {
|
||||
return errors.New("expected argument")
|
||||
}
|
||||
if ctx.NArg() > 1 {
|
||||
return errors.New("expected exactly one argument")
|
||||
}
|
||||
app := ctx.Args().Get(0)
|
||||
if !valid(app) {
|
||||
return errors.New("application does not exist")
|
||||
}
|
||||
if _, err := os.Stat(AppPath(cfgPath, app)); err != nil {
|
||||
return errors.New("application does not exist")
|
||||
}
|
||||
err := os.WriteFile(path.Join(cfgPath, "current_app"), []byte(ctx.Args().First()), 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Print(app)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func ListApplications(cfgPath string) func(ctx *cli.Context) error {
|
||||
return func(ctx *cli.Context) error {
|
||||
_path := AppPath(cfgPath, "")
|
||||
dirs, err := os.ReadDir(_path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var apps string
|
||||
for _, dir := range dirs {
|
||||
appinfo := new(AppInfo)
|
||||
contents, err := os.ReadFile(AppInfoFilePath(cfgPath, dir.Name()))
|
||||
if err != nil {
|
||||
return errors.New("failed to read application info")
|
||||
}
|
||||
err = yaml.Unmarshal(contents, appinfo)
|
||||
if err != nil {
|
||||
return errors.New(".appinfo is malformed or corrupted")
|
||||
}
|
||||
if appinfo.Description == "" {
|
||||
apps += appinfo.Name + "\n"
|
||||
} else {
|
||||
apps += appinfo.Name + ": " + appinfo.Description + "\n"
|
||||
}
|
||||
}
|
||||
fmt.Print(apps)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func ListRequests(cfgPath string) func(ctx *cli.Context) error {
|
||||
return func(ctx *cli.Context) error {
|
||||
app, err := getApp(cfgPath, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_path := AppPath(cfgPath, app)
|
||||
files, err := os.ReadDir(_path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reqs := ""
|
||||
for _, file := range files {
|
||||
if file.Name() == ".appinfo" {
|
||||
continue
|
||||
}
|
||||
reqinfo := new(RequestInfo)
|
||||
contents, err := os.ReadFile(path.Join(AppPath(cfgPath, app), file.Name()))
|
||||
if err != nil {
|
||||
return errors.New("failed to read request info")
|
||||
}
|
||||
err = yaml.Unmarshal(contents, reqinfo)
|
||||
if err != nil {
|
||||
return errors.New("request file is malformed or corrupted")
|
||||
}
|
||||
if reqinfo.Description == "" {
|
||||
reqs += reqinfo.Name + "\n"
|
||||
} else {
|
||||
reqs += reqinfo.Name + ": " + reqinfo.Description + "\n"
|
||||
}
|
||||
}
|
||||
fmt.Print(reqs)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func ListAll(cfgPath string) func(ctx *cli.Context) error {
|
||||
return func(ctx *cli.Context) error {
|
||||
apps, err := os.ReadDir(AppPath(cfgPath, ""))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
output := ""
|
||||
if len(apps) < 1 {
|
||||
fmt.Println("No applications or requests")
|
||||
return nil
|
||||
}
|
||||
for _, app := range apps {
|
||||
reqfiles, err := os.ReadDir(AppPath(cfgPath, app.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(reqfiles) <= 1 {
|
||||
output += app.Name() + "\n"
|
||||
} else {
|
||||
output += app.Name() + ":\n"
|
||||
}
|
||||
for _, reqfile := range reqfiles {
|
||||
if reqfile.Name() == ".appinfo" {
|
||||
continue
|
||||
}
|
||||
reqinfo := new(RequestInfo)
|
||||
contents, err := os.ReadFile(path.Join(AppPath(cfgPath, app.Name()), reqfile.Name()))
|
||||
if err != nil {
|
||||
return errors.New("failed to read request info")
|
||||
}
|
||||
err = yaml.Unmarshal(contents, reqinfo)
|
||||
if err != nil {
|
||||
return errors.New("request file is malformed or corrupted")
|
||||
}
|
||||
if reqinfo.Description == "" {
|
||||
output += "\t- " + reqinfo.Name + "\n"
|
||||
} else {
|
||||
output += "\t- " + reqinfo.Name + ": " + reqinfo.Description + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Print(output)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func InfoApplication(cfgPath string) func(ctx *cli.Context) error {
|
||||
return func(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 1 {
|
||||
return errors.New("expected argument")
|
||||
}
|
||||
if ctx.NArg() > 1 {
|
||||
return errors.New("expected exactly one argument")
|
||||
}
|
||||
if !valid(ctx.Args().Get(0)) {
|
||||
return errors.New("application name is invalid")
|
||||
}
|
||||
contents, err := os.ReadFile(AppInfoFilePath(cfgPath, ctx.Args().Get(0)))
|
||||
if err != nil {
|
||||
return errors.New("failed to read app info")
|
||||
}
|
||||
appinfo := new(AppInfo)
|
||||
err = yaml.Unmarshal(contents, appinfo)
|
||||
if err != nil {
|
||||
return errors.New("failed to read app info")
|
||||
}
|
||||
fmt.Printf("%s:\n\tDescription: %s\n\tHost: %s\n", appinfo.Name, appinfo.Description, appinfo.Host)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func InfoRequest(cfgPath string) func(ctx *cli.Context) error {
|
||||
return func(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 1 {
|
||||
return errors.New("expected argument")
|
||||
}
|
||||
if ctx.NArg() > 1 {
|
||||
return errors.New("expected exactly one argument")
|
||||
}
|
||||
app, err := getApp(cfgPath, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !valid(app) {
|
||||
return errors.New("application name is invalid")
|
||||
}
|
||||
if !valid(ctx.Args().Get(0)) {
|
||||
return errors.New("request name is invalid")
|
||||
}
|
||||
contents, err := os.ReadFile(ReqPath(cfgPath, app, ctx.Args().Get(0)))
|
||||
if err != nil {
|
||||
return errors.New("failed to read request info")
|
||||
}
|
||||
fileLines := strings.Split(string(contents), "\n")
|
||||
fmt.Println(ctx.Args().Get(0) + ":\n\t" + strings.Join(fileLines[2:len(fileLines)-1], "\n\t"))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type VerboseCallResponse struct {
|
||||
Request string `yaml:"Request"`
|
||||
RequestBody string `yaml:"RequestBody"`
|
||||
Headers map[string]string `yaml:"Headers"`
|
||||
Latency string `yaml:"Latency"`
|
||||
Status string `yaml:"Status"`
|
||||
ResponseBody string `yaml:"ResponseBody"`
|
||||
}
|
||||
|
||||
func Call(cfgPath string, httpClient http.Client) func(ctx *cli.Context) error {
|
||||
return func(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 1 {
|
||||
return errors.New("expected argument")
|
||||
}
|
||||
if ctx.NArg() > 1 {
|
||||
return errors.New("expected exactly one argument")
|
||||
}
|
||||
app, err := getApp(cfgPath, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !valid(app) {
|
||||
return errors.New("application name is invalid")
|
||||
}
|
||||
if !valid(ctx.Args().Get(0)) {
|
||||
return errors.New("request name is invalid")
|
||||
}
|
||||
// retrieve app information
|
||||
contents, err := os.ReadFile(AppInfoFilePath(cfgPath, app))
|
||||
if err != nil {
|
||||
return errors.New("failed to retrieve app information")
|
||||
}
|
||||
appinfo := new(AppInfo)
|
||||
err = yaml.Unmarshal(contents, appinfo)
|
||||
if err != nil {
|
||||
return errors.New("appinfo file is malformed or corrupted")
|
||||
}
|
||||
// retrieve request information
|
||||
contents, err = os.ReadFile(ReqPath(cfgPath, app, ctx.Args().Get(0)))
|
||||
if err != nil {
|
||||
return errors.New("failed to retrieve request information")
|
||||
}
|
||||
reqinfo := new(RequestInfo)
|
||||
err = yaml.Unmarshal(contents, reqinfo)
|
||||
if err != nil {
|
||||
return errors.New("request file is malformed or corrupted")
|
||||
}
|
||||
body := bytes.NewBuffer([]byte(reqinfo.Body))
|
||||
req, err := http.NewRequest(reqinfo.Method, appinfo.Host+reqinfo.Path, body)
|
||||
if err != nil {
|
||||
return errors.New("failed to create web request")
|
||||
}
|
||||
for _, header := range reqinfo.Headers {
|
||||
h := strings.Split(header, ": ")
|
||||
if len(h) < 2 {
|
||||
return errors.New("malformed header(s)")
|
||||
}
|
||||
req.Header.Add(h[0], h[1])
|
||||
}
|
||||
if ctx.Bool("no-redirect") {
|
||||
httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }
|
||||
}
|
||||
t1 := time.Now()
|
||||
resp, err := httpClient.Do(req)
|
||||
t2 := time.Now()
|
||||
if err != nil {
|
||||
return errors.New("failed to send web request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if ctx.Bool("fail") && resp.StatusCode >= 400 {
|
||||
return errors.New("")
|
||||
}
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return errors.New("failed to read response body")
|
||||
}
|
||||
if !ctx.Bool("verbose") {
|
||||
fmt.Print(string(respBody) + "\n")
|
||||
} else if req.Method == "GET" {
|
||||
var out VerboseCallResponse
|
||||
out.Request = req.Method + " " + req.URL.String()
|
||||
out.RequestBody = reqinfo.Body
|
||||
out.Headers = make(map[string]string)
|
||||
for _, header := range reqinfo.Headers {
|
||||
k := strings.Split(header, ": ")[0]
|
||||
v := strings.Split(header, ": ")[1]
|
||||
out.Headers[k] = v
|
||||
}
|
||||
out.Latency = fmt.Sprintf("%v", t2.Sub(t1))
|
||||
out.Status = resp.Status
|
||||
out.ResponseBody = string(respBody)
|
||||
o, err := yaml.Marshal(out)
|
||||
if err != nil {
|
||||
return errors.New("failed to generate command output")
|
||||
}
|
||||
fmt.Print(string(o))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
618
action/actions_test.go
Normal file
618
action/actions_test.go
Normal file
|
|
@ -0,0 +1,618 @@
|
|||
package action_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gabehf/sp9rk/action"
|
||||
"github.com/gabehf/sp9rk/app"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// pretty much all the other tests rely on this test working...
|
||||
// i know its bad practice but I dont want to manually create every mock app and request files
|
||||
// manually every time i make the test.
|
||||
//
|
||||
// Basically, if this test fails, fix it FIRST.
|
||||
func TestWriteFiles(t *testing.T) {
|
||||
cfgPath := path.Join("TestActionWriteFiles", ".sp9rk", "tests")
|
||||
err := action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Version: "1",
|
||||
Name: "TestApp",
|
||||
Description: "test app",
|
||||
Host: "localhost",
|
||||
})
|
||||
assert.NoError(t, err, "FIX FIRST: failed to write app files")
|
||||
p := action.AppPath(cfgPath, "TestApp")
|
||||
_, err = os.Stat(p)
|
||||
assert.NoError(t, err, "failed to create app directory")
|
||||
_, err = os.Stat(path.Join(p, ".appinfo"))
|
||||
assert.NoError(t, err, "failed to create .appinfo file")
|
||||
err = action.WriteRequestFiles(cfgPath, "TestApp", &action.RequestInfo{
|
||||
Version: "1",
|
||||
Name: "MyReq",
|
||||
Description: "my request",
|
||||
})
|
||||
assert.NoError(t, err, "FIX FIRST: failed to write request file")
|
||||
_, err = os.Stat(path.Join(p, "MyReq.yml"))
|
||||
assert.NoError(t, err, "failed to stat request file")
|
||||
os.RemoveAll("TestActionWriteFiles")
|
||||
}
|
||||
|
||||
func TestActionCreateApplication(t *testing.T) {
|
||||
cfgPath := path.Join("TestActionCreateApplication", ".sp9rk", "tests")
|
||||
app := app.New(cfgPath, http.Client{})
|
||||
assert.Error(
|
||||
t,
|
||||
RunWithArgs(app, "create", "app"),
|
||||
"create app should fail with no args",
|
||||
)
|
||||
assert.Error(
|
||||
t,
|
||||
RunWithArgs(app, "create", "app", "-u", "hostname123", "../malicious/path"),
|
||||
"create app should fail invalid name",
|
||||
)
|
||||
assert.Error(
|
||||
t,
|
||||
RunWithArgs(app, "create", "app", "-u", "hostname123", "TestApp", "secondArgument"),
|
||||
"create app should fail with >1 args",
|
||||
)
|
||||
assert.NoError(
|
||||
t,
|
||||
RunWithArgs(app, "create", "app", "-u", "hostname123", "TestApp"),
|
||||
"create app should succeed with 1 args",
|
||||
)
|
||||
assert.Error(
|
||||
t,
|
||||
RunWithArgs(app, "create", "app", "-u", "hostname123", "TestApp"),
|
||||
"create app should fail when app already exists",
|
||||
)
|
||||
// cleanup
|
||||
os.RemoveAll("TestActionCreateApplication")
|
||||
}
|
||||
func TestActionCreateRequest(t *testing.T) {
|
||||
cfgPath := path.Join("TestActionCreateRequest", ".sp9rk", "tests")
|
||||
// simulate apps being created
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestApp",
|
||||
})
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestAppTwo",
|
||||
})
|
||||
// tests
|
||||
app := app.New(cfgPath, http.Client{})
|
||||
|
||||
assert.Error(t, RunWithArgs(app, "create", "req"), "create req should fail with no args")
|
||||
assert.Error(t, RunWithArgs(app, "create", "req", "TestReq"), "create req should fail with no default app")
|
||||
os.WriteFile(path.Join(cfgPath, "current_app"), []byte("TestApp"), 0700)
|
||||
assert.NoError(t, RunWithArgs(app, "create", "req", "TestReq"), "create req should succeed with default app")
|
||||
assert.Error(t, RunWithArgs(app, "create", "req", "TestReq", "SecondArg"), "create req should fail with >1 args")
|
||||
assert.NoError(t, RunWithArgs(app, "create", "req", "-a", "TestAppTwo", "TestReq"), "create req should succeed with specified app")
|
||||
assert.Error(t, RunWithArgs(app, "create", "req", "-a", "TestAppThree", "TestReq"), "create req should fail with unknown app")
|
||||
assert.Error(t, RunWithArgs(app, "create", "req", "../malicious/path"), "create req should fail invalid name")
|
||||
assert.Error(t, RunWithArgs(app, "create", "req", "-a", "TestAppTwo", "TestReq"), "create req should fail if it already exists")
|
||||
contents, _ := os.ReadFile(path.Join(action.AppPath(cfgPath, "TestApp"), "TestReq.yml"))
|
||||
reqinfo := new(action.RequestInfo)
|
||||
err := yaml.Unmarshal(contents, reqinfo)
|
||||
assert.NoError(t, err, "contents should be YAML")
|
||||
assert.EqualValues(t, reqinfo.Name, "TestReq")
|
||||
assert.EqualValues(t, reqinfo.Description, "")
|
||||
assert.EqualValues(t, reqinfo.Method, "GET")
|
||||
assert.EqualValues(t, reqinfo.Path, "/")
|
||||
assert.EqualValues(t, reqinfo.Body, "")
|
||||
assert.Empty(t, reqinfo.Headers)
|
||||
|
||||
// flag tests
|
||||
assert.NoError(
|
||||
t,
|
||||
RunWithArgs(app, "create", "req",
|
||||
"-d", "description",
|
||||
"-X", "POST",
|
||||
"-p", "/path",
|
||||
"-b", "request body",
|
||||
"-H", "X-Header-One: 1",
|
||||
"-H", "X-Header-Two: 2",
|
||||
"TestReqTwo",
|
||||
),
|
||||
"create req should succeed with flags",
|
||||
)
|
||||
contents, _ = os.ReadFile(path.Join(action.AppPath(cfgPath, "TestApp"), "TestReqTwo.yml"))
|
||||
reqinfo = new(action.RequestInfo)
|
||||
err = yaml.Unmarshal(contents, reqinfo)
|
||||
assert.NoError(t, err, "contents should be YAML")
|
||||
if reqinfo.Headers == nil || len(reqinfo.Headers) < 2 {
|
||||
assert.FailNow(t, "file contents were not parsed successfully")
|
||||
}
|
||||
assert.EqualValues(t, reqinfo.Name, "TestReqTwo")
|
||||
assert.EqualValues(t, reqinfo.Description, "description")
|
||||
assert.EqualValues(t, reqinfo.Method, "POST")
|
||||
assert.EqualValues(t, reqinfo.Path, "/path")
|
||||
assert.EqualValues(t, reqinfo.Body, "request body")
|
||||
assert.EqualValues(t, reqinfo.Headers[0], "X-Header-One: 1")
|
||||
assert.EqualValues(t, reqinfo.Headers[1], "X-Header-Two: 2")
|
||||
// cleanup
|
||||
os.RemoveAll("TestActionCreateRequest")
|
||||
}
|
||||
func TestActionSwitch(t *testing.T) {
|
||||
cfgPath := path.Join("TestActionSwitch", ".sp9rk", "tests")
|
||||
app := app.New(cfgPath, http.Client{})
|
||||
assert.Error(t, RunWithArgs(app, "switch"), "switch should fail with no args")
|
||||
assert.Error(t, RunWithArgs(app, "switch", "TestApp"), "switch should fail with no apps existing")
|
||||
// simulate apps being created and one set as default
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestApp",
|
||||
})
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestAppTwo",
|
||||
})
|
||||
os.WriteFile(path.Join(cfgPath, "current_app"), []byte("TestApp"), 0700)
|
||||
// tests
|
||||
assert.Error(t, RunWithArgs(app, "switch"), "switch should fail with no args")
|
||||
output, err := captureOutput(RunWithArgs, app, "switch", "TestApp")
|
||||
assert.NoError(t, err, "switch should NOT fail with existing app")
|
||||
assert.Equal(t, "TestApp", output, "switch output should be equal to new app name on success")
|
||||
assert.Error(t, RunWithArgs(app, "switch", "../malicious/path"), "switch should fail invalid name")
|
||||
assert.Error(t, RunWithArgs(app, "switch", "TestApp", "somethingElse"), "switch should fail with >1 arg")
|
||||
assert.Error(t, RunWithArgs(app, "switch", "NotRealApp"), "switch should fail with unknown app")
|
||||
os.RemoveAll("TestActionSwitch")
|
||||
}
|
||||
func TestActionDeleteApp(t *testing.T) {
|
||||
cfgPath := path.Join("TestActionDeleteApp", ".sp9rk", "tests")
|
||||
// simulate apps being created
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestApp",
|
||||
Description: "this is a very cool app that does cool stuff",
|
||||
Host: "http://localhost:3000",
|
||||
})
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestAppTwo",
|
||||
Description: "this is another very cool app that does cool stuff",
|
||||
Host: "http://localhost:3001",
|
||||
})
|
||||
// tests
|
||||
app := app.New(cfgPath, http.Client{})
|
||||
assert.Error(t, RunWithArgs(app, "delete", "app"), "delete app should fail with no args")
|
||||
assert.NoError(t, RunWithArgs(app, "delete", "app", "--confirm", "TestApp"), "delete app should NOT fail with existing app")
|
||||
assert.Error(t, RunWithArgs(app, "delete", "app", "../malicious/path"), "delete app should fail invalid name")
|
||||
assert.Error(t, RunWithArgs(app, "delete", "app", "TestAppTwo", "somethingElse"), "delete app should fail with >1 arg")
|
||||
assert.Error(t, RunWithArgs(app, "delete", "app", "NotRealApp"), "delete app should fail with unknown app")
|
||||
assert.NoError(t, RunWithArgs(app, "delete", "app", "--confirm", "TestAppTwo"), "delete app should NOT fail with existing app")
|
||||
assert.Error(t, RunWithArgs(app, "delete", "app", "TestAppTwo"), "delete app should fail with deleted app")
|
||||
os.RemoveAll("TestActionDeleteApp")
|
||||
}
|
||||
func TestActionDeleteRequest(t *testing.T) {
|
||||
cfgPath := path.Join("TestActionDeleteRequest", ".sp9rk", "tests")
|
||||
// simulate apps being created
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestApp",
|
||||
Description: "this is a very cool app that does cool stuff",
|
||||
Host: "http://localhost:3000",
|
||||
})
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestAppTwo",
|
||||
Description: "this is a very cool app that does cool stuff",
|
||||
Host: "http://localhost:3001",
|
||||
})
|
||||
action.WriteRequestFiles(cfgPath, "TestApp", &action.RequestInfo{
|
||||
Version: "1",
|
||||
Name: "MyReq",
|
||||
})
|
||||
action.WriteRequestFiles(cfgPath, "TestAppTwo", &action.RequestInfo{
|
||||
Version: "1",
|
||||
Name: "AnotherReq",
|
||||
})
|
||||
// tests
|
||||
app := app.New(cfgPath, http.Client{})
|
||||
assert.Error(t, RunWithArgs(app, "delete", "req"), "delete req should fail with no args")
|
||||
assert.Error(t, RunWithArgs(app, "delete", "req", "--confirm", "MyReq"), "delete req should fail with no default app")
|
||||
assert.NoError(t, RunWithArgs(app, "delete", "req", "--confirm", "-a", "TestApp", "MyReq"), "delete req should NOT fail with existing req")
|
||||
assert.Error(t, RunWithArgs(app, "delete", "req", "-a", "TestApp", "MyReq"), "delete req should fail with deleted req")
|
||||
assert.Error(t, RunWithArgs(app, "delete", "req", "-a", "TestApp", "../malicious/path"), "delete req should fail invalid name")
|
||||
// create default app
|
||||
os.WriteFile(path.Join(cfgPath, "current_app"), []byte("TestAppTwo"), 0700)
|
||||
assert.Error(t, RunWithArgs(app, "delete", "req", "AnotherReq", "somethingElse"), "delete req should fail with >1 arg")
|
||||
assert.Error(t, RunWithArgs(app, "delete", "req", "NotRealApp"), "delete req should fail with unknown req")
|
||||
assert.NoError(t, RunWithArgs(app, "delete", "req", "--confirm", "AnotherReq"), "delete req should NOT fail with default app and existing req")
|
||||
assert.Error(t, RunWithArgs(app, "delete", "req", "AnotherReq"), "delete req should fail with deleted req")
|
||||
os.RemoveAll("TestActionDeleteRequest")
|
||||
// tests
|
||||
}
|
||||
|
||||
func TestActionEditApp(t *testing.T) {
|
||||
cfgPath := path.Join("TestActionEditApp", ".sp9rk", "tests")
|
||||
// simulate apps being created
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestApp",
|
||||
})
|
||||
// tests
|
||||
app := app.New(cfgPath, http.Client{})
|
||||
assert.NoError(t,
|
||||
RunWithArgs(app, "edit", "app", "-d", "new description", "-u", "http://123.456.789", "TestApp"),
|
||||
"edit app should not fail with 1 arg and flags",
|
||||
)
|
||||
contents, err := os.ReadFile(action.AppInfoFilePath(cfgPath, "TestApp"))
|
||||
assert.NoError(t, err, "new .appinfo file should exist")
|
||||
appinfo := new(action.AppInfo)
|
||||
err = yaml.Unmarshal(contents, appinfo)
|
||||
assert.NoError(t, err, "new .appinfo file should be correctly formed")
|
||||
assert.EqualValues(t, "new description", appinfo.Description, "description should be updated")
|
||||
assert.EqualValues(t, "http://123.456.789", appinfo.Host, "host should be updated")
|
||||
assert.Error(t, RunWithArgs(app, "edit", "app"), "edit app should fail with no args")
|
||||
assert.NoError(t, RunWithArgs(app, "edit", "app", "TestApp"), "edit app should NOT fail with no flags")
|
||||
os.RemoveAll("TestActionEditApp")
|
||||
}
|
||||
|
||||
func TestActionEditRequest(t *testing.T) {
|
||||
cfgPath := path.Join("TestActionEditRequest", ".sp9rk", "tests")
|
||||
// simulate apps being created
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestApp",
|
||||
})
|
||||
action.WriteRequestFiles(cfgPath, "TestApp", &action.RequestInfo{
|
||||
Name: "Req",
|
||||
})
|
||||
action.WriteRequestFiles(cfgPath, "TestApp", &action.RequestInfo{
|
||||
Name: "ReqTwo",
|
||||
Path: "/path",
|
||||
Description: "hello",
|
||||
Body: "request body",
|
||||
Method: "POST",
|
||||
Headers: []string{"Header: One"},
|
||||
})
|
||||
os.WriteFile(path.Join(cfgPath, "current_app"), []byte("TestApp"), 0700)
|
||||
// tests
|
||||
app := app.New(cfgPath, http.Client{})
|
||||
assert.NoError(t,
|
||||
RunWithArgs(app, "edit", "req",
|
||||
"-a", "TestApp",
|
||||
"-d", "new description",
|
||||
"-X", "POST",
|
||||
"-b", "new body",
|
||||
"-H", "X-Header: ABC",
|
||||
"-p", "/new-path",
|
||||
"Req",
|
||||
),
|
||||
"edit app should not fail with 1 arg and flags",
|
||||
)
|
||||
contents, err := os.ReadFile(path.Join(action.AppPath(cfgPath, "TestApp"), "Req.yml"))
|
||||
assert.NoError(t, err, "new request file should exist")
|
||||
reqinfo := new(action.RequestInfo)
|
||||
err = yaml.Unmarshal(contents, reqinfo)
|
||||
assert.NoError(t, err, "new request file should be correctly formed")
|
||||
assert.EqualValues(t, "new description", reqinfo.Description, "description should be updated")
|
||||
assert.EqualValues(t, "POST", reqinfo.Method, "method should be updated")
|
||||
assert.EqualValues(t, "new body", reqinfo.Body, "body should be updated")
|
||||
assert.EqualValues(t, "X-Header: ABC", reqinfo.Headers[0], "header(s) should be updated")
|
||||
assert.EqualValues(t, "/new-path", reqinfo.Path, "path should be updated")
|
||||
assert.Error(t, RunWithArgs(app, "edit", "req"), "edit req should fail with no args")
|
||||
assert.NoError(t, RunWithArgs(app, "edit", "req", "ReqTwo"), "edit req should NOT fail with no flags")
|
||||
assert.NoError(t, RunWithArgs(app, "edit", "req", "-d", "new description", "ReqTwo"), "edit req should NOT fail when updating description")
|
||||
contents, _ = os.ReadFile(path.Join(action.AppPath(cfgPath, "TestApp"), "ReqTwo.yml"))
|
||||
reqinfo = new(action.RequestInfo)
|
||||
yaml.Unmarshal(contents, reqinfo)
|
||||
assert.EqualValues(t, "Header: One", reqinfo.Headers[0], "header(s) should NOT be overwritten when not updated")
|
||||
assert.EqualValues(t, "/path", reqinfo.Path, "path should NOT be overwritten when not updated")
|
||||
assert.EqualValues(t, "request body", reqinfo.Body, "header(s) should NOT be overwritten when not updated")
|
||||
assert.EqualValues(t, "POST", reqinfo.Method, "method should NOT be overwritten when not updated")
|
||||
os.RemoveAll("TestActionEditRequest")
|
||||
}
|
||||
|
||||
func TestActionListApps(t *testing.T) {
|
||||
cfgPath := path.Join("TestActionListApps", ".sp9rk", "tests")
|
||||
// simulate apps being created
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestApp",
|
||||
})
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "AnotherApp",
|
||||
Description: "another one",
|
||||
})
|
||||
// tests
|
||||
expected := "AnotherApp: another one\nTestApp\n"
|
||||
app := app.New(cfgPath, http.Client{})
|
||||
out, err := captureOutput(RunWithArgs, app, "list", "app")
|
||||
assert.NoError(t, err, "list app should NOT generate an error")
|
||||
assert.EqualValues(t, expected, out, "list app should be alphabetical")
|
||||
os.RemoveAll("TestActionListApps")
|
||||
}
|
||||
|
||||
func TestActionListRequests(t *testing.T) {
|
||||
cfgPath := path.Join("TestActionListRequests", ".sp9rk", "tests")
|
||||
// simulate apps being created
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestAppTwo",
|
||||
})
|
||||
action.WriteRequestFiles(cfgPath, "TestAppTwo", &action.RequestInfo{
|
||||
Name: "Req",
|
||||
})
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestApp",
|
||||
})
|
||||
action.WriteRequestFiles(cfgPath, "TestApp", &action.RequestInfo{
|
||||
Name: "MyReq",
|
||||
Description: "my request",
|
||||
})
|
||||
action.WriteRequestFiles(cfgPath, "TestApp", &action.RequestInfo{
|
||||
Name: "AnotherReq",
|
||||
})
|
||||
os.WriteFile(path.Join(cfgPath, "current_app"), []byte("TestApp"), 0700)
|
||||
// tests
|
||||
expected := "AnotherReq\nMyReq: my request\n"
|
||||
app := app.New(cfgPath, http.Client{})
|
||||
out, err := captureOutput(RunWithArgs, app, "list", "req")
|
||||
assert.NoError(t, err, "list req should NOT generate an error")
|
||||
assert.EqualValues(t, expected, out, "list req should succeed with default app")
|
||||
expected = "Req\n"
|
||||
out, err = captureOutput(RunWithArgs, app, "list", "req", "-a", "TestAppTwo")
|
||||
assert.NoError(t, err, "list req should NOT generate an error")
|
||||
assert.EqualValues(t, expected, out, "list req should succeed with specific app")
|
||||
os.RemoveAll("TestActionListRequests")
|
||||
}
|
||||
|
||||
func TestActionListAll(t *testing.T) {
|
||||
cfgPath := path.Join("TestActionListAll", ".sp9rk", "tests")
|
||||
// simulate apps being created
|
||||
|
||||
app := app.New(cfgPath, http.Client{})
|
||||
|
||||
expected := "No applications or requests\n"
|
||||
out, err := captureOutput(RunWithArgs, app, "list")
|
||||
assert.NoError(t, err, "list all with nothing loaded should NOT generate an error")
|
||||
assert.EqualValues(t, expected, out, "list should generate expected output")
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestAppTwo",
|
||||
})
|
||||
expected = "TestAppTwo\n"
|
||||
out, err = captureOutput(RunWithArgs, app, "list")
|
||||
assert.NoError(t, err, "list req should NOT generate an error")
|
||||
assert.EqualValues(t, expected, out, "list should generate expected output")
|
||||
|
||||
action.WriteRequestFiles(cfgPath, "TestAppTwo", &action.RequestInfo{
|
||||
Name: "Req",
|
||||
})
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestApp",
|
||||
})
|
||||
action.WriteRequestFiles(cfgPath, "TestApp", &action.RequestInfo{
|
||||
Name: "MyReq",
|
||||
Description: "my request",
|
||||
})
|
||||
action.WriteRequestFiles(cfgPath, "TestApp", &action.RequestInfo{
|
||||
Name: "AnotherReq",
|
||||
})
|
||||
expected = `TestApp:
|
||||
- AnotherReq
|
||||
- MyReq: my request
|
||||
TestAppTwo:
|
||||
- Req
|
||||
`
|
||||
out, err = captureOutput(RunWithArgs, app, "list")
|
||||
assert.NoError(t, err, "list req should NOT generate an error")
|
||||
assert.EqualValues(t, expected, out, "list should generate expected output")
|
||||
os.RemoveAll("TestActionListAll")
|
||||
}
|
||||
|
||||
func TestActionAppInfo(t *testing.T) {
|
||||
cfgPath := path.Join("TestActionAppInfo", ".sp9rk", "tests")
|
||||
// simulate apps being created
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestApp",
|
||||
Description: "test application",
|
||||
Host: "http://123.456.789",
|
||||
})
|
||||
// tests
|
||||
expected := `TestApp:
|
||||
Description: test application
|
||||
Host: http://123.456.789
|
||||
`
|
||||
app := app.New(cfgPath, http.Client{})
|
||||
out, err := captureOutput(RunWithArgs, app, "info", "app", "TestApp")
|
||||
assert.NoError(t, err, "list req should NOT generate an error")
|
||||
assert.EqualValues(t, expected, out, "list should generate expected output")
|
||||
assert.Error(t, RunWithArgs(app, "info", "app"), "info app should generate an error with no args")
|
||||
os.RemoveAll("TestActionAppInfo")
|
||||
}
|
||||
|
||||
func TestActionRequestInfo(t *testing.T) {
|
||||
cfgPath := path.Join("TestActionRequestInfo", ".sp9rk", "tests")
|
||||
// simulate apps being created
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestApp",
|
||||
})
|
||||
action.WriteRequestFiles(cfgPath, "TestApp", &action.RequestInfo{
|
||||
Name: "MyReq",
|
||||
Method: "GET",
|
||||
Description: "my request",
|
||||
Path: "/path",
|
||||
Body: "request body",
|
||||
Headers: []string{
|
||||
"X-API-Key: ABC123",
|
||||
},
|
||||
})
|
||||
// tests
|
||||
expected := `MyReq:
|
||||
description: my request
|
||||
method: GET
|
||||
path: /path
|
||||
headers:
|
||||
- 'X-API-Key: ABC123'
|
||||
body: request body
|
||||
`
|
||||
app := app.New(cfgPath, http.Client{})
|
||||
out, err := captureOutput(RunWithArgs, app, "info", "req", "-a", "TestApp", "MyReq")
|
||||
assert.NoError(t, err, "list req should NOT generate an error")
|
||||
assert.EqualValues(t, expected, out, "list should generate expected output")
|
||||
os.WriteFile(path.Join(cfgPath, "current_app"), []byte("TestApp"), 0700)
|
||||
out, err = captureOutput(RunWithArgs, app, "info", "req", "MyReq")
|
||||
assert.NoError(t, err, "list req should NOT generate an error")
|
||||
assert.EqualValues(t, expected, out, "list should generate expected output with default app")
|
||||
assert.Error(t, RunWithArgs(app, "info", "req"), "info req should generate an error with no args")
|
||||
os.RemoveAll("TestActionRequestInfo")
|
||||
}
|
||||
|
||||
type ClientMock struct{}
|
||||
|
||||
func (c *ClientMock) Do(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
Status: "OK",
|
||||
StatusCode: 200,
|
||||
Body: io.NopCloser(strings.NewReader("Hello, world!")),
|
||||
}, nil
|
||||
}
|
||||
func TestActionCall(t *testing.T) {
|
||||
cfgPath := path.Join("TestActionCall", ".sp9rk", "tests")
|
||||
// simulate apps being created
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.EqualValues(t, "/path", r.URL.Path, "path is incorrect")
|
||||
assert.EqualValues(t, r.Header.Get("X-API-Key"), "ABC123", "headers are incorrect")
|
||||
assert.EqualValues(t, r.Method, "GET", "method is incorrect")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`Hello, World!`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestApp",
|
||||
Host: server.URL,
|
||||
})
|
||||
action.WriteRequestFiles(cfgPath, "TestApp", &action.RequestInfo{
|
||||
Name: "MyReq",
|
||||
Method: "GET",
|
||||
Description: "my request",
|
||||
Path: "/path",
|
||||
Body: "request body",
|
||||
Headers: []string{
|
||||
"X-API-Key: ABC123",
|
||||
},
|
||||
})
|
||||
// test basic functionality
|
||||
app := app.New(cfgPath, http.Client{})
|
||||
assert.Error(t, RunWithArgs(app, "call"), "call should fail with no argument")
|
||||
assert.Error(t, RunWithArgs(app, "call", "MyReq"), "call should fail with no default app")
|
||||
assert.Error(t, RunWithArgs(app, "call", "../bad/path"), "call should fail invalid request name")
|
||||
assert.Error(t, RunWithArgs(app, "call", "-a", "../bad/app/path", "MyReq"), "call should fail with invalid app name")
|
||||
assert.Error(t, RunWithArgs(app, "call", "-a", "TestApp", "FakeReq"), "call should fail with unknown app")
|
||||
assert.Error(t, RunWithArgs(app, "call", "MyReq"), "call should fail with no default app")
|
||||
output, err := captureOutput(RunWithArgs, app, "call", "-a", "TestApp", "MyReq")
|
||||
assert.EqualValues(t, "Hello, World!\n", output, "call output should be response body")
|
||||
assert.NoError(t, err, "call should succeed with explicit app")
|
||||
os.WriteFile(path.Join(cfgPath, "current_app"), []byte("TestApp"), 0700)
|
||||
output, err = captureOutput(RunWithArgs, app, "call", "MyReq")
|
||||
assert.NoError(t, err, "call should succeed with default app")
|
||||
assert.EqualValues(t, "Hello, World!\n", output, "call output should be response body")
|
||||
|
||||
// test extended functionality (flags)
|
||||
output, err = captureOutput(RunWithArgs, app, "call", "--verbose", "MyReq")
|
||||
assert.NoError(t, err, "call should succeed with verbose flag")
|
||||
respData := new(action.VerboseCallResponse)
|
||||
err = yaml.Unmarshal([]byte(output), respData)
|
||||
assert.NoError(t, err, "output should be valid yaml")
|
||||
assert.Equal(t, "GET "+server.URL+"/path", respData.Request, "request is incorrect")
|
||||
assert.NotEmpty(t, respData.Latency, "latency must not be empty")
|
||||
assert.Equal(t, "request body", respData.RequestBody, "request body is incorrect")
|
||||
assert.Equal(t, "200 OK", respData.Status, "response status is incorrect")
|
||||
assert.NotNil(t, respData.Headers, "request headers must not be nil")
|
||||
assert.Equal(t, "ABC123", respData.Headers["X-API-Key"], "request headers are incorrect")
|
||||
assert.Equal(t, "Hello, World!", respData.ResponseBody, "response body is incorrect")
|
||||
os.RemoveAll("TestActionCall")
|
||||
}
|
||||
|
||||
func TestActionCallFail(t *testing.T) {
|
||||
cfgPath := path.Join("TestActionCallFail", ".sp9rk", "tests")
|
||||
// make a new server that will make every request return >=400 status
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte(`Hello, World!`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestApp",
|
||||
Host: server.URL,
|
||||
})
|
||||
action.WriteRequestFiles(cfgPath, "TestApp", &action.RequestInfo{
|
||||
Name: "MyReq",
|
||||
Method: "GET",
|
||||
Description: "my request",
|
||||
Path: "/path",
|
||||
Body: "request body",
|
||||
Headers: []string{
|
||||
"X-API-Key: ABC123",
|
||||
},
|
||||
})
|
||||
os.WriteFile(path.Join(cfgPath, "current_app"), []byte("TestApp"), 0700)
|
||||
app := app.New(cfgPath, http.Client{})
|
||||
output, err := captureOutput(RunWithArgs, app, "call", "--fail", "MyReq")
|
||||
assert.Error(t, err, "call should fail with fail flag")
|
||||
assert.Empty(t, output, "failed request should be silent with fail flag")
|
||||
os.RemoveAll("TestActionCallFail")
|
||||
}
|
||||
|
||||
// TODO test redirects
|
||||
func TestActionCallRedirects(t *testing.T) {
|
||||
cfgPath := path.Join("TestActionCallLocation", ".sp9rk", "tests")
|
||||
// make a new server that will make every request return >=400 status
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/hello" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`Hello, World!`))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(404)
|
||||
}))
|
||||
defer server.Close()
|
||||
server2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/redirect-me" {
|
||||
w.Header().Add("Location", server.URL+"/hello")
|
||||
w.WriteHeader(http.StatusMovedPermanently)
|
||||
w.Write([]byte(`Don't follow me!`))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(404)
|
||||
}))
|
||||
defer server2.Close()
|
||||
|
||||
action.WriteAppFiles(cfgPath, &action.AppInfo{
|
||||
Name: "TestApp",
|
||||
Host: server2.URL,
|
||||
})
|
||||
action.WriteRequestFiles(cfgPath, "TestApp", &action.RequestInfo{
|
||||
Name: "MyReq",
|
||||
Method: "GET",
|
||||
Description: "my request",
|
||||
Path: "/redirect-me",
|
||||
})
|
||||
os.WriteFile(path.Join(cfgPath, "current_app"), []byte("TestApp"), 0700)
|
||||
app := app.New(cfgPath, http.Client{})
|
||||
output, err := captureOutput(RunWithArgs, app, "call", "MyReq")
|
||||
assert.NoError(t, err, "call should not fail when following valid redirect")
|
||||
assert.Equal(t, "Hello, World!\n", output, "output should be equal to response body")
|
||||
output, err = captureOutput(RunWithArgs, app, "call", "--no-redirect", "MyReq")
|
||||
assert.NoError(t, err, "call should not fail when not following redirect")
|
||||
assert.Equal(t, "Don't follow me!\n", output, "output should be equal to response body")
|
||||
os.RemoveAll("TestActionCallLocation")
|
||||
}
|
||||
|
||||
// helper functions for testing
|
||||
func RunWithArgs(app *cli.App, args ...string) error {
|
||||
a := os.Args[0:1]
|
||||
a = append(a, args...)
|
||||
return app.Run(a)
|
||||
}
|
||||
func captureOutput(f func(*cli.App, ...string) error, app *cli.App, args ...string) (string, error) {
|
||||
orig := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
err := f(app, args...)
|
||||
os.Stdout = orig
|
||||
w.Close()
|
||||
out, _ := io.ReadAll(r)
|
||||
return string(out), err
|
||||
}
|
||||
52
action/files.go
Normal file
52
action/files.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package action
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type RequestInfo struct {
|
||||
Version string `yaml:"version"`
|
||||
Name string `yaml:"name"`
|
||||
Description string `yaml:"description"`
|
||||
Method string `yaml:"method"`
|
||||
Path string `yaml:"path"`
|
||||
Headers []string `yaml:"headers"`
|
||||
Body string `yaml:"body"`
|
||||
}
|
||||
|
||||
func WriteRequestFiles(cfgPath, app string, req *RequestInfo) error {
|
||||
data, err := yaml.Marshal(req)
|
||||
if err != nil {
|
||||
return errors.New("failed to marshal data")
|
||||
}
|
||||
path := path.Join(AppPath(cfgPath, app), req.Name+".yml")
|
||||
return os.WriteFile(path, data, 0700)
|
||||
}
|
||||
|
||||
type AppInfo struct {
|
||||
Version string `yaml:"version"`
|
||||
Name string `yaml:"name"`
|
||||
Description string `yaml:"description"`
|
||||
Host string `yaml:"host"`
|
||||
}
|
||||
|
||||
func WriteAppFiles(cfgPath string, app *AppInfo) error {
|
||||
data, err := yaml.Marshal(app)
|
||||
if err != nil {
|
||||
return errors.New("failed to marshal data")
|
||||
}
|
||||
err = os.MkdirAll(AppPath(cfgPath, app.Name), 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(AppInfoFilePath(cfgPath, app.Name), data, 0700)
|
||||
if err != nil {
|
||||
os.Remove(AppPath(cfgPath, app.Name))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
78
action/helpers.go
Normal file
78
action/helpers.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package action
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// If the --app flag is set, returns that app. Otherwise, returns the default app.
|
||||
// If no default exists, returns an error.
|
||||
func getApp(cfgPath string, ctx *cli.Context) (string, error) {
|
||||
var app string
|
||||
if ctx.String("app") == "" {
|
||||
app = currentApp(cfgPath)
|
||||
if app == "" {
|
||||
return "", errors.New("application does not exist")
|
||||
}
|
||||
} else {
|
||||
app = ctx.String("app")
|
||||
}
|
||||
return app, nil
|
||||
}
|
||||
|
||||
// "" if no current app is set
|
||||
func currentApp(cfgPath string) string {
|
||||
if _, err := os.Stat(path.Join(cfgPath, "current_app")); err != nil {
|
||||
return ""
|
||||
}
|
||||
app, err := os.ReadFile(path.Join(cfgPath, "current_app"))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(app)
|
||||
}
|
||||
|
||||
// TRUE if string is alphanumeric with - or _
|
||||
func valid(name string) bool {
|
||||
return regexp.MustCompile(`^[a-zA-Z0-9_-]*$`).MatchString(name)
|
||||
}
|
||||
|
||||
// TRUE if app folder exists
|
||||
func appExists(cfgPath, app string) bool {
|
||||
_, err := os.Stat(AppPath(cfgPath, app))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func ConfirmPrompt() bool {
|
||||
fmt.Print("Are you sure? [y/N]: ")
|
||||
r := bufio.NewReader(os.Stdin)
|
||||
res, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// if empty, default to N
|
||||
if len(res) < 2 {
|
||||
return false
|
||||
}
|
||||
return strings.ToLower(strings.TrimSpace(res))[0] == 'y'
|
||||
}
|
||||
|
||||
func AppPath(cfgPath, app string) string {
|
||||
return path.Join(cfgPath, "apps", app)
|
||||
}
|
||||
|
||||
func AppInfoFilePath(cfgPath, app string) string {
|
||||
return path.Join(AppPath(cfgPath, app), ".appinfo")
|
||||
}
|
||||
|
||||
func ReqPath(cfgPath, app, req string) string {
|
||||
return path.Join(cfgPath, "apps", app, req+".yml")
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue