github.com/actions-on-google/gactions@v3.2.0+incompatible/project/studio_test.go (about) 1 // Copyright 2020 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package studio 16 17 import ( 18 "archive/zip" 19 "bytes" 20 "errors" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "path" 25 "path/filepath" 26 "runtime" 27 "strings" 28 "testing" 29 30 "github.com/actions-on-google/gactions/api/testutils" 31 "github.com/actions-on-google/gactions/project" 32 "github.com/google/go-cmp/cmp" 33 ) 34 35 type MockStudio struct { 36 root string 37 files map[string][]byte 38 clientSecret []byte 39 projectID string 40 } 41 42 func (p MockStudio) ProjectID() string { 43 return p.projectID 44 } 45 46 func NewMock(root string) MockStudio { 47 m := MockStudio{} 48 m.root = root 49 m.files = map[string][]byte{} 50 for k, v := range configFiles { 51 m.files[k] = v 52 } 53 for k, v := range dataFiles { 54 m.files[k] = v 55 } 56 // Add extra files that should be ignored by the CLI 57 m.files[".git"] = []byte("...") 58 folders := []string{ 59 "verticals", 60 "actions", 61 "custom", 62 "custom/global", 63 "custom/intents", 64 "custom/prompts", 65 "custom/scenes", 66 "custom/types", 67 "webhooks/", 68 } 69 for _, v := range folders { 70 m.files[path.Join(v, ".DS_Store")] = []byte("...") 71 } 72 m.files["webhooks/webhook1/.git"] = []byte("...") 73 return m 74 } 75 76 func (MockStudio) Download(sample project.SampleProject, dest string) error { 77 return nil 78 } 79 80 func (MockStudio) AlreadySetup(pathToWorkDir string) bool { 81 return false 82 } 83 84 func (p MockStudio) Files() (map[string][]byte, error) { 85 return p.files, nil 86 } 87 88 func (MockStudio) ClientSecretJSON() ([]byte, error) { 89 return []byte{}, nil 90 } 91 92 func (p MockStudio) ProjectRoot() string { 93 return p.root 94 } 95 96 func obtainProjectDirectory(t *testing.T, got string, dirName string) string { 97 t.Helper() 98 prefix := "" 99 if runtime.GOOS == "darwin" { 100 101 if strings.HasPrefix(got, "/Volumes/BuildData/tmpfs") { 102 prefix = "/Volumes/BuildData/tmpfs" 103 } else { 104 prefix = "/private" 105 } 106 return filepath.Join(prefix, dirName) 107 } 108 // Windows case 109 return filepath.Join(testutils.TestTmpRoot(), dirName) 110 } 111 112 var configFiles = map[string][]byte{ 113 "verticals/character_alarm.yaml": []byte("name: foo"), 114 "actions/actions.yaml": []byte("intent: bar"), 115 "manifest.yaml": []byte("version: 1"), 116 "custom/global/actions.intent.CANCEL.yaml": []byte("transitionToScene: actions.scene.END_CONVERSATION"), 117 "custom/intents/help.yaml": []byte("phrase: hello"), 118 "custom/intents/ru/help.yaml": []byte("phrase: hello"), 119 "custom/prompts/foo.yaml": []byte("prompt: yes"), 120 "custom/prompts/ru/foo.yaml": []byte("prompt: yes"), 121 "custom/scenes/a.yaml": []byte("name: a"), 122 "custom/types/b.yaml": []byte("type: b"), 123 "custom/types/ru/b.yaml": []byte("type: b"), 124 "webhooks/webhook1.yaml": []byte( 125 ` 126 inlineCloudFunction: 127 execute_function: hello 128 `), 129 "webhooks/webhook2.yaml": []byte( 130 ` 131 external_endpoint: 132 base_url: https://google.com 133 http_headers: 134 content-type: application/json 135 endpoint_api_version: 1 136 `), 137 "resources/strings/bundle.yaml": []byte( 138 ` 139 x: "777" 140 y: "777" 141 greeting: "hello world" 142 `), 143 } 144 145 var dataFiles = map[string][]byte{ 146 "resources/images/a.png": []byte("abc123"), 147 "resources/audio/b.mp3": []byte("cde456"), 148 "resources/audio/c.wav": []byte("mno234"), 149 "webhooks/webhook1/index.js": []byte("exports.hello = functions.https.onRequest(app);"), 150 "webhooks/webhook1/package.json": []byte("{}"), 151 "resources/animations/d.flr": []byte("fgh789"), 152 } 153 154 func TestAlreadySetup(t *testing.T) { 155 proj := New([]byte{}, ".") 156 tests := []struct { 157 dirExists bool 158 dirEmpty bool 159 wantIsSetup bool 160 }{ 161 { 162 dirExists: true, 163 dirEmpty: false, 164 wantIsSetup: true, 165 }, 166 { 167 dirExists: true, 168 dirEmpty: true, 169 wantIsSetup: false, 170 }, 171 { 172 dirExists: false, 173 dirEmpty: true, 174 wantIsSetup: false, 175 }, 176 { 177 dirExists: false, 178 dirEmpty: false, 179 wantIsSetup: false, 180 }, 181 } 182 for _, tc := range tests { 183 var dirName string 184 if tc.dirExists { 185 var err error 186 dirName, err = ioutil.TempDir(testutils.TestTmpDir, "actions-sdk-cli-project-folder") 187 if err != nil { 188 t.Errorf("Can't create temporary directory under %q: %v", testutils.TestTmpDir, err) 189 } 190 defer os.RemoveAll(dirName) 191 if !tc.dirEmpty { 192 tempFile, err := ioutil.TempFile(dirName, "actions-sdk-*.yaml") 193 fmt.Printf("tempFile = %v\n", tempFile.Name()) 194 if err != nil { 195 t.Fatalf("can not create tempfile. got %v", err) 196 } 197 defer tempFile.Close() 198 } 199 } 200 if isSetup := proj.AlreadySetup(dirName); isSetup != tc.wantIsSetup { 201 t.Errorf("AlreadySetup returned %v, expected %v, when project directory exists (%v) and is empty (%v)", isSetup, tc.wantIsSetup, tc.dirExists, tc.dirEmpty) 202 } 203 } 204 } 205 206 func TestFilesWhenDirectoryManifestPresent(t *testing.T) { 207 dirName, err := ioutil.TempDir(testutils.TestTmpDir, "actions-sdk-cli-project-folder") 208 if err != nil { 209 t.Fatalf("Can't create temporary directory under %q: %v", testutils.TestTmpDir, err) 210 } 211 proj := New([]byte("secret"), dirName) 212 defer os.RemoveAll(dirName) 213 // first file 214 err = ioutil.WriteFile(filepath.Join(dirName, "manifest.yaml"), []byte("hello"), 0666) 215 if err != nil { 216 t.Fatalf("Can't write a file under %q: %v", dirName, err) 217 } 218 // second file 219 err = ioutil.WriteFile(filepath.Join(dirName, "second-file.yaml"), []byte("world"), 0666) 220 if err != nil { 221 t.Fatalf("Can't create a file under %q: %v", dirName, err) 222 } 223 got, err := proj.Files() 224 if err != nil { 225 t.Errorf("Files got %v, want %v\n", err, nil) 226 } 227 gotNorm := make(map[string][]byte) 228 // strip parent paths to eliminate undeterminism 229 for k, v := range got { 230 gotNorm[filepath.Base(k)] = v 231 } 232 want := map[string][]byte{ 233 "manifest.yaml": []byte("hello"), 234 "second-file.yaml": []byte("world"), 235 } 236 if !cmp.Equal(gotNorm, want) { 237 t.Errorf("Files returned incorrect files, got %v, want %v", got, want) 238 } 239 } 240 241 func TestClientSecretJSON(t *testing.T) { 242 dirName, err := ioutil.TempDir(testutils.TestTmpDir, "actions-sdk-cli-project-folder") 243 if err != nil { 244 t.Fatalf("Can't create temporary directory under %q: %v", testutils.TestTmpDir, err) 245 } 246 defer os.RemoveAll(dirName) 247 want := "{client_id: 123456789}" 248 err = ioutil.WriteFile(filepath.Join(dirName, "test-client-secret.json"), []byte(want), 0666) 249 if err != nil { 250 t.Fatalf("Can't create a file under %q: %v", dirName, err) 251 } 252 proj := Studio{clientSecretJSON: []byte(want)} 253 got, err := proj.ClientSecretJSON() 254 if err != nil { 255 t.Errorf("ClientSecretJSON got %v, want %v", err, nil) 256 } 257 if string(got) != want { 258 t.Errorf("ClientSecretJSON returned incorrect result, got %v, want %v", string(got), want) 259 } 260 } 261 262 func TestConfigFiles(t *testing.T) { 263 p := NewMock(".") 264 want := configFiles 265 files, _ := p.Files() 266 got := ConfigFiles(files) 267 if diff := cmp.Diff(got, want); diff != "" { 268 t.Errorf("ConfigFiles returned %v, want %v, diff %v", got, want, diff) 269 } 270 } 271 272 func TestDataFiles(t *testing.T) { 273 p := NewMock(".") 274 want := map[string][]byte{} 275 // Server expects Cloud Functions to have the filePath stripped 276 // (i.e. webhooks/myfunction/index.js -> ./index.js) 277 for k, v := range dataFiles { 278 if !strings.Contains(k, "resources/") { 279 want[path.Base(k)] = v 280 } else { 281 want[k] = v 282 } 283 } 284 p.files["webhooks/myfunction/node_modules/foo/foo.js"] = []byte("console.log('hello world');") 285 got, err := DataFiles(p) 286 if err != nil { 287 t.Errorf("DataFiles got %v, want %v", err, nil) 288 } 289 if zipped, ok := got["webhooks/webhook1.zip"]; !ok { 290 t.Errorf("DataFiles didn't include webhook1.zip into a map of data files: data files = %v", got) 291 } else { 292 r, err := zip.NewReader(bytes.NewReader(zipped), int64(len(zipped))) 293 if err != nil { 294 t.Fatalf("can not create a zip.NewReader: got %v", err) 295 } 296 for _, f := range r.File { 297 rc, err := f.Open() 298 if err != nil { 299 t.Fatalf("can not open %v: got %v", f.Name, err) 300 } 301 b, err := ioutil.ReadAll(rc) 302 if err != nil { 303 t.Fatalf("can not read from %v: got %v", f.Name, err) 304 } 305 rc.Close() 306 got[f.Name] = b 307 } 308 delete(got, "webhooks/webhook1.zip") 309 } 310 if diff := cmp.Diff(got, want); diff != "" { 311 t.Errorf("DataFiles returned %v, want %v, diff %v", got, want, diff) 312 } 313 } 314 315 func TestAddInlineWebhooksReturnsErrorWithInvalidWebhookYaml(t *testing.T) { 316 p := NewMock(".") 317 p.files["webhooks/malformed_webhook.yaml"] = []byte( 318 ` 319 external_endpoint: 320 base_url: https://google.com 321 endpoint_api_version: 1 322 `) 323 324 err := addInlineWebhooks(map[string][]byte{}, p.files, "") 325 if err == nil || !strings.Contains(err.Error(), "malformed_webhook.yaml has incorrect syntax") { 326 t.Errorf("Expected error not thrown") 327 } 328 } 329 330 func TestProjectIDFound(t *testing.T) { 331 want := "my_project123" 332 files := map[string][]byte{ 333 "settings/settings.yaml": []byte(fmt.Sprintf("projectId: %v", want)), 334 } 335 proj := MockStudio{files: files} 336 got, err := ProjectID(proj) 337 if err != nil { 338 t.Errorf("ProjectID returned %v, want %v", err, nil) 339 } 340 if got != want { 341 t.Errorf("ProjectID returned %v, want %v", got, want) 342 } 343 } 344 345 func TestProjectIDSNotFound(t *testing.T) { 346 files := map[string][]byte{ 347 "manifest.yaml": []byte("version: 1"), 348 } 349 proj := MockStudio{files: files} 350 _, err := ProjectID(proj) 351 if err == nil { 352 t.Errorf("When settings.yaml is absent, ProjectID returned %v, want %v", err, errors.New("can't find a projectId: settings.yaml not found")) 353 } 354 files = map[string][]byte{ 355 "settings.yaml": []byte("display_name: foo"), 356 } 357 proj = MockStudio{files: files} 358 _, err = ProjectID(proj) 359 if err == nil { 360 t.Errorf("When settings.yaml doesn't contain projectId field, ProjectID returned %v, want %v", err, errors.New("projectId is not present in the settings file")) 361 } 362 } 363 364 func TestUnixPath(t *testing.T) { 365 tests := []struct { 366 in string 367 want string 368 }{ 369 { 370 in: "/google/assistant/aog/sdk/", 371 want: "/google/assistant/aog/sdk/", 372 }, 373 { 374 in: "\\google\\assistant\\aog\\sdk", 375 want: "/google/assistant/aog/sdk", 376 }, 377 { 378 in: "foo/", 379 want: "foo/", 380 }, 381 { 382 in: "foo\\", 383 want: "foo/", 384 }, 385 { 386 in: "dir\\to\\foo bar", 387 want: "dir/to/foo bar", 388 }, 389 } 390 for _, tc := range tests { 391 if got := winToUnix(tc.in); got != tc.want { 392 t.Errorf("unixPath returned %v, want %v", got, tc.want) 393 } 394 } 395 } 396 397 func TestSetProjectID(t *testing.T) { 398 tests := []struct { 399 settings []byte 400 flag string 401 want string 402 }{ 403 { // Case 1. 404 settings: nil, 405 flag: "", 406 want: "", 407 }, 408 { // Case 2. 409 settings: []byte("projectId: placeholder_project"), 410 flag: "", 411 want: "placeholder_project", 412 }, 413 { // Case 3. 414 settings: []byte("projectId: hello-world"), 415 flag: "", 416 want: "hello-world", 417 }, 418 { // Case 4. 419 settings: nil, 420 flag: "foobar", 421 want: "foobar", 422 }, 423 { // Case 5. 424 settings: []byte("projectId: placeholder_project"), 425 flag: "hello-world", 426 want: "hello-world", 427 }, 428 { 429 settings: []byte("projectId: hello-world"), 430 flag: "foobar", 431 want: "foobar", 432 }, 433 } 434 for _, tc := range tests { 435 t.Run("foo", func(t *testing.T) { 436 dirName, err := ioutil.TempDir(testutils.TestTmpDir, "actions-sdk-cli-project-folder") 437 if err != nil { 438 t.Fatalf("Can't create temporary directory under %q: %v", testutils.TestTmpDir, err) 439 } 440 defer func() { 441 if err := os.RemoveAll(dirName); err != nil { 442 t.Fatalf("Can't remove temp directory: %v", err) 443 } 444 }() 445 if tc.settings != nil { 446 fp := filepath.Join(dirName, "settings", "settings.yaml") 447 if err := os.MkdirAll(filepath.Dir(fp), 0750); err != nil { 448 t.Fatalf("Can't create settings directory: %v", err) 449 } 450 if err := ioutil.WriteFile(fp, tc.settings, 0640); err != nil { 451 t.Fatalf("Can't create settings file: %v", err) 452 } 453 } 454 studio := New([]byte{}, dirName) 455 if err := (&studio).SetProjectID(tc.flag); err != nil && tc.settings != nil { 456 t.Errorf("SetProjectID returned %v, want %v", err, nil) 457 } 458 if studio.projectID != tc.want { 459 t.Errorf("Project ID is %v after calling SetProjectID, but want %v", studio.projectID, tc.want) 460 } 461 }) 462 } 463 } 464 465 func cloudFuncZip(t *testing.T) []byte { 466 t.Helper() 467 files := map[string][]byte{} 468 for k, v := range dataFiles { 469 if strings.Contains(k, ".js") { 470 files[k] = v 471 } 472 } 473 b, err := zipFiles(files) 474 if err != nil { 475 t.Fatalf("Can not zip %v: %v", files, err) 476 } 477 return b 478 } 479 480 func TestWriteToDiskToNonEmptyDir(t *testing.T) { 481 tests := []struct { 482 user string 483 force bool 484 name string 485 }{ 486 { 487 user: "yes", 488 force: false, 489 name: "User says yes", 490 }, 491 { 492 user: "no", 493 name: "User says no", 494 force: false, 495 }, 496 { 497 user: "", 498 force: true, 499 name: "Force is true", 500 }, 501 } 502 for _, tc := range tests { 503 t.Run(tc.name, func(t *testing.T) { 504 dirName, err := ioutil.TempDir(testutils.TestTmpDir, "actions-sdk-cli-project-folder") 505 if err != nil { 506 t.Fatalf("Can't create temporary directory under %q: %v", testutils.TestTmpDir, err) 507 } 508 defer os.RemoveAll(dirName) 509 proj := NewMock(dirName) 510 og := askYesNo 511 askYesNo = func(msg string) (string, error) { 512 return tc.user, nil 513 } 514 defer func() { 515 askYesNo = og 516 }() 517 if err := ioutil.WriteFile(filepath.Join(dirName, "manifest.yaml"), []byte("version:2.0"), 0640); err != nil { 518 t.Fatalf("Can't write %v: %v", filepath.Join(dirName, "manifest.yaml"), err) 519 } 520 if err := WriteToDisk(proj, "manifest.yaml", "", []byte("version:1.0"), tc.force); err != nil { 521 t.Errorf("WriteToDisk returned %v, want %v", err, nil) 522 } 523 if tc.user == "yes" || tc.force { 524 if !exists(filepath.Join(dirName, "manifest.yaml")) { 525 t.Errorf("WriteToDisk didn't write %v to disk", filepath.Join(dirName, "manifest.yaml")) 526 } 527 b, err := ioutil.ReadFile(filepath.Join(dirName, "manifest.yaml")) 528 if err != nil { 529 t.Errorf("Failed to read %v: %v", filepath.Join(dirName, "manifest.yaml"), err) 530 } 531 if len(b) == 0 { 532 t.Errorf("WriteToDisk wrote empty file %v", filepath.Join(dirName, "manifest.yaml")) 533 } 534 } 535 }) 536 } 537 } 538 539 func TestWriteToDiskToEmptyDir(t *testing.T) { 540 tests := []struct { 541 path string 542 contentType string 543 payload []byte 544 wantFiles []string 545 name string 546 }{ 547 { 548 path: "webhooks/webhook1.zip", 549 contentType: "application/zip;zip_type=cloud_function", 550 payload: cloudFuncZip(t), 551 wantFiles: []string{"webhooks/webhook1/index.js", "webhooks/webhook1/package.json"}, 552 name: "Webhook.zip", 553 }, 554 { 555 path: "settings/en/settings.yaml", 556 contentType: "", 557 payload: []byte("projectId: hello-world"), 558 wantFiles: []string{"settings/en/settings.yaml"}, 559 name: "Settings", 560 }, 561 } 562 for _, tc := range tests { 563 t.Run(tc.name, func(t *testing.T) { 564 dirName, err := ioutil.TempDir(testutils.TestTmpDir, "actions-sdk-cli-project-folder") 565 if err != nil { 566 t.Fatalf("Can't create temporary directory under %q: %v", testutils.TestTmpDir, err) 567 } 568 defer os.RemoveAll(dirName) 569 proj := NewMock(dirName) 570 if err := WriteToDisk(proj, tc.path, tc.contentType, tc.payload, false); err != nil { 571 t.Errorf("WriteToDisk got %v, want %v", err, nil) 572 } 573 for _, f := range tc.wantFiles { 574 fp := path.Join(dirName, f) 575 fp = filepath.FromSlash(fp) 576 if !exists(fp) { 577 t.Errorf("WriteToDisk didn't write %v to disk", fp) 578 } 579 b, err := ioutil.ReadFile(fp) 580 if err != nil { 581 t.Errorf("Failed to read %v: %v", fp, err) 582 } 583 if len(b) == 0 { 584 t.Errorf("WriteToDisk created an empty file %v, want not empty", fp) 585 } 586 } 587 }) 588 } 589 } 590 591 func TestFindProjectRootWithConfig(t *testing.T) { 592 tests := []struct { 593 names []string 594 err error 595 cwd string 596 name string 597 sdkPath string 598 }{ 599 { 600 names: []string{ 601 "manifest.yaml", 602 project.ConfigName, 603 filepath.Join("settings", "settings.yaml"), 604 filepath.Join("mywebhook", "index.js"), 605 }, 606 err: nil, 607 cwd: "", 608 sdkPath: ".", 609 name: "CLI config is found, sdkPath is . and cwd is .", 610 }, 611 { 612 names: []string{ 613 filepath.Join("sdk", "manifest.yaml"), 614 project.ConfigName, 615 filepath.Join("sdk", "settings", "settings.yaml"), 616 filepath.Join("sdk", "mywebhook", "index.js"), 617 }, 618 err: nil, 619 cwd: "", 620 sdkPath: "sdk/", 621 name: "CLI config is found, sdkPath is sdk/ and cwd is .", 622 }, 623 { 624 names: []string{ 625 filepath.Join("settings", "settings.yaml"), 626 filepath.Join("verticals", "foo.yaml"), 627 }, 628 err: fmt.Errorf("%s not found, and manifest is not present", project.ConfigName), 629 cwd: "", 630 name: "CLI config is not found and manifest is not present", 631 }, 632 { 633 names: []string{ 634 filepath.Join("settings", "settings.yaml"), 635 filepath.Join("verticals", "foo.yaml"), 636 project.ConfigName, 637 }, 638 cwd: "settings", 639 err: nil, 640 sdkPath: ".", 641 name: "CLI config is found and cwd is settings", 642 }, 643 { 644 names: []string{ 645 filepath.Join("settings", "settings.yaml"), 646 filepath.Join("verticals", "foo.yaml"), 647 project.ConfigName, 648 }, 649 cwd: "settings", 650 err: errors.New("sdkPath can not be empty"), 651 name: "CLI config is found and cwd is settings", 652 }, 653 } 654 for _, tc := range tests { 655 t.Run(tc.name, func(t *testing.T) { 656 dirName, err := ioutil.TempDir(testutils.TestTmpDir, "actions-sdk-cli-project-folder") 657 if err != nil { 658 t.Fatalf("Can't create temporary directory under %q: %v", testutils.TestTmpDir, err) 659 } 660 defer os.RemoveAll(dirName) 661 for _, f := range tc.names { 662 if err := os.MkdirAll(filepath.Join(dirName, filepath.Dir(f)), 0777); err != nil { 663 t.Errorf("Can't create a directory %v, got %v", filepath.Join(dirName, filepath.Dir(f)), err) 664 } 665 content := []byte("hello") 666 if strings.Contains(f, project.ConfigName) { 667 content = []byte(fmt.Sprintf("sdkPath: %s", tc.sdkPath)) 668 } 669 if err := ioutil.WriteFile(filepath.Join(dirName, f), content, 0666); err != nil { 670 t.Fatalf("Can't write a file under %q: %v", dirName, err) 671 } 672 } 673 674 // wkdir is where CLI config file will be located. 675 wkdir := dirName 676 if tc.cwd != "" { 677 wkdir = filepath.Join(wkdir, tc.cwd) 678 } 679 if err := os.Chdir(wkdir); err != nil { 680 t.Errorf("Could not cd into %v: %v", wkdir, err) 681 } 682 got, err := FindProjectRoot() 683 directory := obtainProjectDirectory(t, got, dirName) 684 if tc.err == nil { 685 if got != filepath.Join(directory, tc.sdkPath) { 686 t.Errorf("findProjectRoot found %v as root, but should get %v", got, filepath.Join(directory, tc.sdkPath)) 687 } 688 } else { 689 if err == nil { 690 t.Errorf("findProjectRoot got %v, want %v", err, tc.err) 691 } 692 } 693 }) 694 } 695 } 696 697 func TestFindProjectRootWithoutConfig(t *testing.T) { 698 tests := []struct { 699 names []string 700 err error 701 cwd string 702 name string 703 }{ 704 { 705 names: []string{ 706 "manifest.yaml", 707 filepath.Join("settings", "settings.yaml"), 708 filepath.Join("mywebhook", "index.js"), 709 }, 710 err: nil, 711 cwd: "", 712 name: "manifest found and cwd is .", 713 }, 714 { 715 names: []string{ 716 filepath.Join("settings", "settings.yaml"), 717 filepath.Join("verticals", "foo.yaml"), 718 }, 719 err: errors.New("manifest.yaml not found"), 720 cwd: "", 721 name: "manifest not found", 722 }, 723 { 724 names: []string{ 725 filepath.Join("settings", "settings.yaml"), 726 filepath.Join("verticals", "foo.yaml"), 727 "manifest.yaml", 728 }, 729 cwd: "settings", 730 err: nil, 731 name: "manifest found and cwd is settings", 732 }, 733 } 734 for _, tc := range tests { 735 t.Run(tc.name, func(t *testing.T) { 736 dirName, err := ioutil.TempDir(testutils.TestTmpDir, "actions-sdk-cli-project-folder") 737 if err != nil { 738 t.Fatalf("Can't create temporary directory under %q: %v", testutils.TestTmpDir, err) 739 } 740 defer os.RemoveAll(dirName) 741 for _, f := range tc.names { 742 if err := os.MkdirAll(filepath.Join(dirName, filepath.Dir(f)), 0777); err != nil { 743 t.Errorf("Can't create a directory %v, got %v", filepath.Join(dirName, filepath.Dir(f)), err) 744 } 745 if err := ioutil.WriteFile(filepath.Join(dirName, f), []byte("hello"), 0666); err != nil { 746 t.Fatalf("Can't write a file under %q: %v", dirName, err) 747 } 748 } 749 wkdir := dirName 750 if tc.cwd != "" { 751 wkdir = filepath.Join(wkdir, tc.cwd) 752 } 753 if err := os.Chdir(wkdir); err != nil { 754 t.Errorf("Could not cd into %v: %v", wkdir, err) 755 } 756 got, err := FindProjectRoot() 757 directory := obtainProjectDirectory(t, got, dirName) 758 if tc.err == nil { 759 if got != directory { 760 t.Errorf("findProjectRoot found %v as root, but should get %v", directory, got) 761 } 762 } else { 763 if err == nil { 764 t.Errorf("findProjectRoot got %v, want %v", err, tc.err) 765 } 766 } 767 }) 768 } 769 }