Skip to content

Commit

Permalink
Add tests for quality checks with soda cli
Browse files Browse the repository at this point in the history
  • Loading branch information
stefannegele committed Nov 17, 2023
1 parent 73e4ee2 commit fb620b2
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 17 deletions.
6 changes: 6 additions & 0 deletions exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package datacontract

import "os/exec"

// allow mocking command execution
var cmdCombinedOutput = (*exec.Cmd).CombinedOutput
35 changes: 19 additions & 16 deletions quality.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,22 +64,23 @@ func QualityCheck(

tempFile.Write(qualitySpecification)

res, err := sodaQualityCheck(tempFile.Name(), options)
if err != nil {
return fmt.Errorf("quality checks failed: %v", res)
}
err = sodaQualityCheck(tempFile.Name(), options)

// todo
log.Println("Soda CLI output:")
log.Println(res)
printQualityChecksResult(err)

printQualityCheckState()
if err != nil {
return fmt.Errorf("quality checks failed: %w", err)
}

return nil
}

func printQualityCheckState() {
fmt.Println("🟢 quality checks on data contract passed!")
func printQualityChecksResult(err error) {
if err == nil {
log.Println("🟢 quality checks on data contract passed!")
} else {
log.Println("🔴 quality checks on data contract failed!")
}
}

func getQualityType(
Expand Down Expand Up @@ -113,7 +114,9 @@ func getQualitySpecification(
return TakeStringOrMarshall(spec), nil
}

func sodaQualityCheck(qualitySpecFileName string, options QualityCheckOptions) (res string, err error) {
// soda cli checks

func sodaQualityCheck(qualitySpecFileName string, options QualityCheckOptions) error {
var args = []string{"scan"}

args = append(args, "-d")
Expand All @@ -131,13 +134,13 @@ func sodaQualityCheck(qualitySpecFileName string, options QualityCheckOptions) (
args = append(args, qualitySpecFileName)

cmd := exec.Command("soda", args...)
output, err := cmdCombinedOutput(cmd)

log.Println(string(output))

stdout, err := cmd.Output()
if err != nil {
return res, fmt.Errorf("the CLI failed: %v", string(stdout))
return fmt.Errorf("the soda CLI failed: %v", string(output))
}

res = string(stdout)

return res, nil
return nil
}
171 changes: 170 additions & 1 deletion quality_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package datacontract

import "testing"
import (
"errors"
"fmt"
"os"
"os/exec"
"strings"
"testing"
)

func TestPrintQuality(t *testing.T) {
type args struct {
Expand All @@ -27,3 +34,165 @@ func TestPrintQuality(t *testing.T) {
})
}
}

func TestQualityCheck_Soda_Output(t *testing.T) {
type args struct {
dataContractFileName string
pathToType []string
pathToSpecification []string
options QualityCheckOptions
}
tests := []LogOutputTest[args]{
{
name: "success",
args: args{
dataContractFileName: "test_resources/quality/datacontract.yaml",
pathToType: []string{"quality", "type"},
pathToSpecification: []string{"quality", "specification"},
options: QualityCheckOptions{},
},
wantOutput: `output from soda
🟢 quality checks on data contract passed!
`,
},
{
name: "error",
args: args{
dataContractFileName: "test_resources/quality/datacontract.yaml",
pathToType: []string{"quality", "type"},
pathToSpecification: []string{"quality", "specification"},
options: QualityCheckOptions{},
},
wantErr: true,
wantOutput: `output from soda
🔴 quality checks on data contract failed!
`,
},
}

defer func() { cmdCombinedOutput = (*exec.Cmd).CombinedOutput }()

for _, tt := range tests {
cmdCombinedOutput = func(cmd *exec.Cmd) ([]byte, error) {
if tt.name == "success" {
return []byte("output from soda"), nil
} else {
return []byte("output from soda"), errors.New("checks failed")
}
}

RunLogOutputTest(t, tt, "Diff", func() error {
return QualityCheck(tt.args.dataContractFileName, tt.args.pathToType, tt.args.pathToSpecification, tt.args.options)
})
}
}

func TestQualityCheck_Soda_Arguments(t *testing.T) {
otherDatasource := "duckdb_local"
otherConfigurationFileName := "./my-soda-conf.yaml"

type args struct {
dataContractFileName string
pathToType []string
pathToSpecification []string
options QualityCheckOptions
}
tests := []struct {
name string
args args
wantSodaArgs []string
wantErr bool
}{
{
name: "with defaults",
args: args{
dataContractFileName: "test_resources/quality/datacontract.yaml",
pathToType: []string{"quality", "type"},
pathToSpecification: []string{"quality", "specification"},
options: QualityCheckOptions{},
},
wantSodaArgs: []string{"soda", "scan", "-d", "default"},
},
{
name: "with data source option",
args: args{
dataContractFileName: "test_resources/quality/datacontract.yaml",
pathToType: []string{"quality", "type"},
pathToSpecification: []string{"quality", "specification"},
options: QualityCheckOptions{SodaDataSource: &otherDatasource},
},
wantSodaArgs: []string{"soda", "scan", "-d", otherDatasource},
},
{
name: "with configuration file name option",
args: args{
dataContractFileName: "test_resources/quality/datacontract.yaml",
pathToType: []string{"quality", "type"},
pathToSpecification: []string{"quality", "specification"},
options: QualityCheckOptions{SodaConfigurationFileName: &otherConfigurationFileName},
},
wantSodaArgs: []string{"soda", "scan", "-d", "default", "-c", otherConfigurationFileName},
},
{
name: "with all soda options",
args: args{
dataContractFileName: "test_resources/quality/datacontract.yaml",
pathToType: []string{"quality", "type"},
pathToSpecification: []string{"quality", "specification"},
options: QualityCheckOptions{
SodaDataSource: &otherDatasource,
SodaConfigurationFileName: &otherConfigurationFileName,
},
},
wantSodaArgs: []string{"soda", "scan", "-d", otherDatasource, "-c", otherConfigurationFileName},
},
}

defer func() { cmdCombinedOutput = (*exec.Cmd).CombinedOutput }()

for _, tt := range tests {
cmdCombinedOutput = mockSodaCLI(tt.wantSodaArgs)

t.Run(tt.name, func(t *testing.T) {
if err := QualityCheck(tt.args.dataContractFileName, tt.args.pathToType, tt.args.pathToSpecification, tt.args.options); (err != nil) != tt.wantErr {
t.Errorf("QualityCheck() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func mockSodaCLI(wantedArguments []string) func(cmd *exec.Cmd) (res []byte, err error) {
return func(cmd *exec.Cmd) (res []byte, err error) {

if len(wantedArguments) != len(cmd.Args)-1 {
return nil, errors.New("unwanted soda argument length")
}

for i, actualArg := range wantedArguments[0:] {
err = checkArgument(cmd, i, actualArg)
}
err = checkFileNameArgument(cmd)

if err != nil {
return nil, err
}

return []byte{}, nil
}
}

func checkFileNameArgument(cmd *exec.Cmd) error {
fileName := cmd.Args[len(cmd.Args)-1]
expectedPrefix := fmt.Sprintf("%v%v", os.TempDir(), "quality-checks-")
if !strings.HasPrefix(fileName, expectedPrefix) {
return fmt.Errorf("unwanted quality checks filename argument: %v", fileName)
}
return nil
}

func checkArgument(cmd *exec.Cmd, position int, argument string) error {
if cmd.Args[position] != argument {
return fmt.Errorf("invalid argument %v on position %v", argument, position)
}
return nil
}

0 comments on commit fb620b2

Please sign in to comment.