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