github.com/qlik-oss/core-cli@v0.3.0/test/corectl_integration_test.go (about)

     1  // +build integration
     2  
     3  package test
     4  
     5  import (
     6  	"encoding/json"
     7  	"flag"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"reflect"
    14  	"runtime"
    15  	"strings"
    16  	"testing"
    17  
    18  	"github.com/kr/pretty"
    19  	enigma "github.com/qlik-oss/enigma-go"
    20  	"github.com/stretchr/testify/assert"
    21  )
    22  
    23  var update = flag.Bool("update", false, "update golden files")
    24  
    25  var engineIP = flag.String("engineIP", "localhost:9076", "dir of package containing embedded files")
    26  var engine2IP = flag.String("engine2IP", "localhost:9176", "dir of package containing embedded files")
    27  
    28  func getBinaryName() string {
    29  	if runtime.GOOS == "windows" {
    30  		return "corectl.exe"
    31  	} else {
    32  		return "corectl"
    33  	}
    34  }
    35  
    36  var binaryName = getBinaryName()
    37  
    38  var binaryPath string
    39  
    40  type testFile struct {
    41  	t    *testing.T
    42  	name string
    43  	dir  string
    44  }
    45  
    46  func newGoldenFile(t *testing.T, name string) *testFile {
    47  	return &testFile{t: t, name: name, dir: "golden"}
    48  }
    49  
    50  func (tf *testFile) path() string {
    51  	tf.t.Helper()
    52  	_, filename, _, ok := runtime.Caller(0)
    53  	if !ok {
    54  		tf.t.Fatal("problems recovering caller information")
    55  	}
    56  
    57  	return filepath.Join(filepath.Dir(filename), tf.dir, tf.name)
    58  }
    59  
    60  func (tf *testFile) write(content string) {
    61  	tf.t.Helper()
    62  	err := ioutil.WriteFile(tf.path(), []byte(content), 0644)
    63  	if err != nil {
    64  		tf.t.Fatalf("could not write %s: %v", tf.name, err)
    65  	}
    66  }
    67  
    68  func diff(expected, actual interface{}) []string {
    69  	return pretty.Diff(expected, actual)
    70  }
    71  
    72  func (tf *testFile) load() string {
    73  	tf.t.Helper()
    74  
    75  	content, err := ioutil.ReadFile(tf.path())
    76  	if err != nil {
    77  		tf.t.Fatalf("could not read file %s: %v", tf.name, err)
    78  	}
    79  
    80  	return string(content)
    81  }
    82  
    83  func TestConnections(t *testing.T) {
    84  	connectToEngine := "--engine=" + *engineIP
    85  	cmd := exec.Command(binaryPath, []string{connectToEngine, "--config=test/project2/corectl.yml", "build", "--connections=test/project2/connections.yml"}...)
    86  	cmd.Run()
    87  	cmd = exec.Command(binaryPath, []string{connectToEngine, "--config=test/project2/corectl.yml", "get", "connections", "--json"}...)
    88  	output, _ := cmd.CombinedOutput()
    89  
    90  	//verify that the connection was created
    91  	var connections []*enigma.Connection
    92  	err := json.Unmarshal(output, &connections)
    93  	assert.NoError(t, err)
    94  	assert.NotNil(t, connections[0])
    95  	assert.NotNil(t, connections[0].Id)
    96  
    97  	//verify that removing the connection works
    98  	cmd = exec.Command(binaryPath, []string{connectToEngine, "--config=test/project2/corectl.yml", "remove", "connection", connections[0].Id}...)
    99  	output, _ = cmd.CombinedOutput()
   100  	assert.Equal(t, "Saving...Done\n\n", string(output))
   101  
   102  	//verify that there is no connections in the app anymore.
   103  	cmd = exec.Command(binaryPath, []string{connectToEngine, "--config=test/project2/corectl.yml", "get", "connections", "--json"}...)
   104  	output, _ = cmd.CombinedOutput()
   105  	assert.Equal(t, "[]\n", string(output))
   106  
   107  	//remove the app as clean-up (Otherwise we might share sessions when we use that app again.)
   108  	_ = exec.Command(binaryPath, []string{connectToEngine, "--config=test/project2/corectl.yml", "remove", "app", "project1.qvf"}...)
   109  }
   110  
   111  func TestCorectl(t *testing.T) {
   112  	connectToEngine := "--engine=" + *engineIP
   113  	connectToEngineWithInccorectLicenseService := "--engine=" + *engine2IP
   114  	tests := []struct {
   115  		name     string
   116  		args     []string
   117  		expected []string
   118  	}{
   119  		{"help 1", []string{""}, []string{"golden", "help-1.golden"}},
   120  		{"help 2", []string{"help"}, []string{"golden", "help-2.golden"}},
   121  		{"help 3", []string{"help", "build"}, []string{"golden", "help-3.golden"}},
   122  		{"project 1 - build", []string{"--config=test/project1/corectl.yml", connectToEngine, "build"}, []string{"Connected", "TableA <<  5 Lines fetched", "TableB <<  5 Lines fetched", "Reload finished successfully", "Saving...Done"}},
   123  		{"project 1 - get tables", []string{"--config=test/project1/corectl.yml ", connectToEngine, "get", "tables"}, []string{"golden", "project1-tables.golden"}},
   124  		{"project 1 - get assoc", []string{"--config=test/project1/corectl.yml ", connectToEngine, "get", "assoc"}, []string{"golden", "project1-assoc.golden"}},
   125  		{"project 1 - get fields", []string{"--config=test/project1/corectl.yml ", connectToEngine, "get", "fields"}, []string{"golden", "project1-fields.golden"}},
   126  		{"project 1 - get field numbers", []string{"--config=test/project1/corectl.yml ", connectToEngine, "get", "field", "numbers"}, []string{"golden", "project1-field-numbers.golden"}},
   127  		{"project 1 - eval", []string{"--config=test/project1/corectl.yml ", connectToEngine, "eval", "count(numbers)", "by", "xyz"}, []string{"golden", "project1-eval-1.golden"}},
   128  		{"project 1 - eval", []string{"--config=test/project1/corectl.yml ", connectToEngine, "eval", "count(numbers)"}, []string{"golden", "project1-eval-2.golden"}},
   129  		{"project 1 - eval", []string{"--config=test/project1/corectl.yml ", connectToEngine, "eval", "=1+1"}, []string{"golden", "project1-eval-3.golden"}},
   130  		{"project 1 - eval", []string{"--config=test/project1/corectl.yml ", connectToEngine, "eval", "1+1"}, []string{"golden", "project1-eval-4.golden"}},
   131  		{"project 1 - eval", []string{"--config=test/project1/corectl.yml ", connectToEngine, "eval", "by", "numbers"}, []string{"golden", "project1-eval-5.golden"}},
   132  		{"project 1 - get objects", []string{"--config=test/project1/corectl.yml ", connectToEngine, "get", "objects"}, []string{"golden", "project1-objects.golden"}},
   133  		{"project 1 - get object data", []string{"--config=test/project1/corectl.yml ", connectToEngine, "get", "object", "data", "my-hypercube"}, []string{"golden", "project1-data.golden"}},
   134  		{"project 1 - get object properties", []string{"--config=test/project1/corectl.yml ", connectToEngine, "get", "object", "properties", "my-hypercube"}, []string{"golden", "project1-properties.golden"}},
   135  		{"project 1 - get object", []string{"--config=test/project1/corectl.yml ", connectToEngine, "get", "object", "my-hypercube"}, []string{"golden", "project1-properties.golden"}},
   136  		{"project 1 - get measures 1", []string{"--config=test/project1/corectl.yml ", connectToEngine, "get", "measures"}, []string{"golden", "project1-measures-1.golden"}},
   137  		{"project 1 - get measures 1 as json", []string{"--config=test/project1/corectl.yml ", connectToEngine, "get", "measures", "--json"}, []string{"golden", "project1-measures-1-json.golden"}},
   138  		{"project 1 - get dimensions", []string{"--config=test/project1/corectl.yml ", connectToEngine, "get", "dimensions"}, []string{"golden", "project1-dimensions.golden"}},
   139  		{"project 1 - get script", []string{"--config=test/project1/corectl.yml ", connectToEngine, "get", "script"}, []string{"golden", "project1-script.golden"}},
   140  		{"project 1 - reload without progress", []string{"--config=test/project1/corectl.yml", connectToEngine, "reload", "--silent"}, []string{"golden", "project1-reload-silent.golden"}},
   141  		{"project 1 - reload without progress and without save", []string{"--config=test/project1/corectl.yml", connectToEngine, "reload", "--silent", "--no-save"}, []string{"golden", "project1-reload-silent-no-save.golden"}},
   142  		{"project 1 - set measures", []string{"--config=test/project1/corectl.yml ", connectToEngine, "set", "measures", "test/project1/not-following-glob-pattern-measure.json", "--no-save"}, []string{"golden", "blank.golden"}},
   143  		{"project 1 - get measures 2", []string{"--config=test/project1/corectl.yml ", connectToEngine, "get", "measures"}, []string{"golden", "project1-measures-2.golden"}},
   144  		{"project 1 - remove measures", []string{"--config=test/project1/corectl.yml ", connectToEngine, "remove", "measures", "measure-3", "--no-save"}, []string{"golden", "blank.golden"}},
   145  		{"project 1 - check measures after removal", []string{"--config=test/project1/corectl.yml ", connectToEngine, "get", "measures"}, []string{"golden", "project1-measures-1.golden"}},
   146  		{"project 1 - set script", []string{"--config=test/project1/corectl.yml ", connectToEngine, "set", "script", "test/project1/dummy-script.qvs", "--no-save"}, []string{"golden", "blank.golden"}},
   147  		{"project 1 - get script after setting it", []string{"--config=test/project1/corectl.yml ", connectToEngine, "get", "script"}, []string{"golden", "project1-script-2.golden"}},
   148  
   149  		// Project 2 has separate connections file
   150  		{"project 2 - build with connections", []string{connectToEngine, "-a=project2.qvf", "--headers=authorization=Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb2xrZSJ9.MD_revuZ8lCEa6bb-qtfYaHdxBiRMUkuH86c4kd1yC0", "build", "--script=test/project2/script.qvs", "--connections=test/project2/connections.yml", "--objects=test/project2/object-*.json"}, []string{"datacsv << data 1 Lines fetched", "Reload finished successfully", "Saving...Done"}},
   151  		{"project 2 - get fields ", []string{"--config=test/project2/corectl.yml ", connectToEngine, "get", "fields"}, []string{"golden", "project2-fields.golden"}},
   152  		{"project 2 - get data", []string{"--config=test/project2/corectl.yml ", connectToEngine, "get", "object", "data", "my-hypercube-on-commandline"}, []string{"golden", "project2-data.golden"}},
   153  
   154  		{"project 3 - build ", []string{"--config=test/project3/corectl.yml ", connectToEngine, "build"}, []string{"No app specified, using session app.", "datacsv << data 1 Lines fetched", "Reload finished successfully"}},
   155  		{"project 3 - get fields", []string{"--config=test/project3/corectl.yml ", connectToEngine, "get", "fields"}, []string{"golden", "project3-fields.golden"}},
   156  		{"err project 1 - invalid-catwalk-url", []string{"--config=test/project1/corectl.yml", connectToEngine, "catwalk", "--catwalk-url=not-a-valid-url"}, []string{"golden", "project1-catwalk-error.golden"}},
   157  		{"err 2", []string{connectToEngine, "--app=nosuchapp.qvf", "--headers=authorization=Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb2xrZSJ9.MD_revuZ8lCEa6bb-qtfYaHdxBiRMUkuH86c4kd1yC0", "eval", "count(numbers)", "by", "xyz"}, []string{"golden", "err-2.golden"}},
   158  		{"err 3", []string{connectToEngine, "--app=project1.qvf", "--headers=authorization=Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb2xrZSJ9.MD_revuZ8lCEa6bb-qtfYaHdxBiRMUkuH86c4kd1yC0", "get", "object", "data", "nosuchobject"}, []string{"golden", "err-3.golden"}},
   159  
   160  		{"project 1 - get status", []string{"--config=test/project1/corectl.yml ", connectToEngine, "get", "status"}, []string{"Connected to project1.qvf @ ", "The data model has 2 tables."}},
   161  		{"list apps", []string{connectToEngine, "--headers=authorization=Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb2xrZSJ9.MD_revuZ8lCEa6bb-qtfYaHdxBiRMUkuH86c4kd1yC0", "get", "apps"}, []string{"Id", "Name", "Last-Reloaded", "ReadOnly", "Title", "project2.qvf", "project1.qvf"}},
   162  		{"list apps json", []string{connectToEngine, "--headers=authorization=Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb2xrZSJ9.MD_revuZ8lCEa6bb-qtfYaHdxBiRMUkuH86c4kd1yC0", "get", "apps", "--json"}, []string{"\"id\": \"/apps/project2.qvf\","}},
   163  		{"err 1", []string{"--engine=localhost:9999", "get", "fields"}, []string{"Please check the --engine parameter or your config file", "Error details:  dial tcp"}},
   164  		// trying to connect to an engine that has JWT authorization activated without a JWT Header
   165  		{"err jwt", []string{connectToEngine, "get", "apps"}, []string{"Error details:  401 from ws server: websocket: bad handshake"}},
   166  		{"err no license", []string{connectToEngineWithInccorectLicenseService, "get", "apps"}, []string{"Failed to connect to engine with error message:  SESSION_ERROR_NO_LICENSE"}},
   167  	}
   168  
   169  	for _, tt := range tests {
   170  		t.Run(tt.name, func(t *testing.T) {
   171  			cmd := exec.Command(binaryPath, tt.args...)
   172  			output, err := cmd.CombinedOutput()
   173  			if strings.HasPrefix(tt.name, "err") {
   174  				if err == nil {
   175  					t.Fatalf("%s\nexpected (err == nil) to be %v, but got %v. err: %v", output, false, err == nil, err)
   176  				}
   177  			} else if err != nil {
   178  				t.Fatalf("%s\nexpected (err != nil) to be %v, but got %v. err: %v", output, false, err != nil, err)
   179  			}
   180  			actual := string(output)
   181  
   182  			if tt.expected[0] == "golden" {
   183  				golden := newGoldenFile(t, tt.expected[1])
   184  
   185  				if *update {
   186  					golden.write(actual)
   187  				}
   188  				expected := golden.load()
   189  
   190  				if !reflect.DeepEqual(expected, actual) {
   191  					t.Fatalf("diff: %v", diff(expected, actual))
   192  				}
   193  			} else {
   194  				for _, sub := range tt.expected {
   195  					if !strings.Contains(actual, sub) {
   196  						t.Fatalf("Output did not contain substring %v", sub)
   197  					}
   198  				}
   199  			}
   200  		})
   201  	}
   202  }
   203  
   204  func TestMain(m *testing.M) {
   205  	err := os.Chdir("..")
   206  	if err != nil {
   207  		fmt.Printf("could not change dir: %v", err)
   208  		os.Exit(1)
   209  	}
   210  
   211  	abs, err := filepath.Abs(binaryName)
   212  	if err != nil {
   213  		fmt.Printf("could not get abs path for %s: %v", binaryName, err)
   214  		os.Exit(1)
   215  	}
   216  
   217  	binaryPath = abs
   218  
   219  	if err := exec.Command("go", "build", "-o", binaryName, "-v").Run(); err != nil {
   220  		fmt.Printf("could not make binary for %s: %v", binaryName, err)
   221  		os.Exit(1)
   222  	}
   223  	os.Exit(m.Run())
   224  }