github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/addrs/module_source_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package addrs 5 6 import ( 7 "testing" 8 9 "github.com/google/go-cmp/cmp" 10 svchost "github.com/hashicorp/terraform-svchost" 11 ) 12 13 func TestParseModuleSource(t *testing.T) { 14 tests := map[string]struct { 15 input string 16 want ModuleSource 17 wantErr string 18 }{ 19 // Local paths 20 "local in subdirectory": { 21 input: "./child", 22 want: ModuleSourceLocal("./child"), 23 }, 24 "local in subdirectory non-normalized": { 25 input: "./nope/../child", 26 want: ModuleSourceLocal("./child"), 27 }, 28 "local in sibling directory": { 29 input: "../sibling", 30 want: ModuleSourceLocal("../sibling"), 31 }, 32 "local in sibling directory non-normalized": { 33 input: "./nope/../../sibling", 34 want: ModuleSourceLocal("../sibling"), 35 }, 36 "Windows-style local in subdirectory": { 37 input: `.\child`, 38 want: ModuleSourceLocal("./child"), 39 }, 40 "Windows-style local in subdirectory non-normalized": { 41 input: `.\nope\..\child`, 42 want: ModuleSourceLocal("./child"), 43 }, 44 "Windows-style local in sibling directory": { 45 input: `..\sibling`, 46 want: ModuleSourceLocal("../sibling"), 47 }, 48 "Windows-style local in sibling directory non-normalized": { 49 input: `.\nope\..\..\sibling`, 50 want: ModuleSourceLocal("../sibling"), 51 }, 52 "an abominable mix of different slashes": { 53 input: `./nope\nope/why\./please\don't`, 54 want: ModuleSourceLocal("./nope/nope/why/please/don't"), 55 }, 56 57 // Registry addresses 58 // (NOTE: There is another test function TestParseModuleSourceRegistry 59 // which tests this situation more exhaustively, so this is just a 60 // token set of cases to see that we are indeed calling into the 61 // registry address parser when appropriate.) 62 "main registry implied": { 63 input: "hashicorp/subnets/cidr", 64 want: ModuleSourceRegistry{ 65 Package: ModuleRegistryPackage{ 66 Host: svchost.Hostname("registry.terraform.io"), 67 Namespace: "hashicorp", 68 Name: "subnets", 69 TargetSystem: "cidr", 70 }, 71 Subdir: "", 72 }, 73 }, 74 "main registry implied, subdir": { 75 input: "hashicorp/subnets/cidr//examples/foo", 76 want: ModuleSourceRegistry{ 77 Package: ModuleRegistryPackage{ 78 Host: svchost.Hostname("registry.terraform.io"), 79 Namespace: "hashicorp", 80 Name: "subnets", 81 TargetSystem: "cidr", 82 }, 83 Subdir: "examples/foo", 84 }, 85 }, 86 "main registry implied, escaping subdir": { 87 input: "hashicorp/subnets/cidr//../nope", 88 // NOTE: This error is actually being caught by the _remote package_ 89 // address parser, because any registry parsing failure falls back 90 // to that but both of them have the same subdir validation. This 91 // case is here to make sure that stays true, so we keep reporting 92 // a suitable error when the user writes a registry-looking thing. 93 wantErr: `subdirectory path "../nope" leads outside of the module package`, 94 }, 95 "custom registry": { 96 input: "example.com/awesomecorp/network/happycloud", 97 want: ModuleSourceRegistry{ 98 Package: ModuleRegistryPackage{ 99 Host: svchost.Hostname("example.com"), 100 Namespace: "awesomecorp", 101 Name: "network", 102 TargetSystem: "happycloud", 103 }, 104 Subdir: "", 105 }, 106 }, 107 "custom registry, subdir": { 108 input: "example.com/awesomecorp/network/happycloud//examples/foo", 109 want: ModuleSourceRegistry{ 110 Package: ModuleRegistryPackage{ 111 Host: svchost.Hostname("example.com"), 112 Namespace: "awesomecorp", 113 Name: "network", 114 TargetSystem: "happycloud", 115 }, 116 Subdir: "examples/foo", 117 }, 118 }, 119 120 // Remote package addresses 121 "github.com shorthand": { 122 input: "github.com/hashicorp/terraform-cidr-subnets", 123 want: ModuleSourceRemote{ 124 Package: ModulePackage("git::https://github.com/hashicorp/terraform-cidr-subnets.git"), 125 }, 126 }, 127 "github.com shorthand, subdir": { 128 input: "github.com/hashicorp/terraform-cidr-subnets//example/foo", 129 want: ModuleSourceRemote{ 130 Package: ModulePackage("git::https://github.com/hashicorp/terraform-cidr-subnets.git"), 131 Subdir: "example/foo", 132 }, 133 }, 134 "git protocol, URL-style": { 135 input: "git://example.com/code/baz.git", 136 want: ModuleSourceRemote{ 137 Package: ModulePackage("git://example.com/code/baz.git"), 138 }, 139 }, 140 "git protocol, URL-style, subdir": { 141 input: "git://example.com/code/baz.git//bleep/bloop", 142 want: ModuleSourceRemote{ 143 Package: ModulePackage("git://example.com/code/baz.git"), 144 Subdir: "bleep/bloop", 145 }, 146 }, 147 "git over HTTPS, URL-style": { 148 input: "git::https://example.com/code/baz.git", 149 want: ModuleSourceRemote{ 150 Package: ModulePackage("git::https://example.com/code/baz.git"), 151 }, 152 }, 153 "git over HTTPS, URL-style, subdir": { 154 input: "git::https://example.com/code/baz.git//bleep/bloop", 155 want: ModuleSourceRemote{ 156 Package: ModulePackage("git::https://example.com/code/baz.git"), 157 Subdir: "bleep/bloop", 158 }, 159 }, 160 "git over HTTPS, URL-style, subdir, query parameters": { 161 input: "git::https://example.com/code/baz.git//bleep/bloop?otherthing=blah", 162 want: ModuleSourceRemote{ 163 Package: ModulePackage("git::https://example.com/code/baz.git?otherthing=blah"), 164 Subdir: "bleep/bloop", 165 }, 166 }, 167 "git over SSH, URL-style": { 168 input: "git::ssh://git@example.com/code/baz.git", 169 want: ModuleSourceRemote{ 170 Package: ModulePackage("git::ssh://git@example.com/code/baz.git"), 171 }, 172 }, 173 "git over SSH, URL-style, subdir": { 174 input: "git::ssh://git@example.com/code/baz.git//bleep/bloop", 175 want: ModuleSourceRemote{ 176 Package: ModulePackage("git::ssh://git@example.com/code/baz.git"), 177 Subdir: "bleep/bloop", 178 }, 179 }, 180 "git over SSH, scp-style": { 181 input: "git::git@example.com:code/baz.git", 182 want: ModuleSourceRemote{ 183 // Normalized to URL-style 184 Package: ModulePackage("git::ssh://git@example.com/code/baz.git"), 185 }, 186 }, 187 "git over SSH, scp-style, subdir": { 188 input: "git::git@example.com:code/baz.git//bleep/bloop", 189 want: ModuleSourceRemote{ 190 // Normalized to URL-style 191 Package: ModulePackage("git::ssh://git@example.com/code/baz.git"), 192 Subdir: "bleep/bloop", 193 }, 194 }, 195 196 // NOTE: We intentionally don't test the bitbucket.org shorthands 197 // here, because that detector makes direct HTTP tequests to the 198 // Bitbucket API and thus isn't appropriate for unit testing. 199 200 "Google Cloud Storage bucket implied, path prefix": { 201 input: "www.googleapis.com/storage/v1/BUCKET_NAME/PATH_TO_MODULE", 202 want: ModuleSourceRemote{ 203 Package: ModulePackage("gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH_TO_MODULE"), 204 }, 205 }, 206 "Google Cloud Storage bucket, path prefix": { 207 input: "gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH_TO_MODULE", 208 want: ModuleSourceRemote{ 209 Package: ModulePackage("gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH_TO_MODULE"), 210 }, 211 }, 212 "Google Cloud Storage bucket implied, archive object": { 213 input: "www.googleapis.com/storage/v1/BUCKET_NAME/PATH/TO/module.zip", 214 want: ModuleSourceRemote{ 215 Package: ModulePackage("gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH/TO/module.zip"), 216 }, 217 }, 218 "Google Cloud Storage bucket, archive object": { 219 input: "gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH/TO/module.zip", 220 want: ModuleSourceRemote{ 221 Package: ModulePackage("gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH/TO/module.zip"), 222 }, 223 }, 224 225 "Amazon S3 bucket implied, archive object": { 226 input: "s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip", 227 want: ModuleSourceRemote{ 228 Package: ModulePackage("s3::https://s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip"), 229 }, 230 }, 231 "Amazon S3 bucket, archive object": { 232 input: "s3::https://s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip", 233 want: ModuleSourceRemote{ 234 Package: ModulePackage("s3::https://s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip"), 235 }, 236 }, 237 238 "HTTP URL": { 239 input: "http://example.com/module", 240 want: ModuleSourceRemote{ 241 Package: ModulePackage("http://example.com/module"), 242 }, 243 }, 244 "HTTPS URL": { 245 input: "https://example.com/module", 246 want: ModuleSourceRemote{ 247 Package: ModulePackage("https://example.com/module"), 248 }, 249 }, 250 "HTTPS URL, archive file": { 251 input: "https://example.com/module.zip", 252 want: ModuleSourceRemote{ 253 Package: ModulePackage("https://example.com/module.zip"), 254 }, 255 }, 256 "HTTPS URL, forced archive file": { 257 input: "https://example.com/module?archive=tar", 258 want: ModuleSourceRemote{ 259 Package: ModulePackage("https://example.com/module?archive=tar"), 260 }, 261 }, 262 "HTTPS URL, forced archive file and checksum": { 263 input: "https://example.com/module?archive=tar&checksum=blah", 264 want: ModuleSourceRemote{ 265 // The query string only actually gets processed when we finally 266 // do the get, so "checksum=blah" is accepted as valid up 267 // at this parsing layer. 268 Package: ModulePackage("https://example.com/module?archive=tar&checksum=blah"), 269 }, 270 }, 271 272 "absolute filesystem path": { 273 // Although a local directory isn't really "remote", we do 274 // treat it as such because we still need to do all of the same 275 // high-level steps to work with these, even though "downloading" 276 // is replaced by a deep filesystem copy instead. 277 input: "/tmp/foo/example", 278 want: ModuleSourceRemote{ 279 Package: ModulePackage("file:///tmp/foo/example"), 280 }, 281 }, 282 "absolute filesystem path, subdir": { 283 // This is a funny situation where the user wants to use a 284 // directory elsewhere on their system as a package containing 285 // multiple modules, but the entry point is not at the root 286 // of that subtree, and so they can use the usual subdir 287 // syntax to move the package root higher in the real filesystem. 288 input: "/tmp/foo//example", 289 want: ModuleSourceRemote{ 290 Package: ModulePackage("file:///tmp/foo"), 291 Subdir: "example", 292 }, 293 }, 294 295 "subdir escaping out of package": { 296 // This is general logic for all subdir regardless of installation 297 // protocol, but we're using a filesystem path here just as an 298 // easy placeholder/ 299 input: "/tmp/foo//example/../../invalid", 300 wantErr: `subdirectory path "../invalid" leads outside of the module package`, 301 }, 302 303 "relative path without the needed prefix": { 304 input: "boop/bloop", 305 // For this case we return a generic error message from the addrs 306 // layer, but using a specialized error type which our module 307 // installer checks for and produces an extra hint for users who 308 // were intending to write a local path which then got 309 // misinterpreted as a remote source due to the missing prefix. 310 // However, the main message is generic here because this is really 311 // just a general "this string doesn't match any of our source 312 // address patterns" situation, not _necessarily_ about relative 313 // local paths. 314 wantErr: `Terraform cannot detect a supported external module source type for boop/bloop`, 315 }, 316 317 "go-getter will accept all sorts of garbage": { 318 input: "dfgdfgsd:dgfhdfghdfghdfg/dfghdfghdfg", 319 want: ModuleSourceRemote{ 320 // Unfortunately go-getter doesn't actually reject a totally 321 // invalid address like this until getting time, as long as 322 // it looks somewhat like a URL. 323 Package: ModulePackage("dfgdfgsd:dgfhdfghdfghdfg/dfghdfghdfg"), 324 }, 325 }, 326 } 327 328 for name, test := range tests { 329 t.Run(name, func(t *testing.T) { 330 addr, err := ParseModuleSource(test.input) 331 332 if test.wantErr != "" { 333 switch { 334 case err == nil: 335 t.Errorf("unexpected success\nwant error: %s", test.wantErr) 336 case err.Error() != test.wantErr: 337 t.Errorf("wrong error messages\ngot: %s\nwant: %s", err.Error(), test.wantErr) 338 } 339 return 340 } 341 342 if err != nil { 343 t.Fatalf("unexpected error: %s", err.Error()) 344 } 345 346 if diff := cmp.Diff(addr, test.want); diff != "" { 347 t.Errorf("wrong result\n%s", diff) 348 } 349 }) 350 } 351 352 } 353 354 func TestModuleSourceRemoteFromRegistry(t *testing.T) { 355 t.Run("both have subdir", func(t *testing.T) { 356 remote := ModuleSourceRemote{ 357 Package: ModulePackage("boop"), 358 Subdir: "foo", 359 } 360 registry := ModuleSourceRegistry{ 361 Subdir: "bar", 362 } 363 gotAddr := remote.FromRegistry(registry) 364 if remote.Subdir != "foo" { 365 t.Errorf("FromRegistry modified the reciever; should be pure function") 366 } 367 if registry.Subdir != "bar" { 368 t.Errorf("FromRegistry modified the given address; should be pure function") 369 } 370 if got, want := gotAddr.Subdir, "foo/bar"; got != want { 371 t.Errorf("wrong resolved subdir\ngot: %s\nwant: %s", got, want) 372 } 373 }) 374 t.Run("only remote has subdir", func(t *testing.T) { 375 remote := ModuleSourceRemote{ 376 Package: ModulePackage("boop"), 377 Subdir: "foo", 378 } 379 registry := ModuleSourceRegistry{ 380 Subdir: "", 381 } 382 gotAddr := remote.FromRegistry(registry) 383 if remote.Subdir != "foo" { 384 t.Errorf("FromRegistry modified the reciever; should be pure function") 385 } 386 if registry.Subdir != "" { 387 t.Errorf("FromRegistry modified the given address; should be pure function") 388 } 389 if got, want := gotAddr.Subdir, "foo"; got != want { 390 t.Errorf("wrong resolved subdir\ngot: %s\nwant: %s", got, want) 391 } 392 }) 393 t.Run("only registry has subdir", func(t *testing.T) { 394 remote := ModuleSourceRemote{ 395 Package: ModulePackage("boop"), 396 Subdir: "", 397 } 398 registry := ModuleSourceRegistry{ 399 Subdir: "bar", 400 } 401 gotAddr := remote.FromRegistry(registry) 402 if remote.Subdir != "" { 403 t.Errorf("FromRegistry modified the reciever; should be pure function") 404 } 405 if registry.Subdir != "bar" { 406 t.Errorf("FromRegistry modified the given address; should be pure function") 407 } 408 if got, want := gotAddr.Subdir, "bar"; got != want { 409 t.Errorf("wrong resolved subdir\ngot: %s\nwant: %s", got, want) 410 } 411 }) 412 } 413 414 func TestParseModuleSourceRemote(t *testing.T) { 415 416 tests := map[string]struct { 417 input string 418 wantString string 419 wantForDisplay string 420 wantErr string 421 }{ 422 "git over HTTPS, URL-style, query parameters": { 423 // Query parameters should be correctly appended after the Package 424 input: `git::https://example.com/code/baz.git?otherthing=blah`, 425 wantString: `git::https://example.com/code/baz.git?otherthing=blah`, 426 wantForDisplay: `git::https://example.com/code/baz.git?otherthing=blah`, 427 }, 428 "git over HTTPS, URL-style, subdir, query parameters": { 429 // Query parameters should be correctly appended after the Package and Subdir 430 input: `git::https://example.com/code/baz.git//bleep/bloop?otherthing=blah`, 431 wantString: `git::https://example.com/code/baz.git//bleep/bloop?otherthing=blah`, 432 wantForDisplay: `git::https://example.com/code/baz.git//bleep/bloop?otherthing=blah`, 433 }, 434 } 435 436 for name, test := range tests { 437 t.Run(name, func(t *testing.T) { 438 remote, err := parseModuleSourceRemote(test.input) 439 440 if test.wantErr != "" { 441 switch { 442 case err == nil: 443 t.Errorf("unexpected success\nwant error: %s", test.wantErr) 444 case err.Error() != test.wantErr: 445 t.Errorf("wrong error messages\ngot: %s\nwant: %s", err.Error(), test.wantErr) 446 } 447 return 448 } 449 450 if err != nil { 451 t.Fatalf("unexpected error: %s", err.Error()) 452 } 453 454 if got, want := remote.String(), test.wantString; got != want { 455 t.Errorf("wrong String() result\ngot: %s\nwant: %s", got, want) 456 } 457 if got, want := remote.ForDisplay(), test.wantForDisplay; got != want { 458 t.Errorf("wrong ForDisplay() result\ngot: %s\nwant: %s", got, want) 459 } 460 }) 461 } 462 } 463 464 func TestParseModuleSourceRegistry(t *testing.T) { 465 // We test parseModuleSourceRegistry alone here, in addition to testing 466 // it indirectly as part of TestParseModuleSource, because general 467 // module parsing unfortunately eats all of the error situations from 468 // registry passing by falling back to trying for a direct remote package 469 // address. 470 471 // Historical note: These test cases were originally derived from the 472 // ones in the old internal/registry/regsrc package that the 473 // ModuleSourceRegistry type is replacing. That package had the notion 474 // of "normalized" addresses as separate from the original user input, 475 // but this new implementation doesn't try to preserve the original 476 // user input at all, and so the main string output is always normalized. 477 // 478 // That package also had some behaviors to turn the namespace, name, and 479 // remote system portions into lowercase, but apparently we didn't 480 // actually make use of that in the end and were preserving the case 481 // the user provided in the input, and so for backward compatibility 482 // we're continuing to do that here, at the expense of now making the 483 // "ForDisplay" output case-preserving where its predecessor in the 484 // old package wasn't. The main Terraform Registry at registry.terraform.io 485 // is itself case-insensitive anyway, so our case-preserving here is 486 // entirely for the benefit of existing third-party registry 487 // implementations that might be case-sensitive, which we must remain 488 // compatible with now. 489 490 tests := map[string]struct { 491 input string 492 wantString string 493 wantForDisplay string 494 wantForProtocol string 495 wantErr string 496 }{ 497 "public registry": { 498 input: `hashicorp/consul/aws`, 499 wantString: `registry.terraform.io/hashicorp/consul/aws`, 500 wantForDisplay: `hashicorp/consul/aws`, 501 wantForProtocol: `hashicorp/consul/aws`, 502 }, 503 "public registry with subdir": { 504 input: `hashicorp/consul/aws//foo`, 505 wantString: `registry.terraform.io/hashicorp/consul/aws//foo`, 506 wantForDisplay: `hashicorp/consul/aws//foo`, 507 wantForProtocol: `hashicorp/consul/aws`, 508 }, 509 "public registry using explicit hostname": { 510 input: `registry.terraform.io/hashicorp/consul/aws`, 511 wantString: `registry.terraform.io/hashicorp/consul/aws`, 512 wantForDisplay: `hashicorp/consul/aws`, 513 wantForProtocol: `hashicorp/consul/aws`, 514 }, 515 "public registry with mixed case names": { 516 input: `HashiCorp/Consul/aws`, 517 wantString: `registry.terraform.io/HashiCorp/Consul/aws`, 518 wantForDisplay: `HashiCorp/Consul/aws`, 519 wantForProtocol: `HashiCorp/Consul/aws`, 520 }, 521 "private registry with non-standard port": { 522 input: `Example.com:1234/HashiCorp/Consul/aws`, 523 wantString: `example.com:1234/HashiCorp/Consul/aws`, 524 wantForDisplay: `example.com:1234/HashiCorp/Consul/aws`, 525 wantForProtocol: `HashiCorp/Consul/aws`, 526 }, 527 "private registry with IDN hostname": { 528 input: `Испытание.com/HashiCorp/Consul/aws`, 529 wantString: `испытание.com/HashiCorp/Consul/aws`, 530 wantForDisplay: `испытание.com/HashiCorp/Consul/aws`, 531 wantForProtocol: `HashiCorp/Consul/aws`, 532 }, 533 "private registry with IDN hostname and non-standard port": { 534 input: `Испытание.com:1234/HashiCorp/Consul/aws//Foo`, 535 wantString: `испытание.com:1234/HashiCorp/Consul/aws//Foo`, 536 wantForDisplay: `испытание.com:1234/HashiCorp/Consul/aws//Foo`, 537 wantForProtocol: `HashiCorp/Consul/aws`, 538 }, 539 "invalid hostname": { 540 input: `---.com/HashiCorp/Consul/aws`, 541 wantErr: `invalid module registry hostname "---.com"; internationalized domain names must be given as direct unicode characters, not in punycode`, 542 }, 543 "hostname with only one label": { 544 // This was historically forbidden in our initial implementation, 545 // so we keep it forbidden to avoid newly interpreting such 546 // addresses as registry addresses rather than remote source 547 // addresses. 548 input: `foo/var/baz/qux`, 549 wantErr: `invalid module registry hostname: must contain at least one dot`, 550 }, 551 "invalid target system characters": { 552 input: `foo/var/no-no-no`, 553 wantErr: `invalid target system "no-no-no": must be between one and 64 ASCII letters or digits`, 554 }, 555 "invalid target system length": { 556 input: `foo/var/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah`, 557 wantErr: `invalid target system "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah": must be between one and 64 ASCII letters or digits`, 558 }, 559 "invalid namespace": { 560 input: `boop!/var/baz`, 561 wantErr: `invalid namespace "boop!": must be between one and 64 characters, including ASCII letters, digits, dashes, and underscores, where dashes and underscores may not be the prefix or suffix`, 562 }, 563 "missing part with explicit hostname": { 564 input: `foo.com/var/baz`, 565 wantErr: `source address must have three more components after the hostname: the namespace, the name, and the target system`, 566 }, 567 "errant query string": { 568 input: `foo/var/baz?otherthing`, 569 wantErr: `module registry addresses may not include a query string portion`, 570 }, 571 "github.com": { 572 // We don't allow using github.com like a module registry because 573 // that conflicts with the historically-supported shorthand for 574 // installing directly from GitHub-hosted git repositories. 575 input: `github.com/HashiCorp/Consul/aws`, 576 wantErr: `can't use "github.com" as a module registry host, because it's reserved for installing directly from version control repositories`, 577 }, 578 "bitbucket.org": { 579 // We don't allow using bitbucket.org like a module registry because 580 // that conflicts with the historically-supported shorthand for 581 // installing directly from BitBucket-hosted git repositories. 582 input: `bitbucket.org/HashiCorp/Consul/aws`, 583 wantErr: `can't use "bitbucket.org" as a module registry host, because it's reserved for installing directly from version control repositories`, 584 }, 585 "local path from current dir": { 586 // Can't use a local path when we're specifically trying to parse 587 // a _registry_ source address. 588 input: `./boop`, 589 wantErr: `can't use local directory "./boop" as a module registry address`, 590 }, 591 "local path from parent dir": { 592 // Can't use a local path when we're specifically trying to parse 593 // a _registry_ source address. 594 input: `../boop`, 595 wantErr: `can't use local directory "../boop" as a module registry address`, 596 }, 597 } 598 599 for name, test := range tests { 600 t.Run(name, func(t *testing.T) { 601 addrI, err := ParseModuleSourceRegistry(test.input) 602 603 if test.wantErr != "" { 604 switch { 605 case err == nil: 606 t.Errorf("unexpected success\nwant error: %s", test.wantErr) 607 case err.Error() != test.wantErr: 608 t.Errorf("wrong error messages\ngot: %s\nwant: %s", err.Error(), test.wantErr) 609 } 610 return 611 } 612 613 if err != nil { 614 t.Fatalf("unexpected error: %s", err.Error()) 615 } 616 617 addr, ok := addrI.(ModuleSourceRegistry) 618 if !ok { 619 t.Fatalf("wrong address type %T; want %T", addrI, addr) 620 } 621 622 if got, want := addr.String(), test.wantString; got != want { 623 t.Errorf("wrong String() result\ngot: %s\nwant: %s", got, want) 624 } 625 if got, want := addr.ForDisplay(), test.wantForDisplay; got != want { 626 t.Errorf("wrong ForDisplay() result\ngot: %s\nwant: %s", got, want) 627 } 628 if got, want := addr.Package.ForRegistryProtocol(), test.wantForProtocol; got != want { 629 t.Errorf("wrong ForRegistryProtocol() result\ngot: %s\nwant: %s", got, want) 630 } 631 }) 632 } 633 }