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 }