commander-cli/commander

View on GitHub
pkg/app/test_command.go

Summary

Maintainability
A
0 mins
Test Coverage
A
94%
package app

import (
    "bufio"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
    "path"
    "strings"

    "github.com/commander-cli/commander/v2/pkg/output"
    "github.com/commander-cli/commander/v2/pkg/runtime"
    "github.com/commander-cli/commander/v2/pkg/suite"
)

var (
    out                 output.OutputWriter
    overwriteConfigPath string
)

// TestCommand executes the test argument
// testPath is the path to the test suite config, it can be a dir or file
// ctx holds the command flags. If directory scanning is enabled with --dir,
// test filtering is not supported
func TestCommand(testPath string, ctx TestCommandContext) error {
    if ctx.Workdir != "" {
        err := os.Chdir(ctx.Workdir)
        if err != nil {
            return fmt.Errorf(err.Error())
        }
    }

    if ctx.Verbose {
        log.SetOutput(os.Stdout)
    }

    overwriteConfigPath = ctx.Config
    out = output.NewCliOutput(!ctx.NoColor)

    if testPath == "" {
        testPath = CommanderFile
    }

    var result runtime.Result
    var err error
    switch {
    case ctx.Dir:
        fmt.Println("Starting test against directory: " + testPath + "...")
        fmt.Println("")
        result, err = testDir(testPath, ctx.Filters)
    case testPath == "-":
        fmt.Println("Starting test from stdin...")
        fmt.Println("")
        result, err = testStdin(ctx.Filters)
    case isURL(testPath):
        fmt.Println("Starting test from " + testPath + "...")
        fmt.Println("")
        result, err = testURL(testPath, ctx.Filters)
    default:
        fmt.Println("Starting test file " + testPath + "...")
        fmt.Println("")
        result, err = testFile(testPath, "", ctx.Filters)
    }

    if err != nil {
        return fmt.Errorf(err.Error())
    }

    if !out.PrintSummary(result) && !ctx.Verbose {
        return fmt.Errorf("Test suite failed, use --verbose for more detailed output")
    }

    return nil
}

func testFile(filePath string, fileName string, filters runtime.Filters) (runtime.Result, error) {
    s, err := getSuite(filePath, fileName)
    if err != nil {
        return runtime.Result{}, fmt.Errorf("Error " + err.Error())
    }

    return execute(s, filters)
}

func testDir(directory string, filters runtime.Filters) (runtime.Result, error) {
    result := runtime.Result{}
    files, err := os.ReadDir(directory)
    if err != nil {
        return result, fmt.Errorf("Error: Input is not a directory")
    }

    for _, f := range files {
        if f.IsDir() {
            continue // skip dirs
        }

        p := path.Join(directory, f.Name())
        newResult, err := testFile(p, f.Name(), filters)
        if err != nil {
            return result, err
        }

        result = convergeResults(result, newResult)
    }

    return result, nil
}

func testURL(url string, filters runtime.Filters) (runtime.Result, error) {
    resp, err := http.Get(url)
    if err != nil {
        return runtime.Result{}, err
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return runtime.Result{}, err
    }

    s := suite.ParseYAML(body, "")

    return execute(s, filters)
}

func isURL(s string) bool {
    return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://")
}

func convergeResults(result runtime.Result, new runtime.Result) runtime.Result {
    result.TestResults = append(result.TestResults, new.TestResults...)
    result.Failed += new.Failed
    result.Duration += new.Duration

    return result
}

func testStdin(filters runtime.Filters) (runtime.Result, error) {
    f, err := os.Stdin.Stat()
    if err != nil {
        return runtime.Result{}, err
    }

    if (f.Mode() & os.ModeCharDevice) != 0 {
        return runtime.Result{}, fmt.Errorf("Error: when testing from stdin the command is intended to work with pipes")
    }

    r := bufio.NewReader(os.Stdin)
    content, err := io.ReadAll(r)
    s := suite.ParseYAML(content, "")

    return execute(s, filters)
}

func execute(s suite.Suite, filters runtime.Filters) (runtime.Result, error) {
    tests := s.GetTests()
    if len(filters) != 0 {
        tests = []runtime.TestCase{}
    }

    // Filter tests if test filters was given
    for _, f := range filters {
        t, err := s.FindTests(f)
        if err != nil {
            return runtime.Result{}, err
        }
        tests = append(tests, t...)
    }

    r := runtime.NewRuntime(out.GetEventHandler(), s.Nodes...)
    result := r.Start(tests)

    return result, nil
}

func getSuite(filePath string, fileName string) (suite.Suite, error) {
    s := suite.Suite{}

    content, err := readFile(filePath)
    if err != nil {
        return suite.Suite{}, err
    }

    overwriteContent := []byte("")
    if overwriteConfigPath != "" {
        overwriteContent, err = readFile(overwriteConfigPath)
        if err != nil {
            return suite.Suite{}, err
        }
    }

    s = suite.NewSuite(content, overwriteContent, fileName)
    return s, nil
}

func readFile(filePath string) ([]byte, error) {
    f, err := os.Stat(filePath)
    if err != nil {
        return nil, fmt.Errorf("open %s: no such file or directory", filePath)
    }

    if f.IsDir() {
        return nil, fmt.Errorf("%s: is a directory\nUse --dir to test directories with multiple test files", filePath)
    }

    content, err := os.ReadFile(filePath)
    if err != nil {
        return nil, err
    }

    return content, nil
}