1//go:build go1.9
2// +build go1.9
3
4package endpoints
5
6import (
7	"bytes"
8	"encoding/json"
9	"io/ioutil"
10	"log"
11	"net/url"
12	"os"
13	"path/filepath"
14	"reflect"
15	"regexp"
16	"strconv"
17	"strings"
18	"testing"
19)
20
21func TestUnmarshalRegionRegex(t *testing.T) {
22	var input = []byte(`
23{
24    "regionRegex": "^(us|eu|ap|sa|ca)\\-\\w+\\-\\d+$"
25}`)
26
27	p := partition{}
28	err := json.Unmarshal(input, &p)
29	if err != nil {
30		t.Fatalf("expect no error, got %v", err)
31	}
32
33	expectRegexp, err := regexp.Compile(`^(us|eu|ap|sa|ca)\-\w+\-\d+$`)
34	if err != nil {
35		t.Fatalf("expect no error, got %v", err)
36	}
37
38	if e, a := expectRegexp.String(), p.RegionRegex.Regexp.String(); e != a {
39		t.Errorf("expect %v, got %v", e, a)
40	}
41}
42
43func TestUnmarshalRegion(t *testing.T) {
44	var input = []byte(`
45{
46	"aws-global": {
47	  "description": "AWS partition-global endpoint"
48	},
49	"us-east-1": {
50	  "description": "US East (N. Virginia)"
51	}
52}`)
53
54	rs := regions{}
55	err := json.Unmarshal(input, &rs)
56	if err != nil {
57		t.Fatalf("expect no error, got %v", err)
58	}
59
60	if e, a := 2, len(rs); e != a {
61		t.Errorf("expect %v len, got %v", e, a)
62	}
63	r, ok := rs["aws-global"]
64	if !ok {
65		t.Errorf("expect found, was not")
66	}
67	if e, a := "AWS partition-global endpoint", r.Description; e != a {
68		t.Errorf("expect %v, got %v", e, a)
69	}
70
71	r, ok = rs["us-east-1"]
72	if !ok {
73		t.Errorf("expect found, was not")
74	}
75	if e, a := "US East (N. Virginia)", r.Description; e != a {
76		t.Errorf("expect %v, got %v", e, a)
77	}
78}
79
80func TestUnmarshalServices(t *testing.T) {
81	var input = []byte(`
82{
83	"acm": {
84	  "endpoints": {
85		"us-east-1": {}
86	  }
87	},
88	"apigateway": {
89      "isRegionalized": true,
90	  "endpoints": {
91		"us-east-1": {},
92        "us-west-2": {}
93	  }
94	},
95	"notRegionalized": {
96      "isRegionalized": false,
97	  "endpoints": {
98		"us-east-1": {},
99        "us-west-2": {}
100	  }
101	}
102}`)
103
104	ss := services{}
105	err := json.Unmarshal(input, &ss)
106	if err != nil {
107		t.Fatalf("expect no error, got %v", err)
108	}
109
110	if e, a := 3, len(ss); e != a {
111		t.Errorf("expect %v len, got %v", e, a)
112	}
113	s, ok := ss["acm"]
114	if !ok {
115		t.Errorf("expect found, was not")
116	}
117	if e, a := 1, len(s.Endpoints); e != a {
118		t.Errorf("expect %v len, got %v", e, a)
119	}
120	if e, a := boxedBoolUnset, s.IsRegionalized; e != a {
121		t.Errorf("expect %v, got %v", e, a)
122	}
123
124	s, ok = ss["apigateway"]
125	if !ok {
126		t.Errorf("expect found, was not")
127	}
128	if e, a := 2, len(s.Endpoints); e != a {
129		t.Errorf("expect %v len, got %v", e, a)
130	}
131	if e, a := boxedTrue, s.IsRegionalized; e != a {
132		t.Errorf("expect %v, got %v", e, a)
133	}
134
135	s, ok = ss["notRegionalized"]
136	if !ok {
137		t.Errorf("expect found, was not")
138	}
139	if e, a := 2, len(s.Endpoints); e != a {
140		t.Errorf("expect %v len, got %v", e, a)
141	}
142	if e, a := boxedFalse, s.IsRegionalized; e != a {
143		t.Errorf("expect %v, got %v", e, a)
144	}
145}
146
147func TestUnmarshalEndpoints(t *testing.T) {
148	var inputs = []byte(`
149{
150	"aws-global": {
151	  "hostname": "cloudfront.amazonaws.com",
152	  "protocols": [
153		"http",
154		"https"
155	  ],
156	  "signatureVersions": [ "v4" ],
157	  "credentialScope": {
158		"region": "us-east-1",
159		"service": "serviceName"
160	  },
161	  "sslCommonName": "commonName"
162	},
163	"us-east-1": {}
164}`)
165
166	es := serviceEndpoints{}
167	err := json.Unmarshal(inputs, &es)
168	if err != nil {
169		t.Fatalf("expect no error, got %v", err)
170	}
171
172	if e, a := 2, len(es); e != a {
173		t.Errorf("expect %v len, got %v", e, a)
174	}
175	s, ok := es[endpointKey{Region: "aws-global"}]
176	if !ok {
177		t.Errorf("expect found, was not")
178	}
179	if e, a := "cloudfront.amazonaws.com", s.Hostname; e != a {
180		t.Errorf("expect %v, got %v", e, a)
181	}
182	if e, a := []string{"http", "https"}, s.Protocols; !reflect.DeepEqual(e, a) {
183		t.Errorf("expect %v, got %v", e, a)
184	}
185	if e, a := []string{"v4"}, s.SignatureVersions; !reflect.DeepEqual(e, a) {
186		t.Errorf("expect %v, got %v", e, a)
187	}
188	if e, a := (credentialScope{"us-east-1", "serviceName"}), s.CredentialScope; e != a {
189		t.Errorf("expect %v, got %v", e, a)
190	}
191	if e, a := "commonName", s.SSLCommonName; e != a {
192		t.Errorf("expect %v, got %v", e, a)
193	}
194}
195
196func TestEndpointResolve(t *testing.T) {
197	defs := []endpoint{
198		{
199			Hostname:          "{service}.{region}.{dnsSuffix}",
200			SignatureVersions: []string{"v2"},
201			SSLCommonName:     "sslCommonName",
202		},
203		{
204			Hostname:  "other-hostname",
205			Protocols: []string{"http"},
206			CredentialScope: credentialScope{
207				Region:  "signing_region",
208				Service: "signing_service",
209			},
210		},
211	}
212
213	e := endpoint{
214		Hostname:          "{service}.{region}.{dnsSuffix}",
215		Protocols:         []string{"http", "https"},
216		SignatureVersions: []string{"v4"},
217		SSLCommonName:     "new sslCommonName",
218	}
219
220	resolved, err := e.resolve("service", "partitionID", "region", dnsSuffixTemplateKey, "dnsSuffix",
221		defs, Options{},
222	)
223	if err != nil {
224		t.Errorf("expected no error, got %v", err)
225	}
226
227	if e, a := "https://service.region.dnsSuffix", resolved.URL; e != a {
228		t.Errorf("expect %v, got %v", e, a)
229	}
230	if e, a := "signing_service", resolved.SigningName; e != a {
231		t.Errorf("expect %v, got %v", e, a)
232	}
233	if e, a := "signing_region", resolved.SigningRegion; e != a {
234		t.Errorf("expect %v, got %v", e, a)
235	}
236	if e, a := "v4", resolved.SigningMethod; e != a {
237		t.Errorf("expect %v, got %v", e, a)
238	}
239
240	// Check Invalid Region Identifier Format
241	_, err = e.resolve("service", "partitionID", "notvalid.com", dnsSuffixTemplateKey, "dnsSuffix",
242		defs, Options{},
243	)
244	if err == nil {
245		t.Errorf("expected err, got nil")
246	}
247}
248
249func TestEndpointMergeIn(t *testing.T) {
250	expected := endpoint{
251		Hostname:          "other hostname",
252		Protocols:         []string{"http"},
253		SignatureVersions: []string{"v4"},
254		SSLCommonName:     "ssl common name",
255		CredentialScope: credentialScope{
256			Region:  "region",
257			Service: "service",
258		},
259	}
260
261	actual := endpoint{}
262	actual.mergeIn(endpoint{
263		Hostname:          "other hostname",
264		Protocols:         []string{"http"},
265		SignatureVersions: []string{"v4"},
266		SSLCommonName:     "ssl common name",
267		CredentialScope: credentialScope{
268			Region:  "region",
269			Service: "service",
270		},
271	})
272
273	if e, a := expected, actual; !reflect.DeepEqual(e, a) {
274		t.Errorf("expect %v, got %v", e, a)
275	}
276}
277
278func TestResolveEndpoint(t *testing.T) {
279	resolved, err := testPartitions.EndpointFor("service2", "us-west-2")
280
281	if err != nil {
282		t.Fatalf("expect no error, got %v", err)
283	}
284	if e, a := "https://service2.us-west-2.amazonaws.com", resolved.URL; e != a {
285		t.Errorf("expect %v, got %v", e, a)
286	}
287	if e, a := "us-west-2", resolved.SigningRegion; e != a {
288		t.Errorf("expect %v, got %v", e, a)
289	}
290	if e, a := "service2", resolved.SigningName; e != a {
291		t.Errorf("expect %v, got %v", e, a)
292	}
293	if resolved.SigningNameDerived {
294		t.Errorf("expect the signing name not to be derived, but was")
295	}
296}
297
298func TestResolveEndpoint_DisableSSL(t *testing.T) {
299	resolved, err := testPartitions.EndpointFor("service2", "us-west-2", DisableSSLOption)
300
301	if err != nil {
302		t.Fatalf("expect no error, got %v", err)
303	}
304	if e, a := "http://service2.us-west-2.amazonaws.com", resolved.URL; e != a {
305		t.Errorf("expect %v, got %v", e, a)
306	}
307	if e, a := "us-west-2", resolved.SigningRegion; e != a {
308		t.Errorf("expect %v, got %v", e, a)
309	}
310	if e, a := "service2", resolved.SigningName; e != a {
311		t.Errorf("expect %v, got %v", e, a)
312	}
313	if resolved.SigningNameDerived {
314		t.Errorf("expect the signing name not to be derived, but was")
315	}
316}
317
318func TestResolveEndpoint_UseDualStack_UseDualStackEndpoint(t *testing.T) {
319	cases := map[string]struct {
320		Service string
321		Region  string
322
323		Options func(*Options)
324
325		ExpectedURL              string
326		ExpectedSigningName      string
327		ExpectedSigningRegion    string
328		ExpectSigningNameDerived bool
329
330		ExpectErr bool
331	}{
332		"deprecated UseDualStack does not apply to services that are not s3 or s3-control": {
333			Service:                  "ec2",
334			Region:                   "us-west-2",
335			Options:                  UseDualStackOption,
336			ExpectedURL:              "https://ec2.us-west-2.amazonaws.com",
337			ExpectedSigningName:      "ec2",
338			ExpectedSigningRegion:    "us-west-2",
339			ExpectSigningNameDerived: true,
340		},
341		"deprecated UseDualStack allowed for s3": {
342			Service:                  "s3",
343			Region:                   "us-west-2",
344			Options:                  UseDualStackOption,
345			ExpectedURL:              "https://s3.dualstack.us-west-2.amazonaws.com",
346			ExpectedSigningName:      "s3",
347			ExpectedSigningRegion:    "us-west-2",
348			ExpectSigningNameDerived: true,
349		},
350		"deprecated UseDualStack allowed for s3-control": {
351			Service:                  "s3-control",
352			Region:                   "us-west-2",
353			Options:                  UseDualStackOption,
354			ExpectedURL:              "https://s3-control.dualstack.us-west-2.amazonaws.com",
355			ExpectedSigningName:      "s3-control",
356			ExpectedSigningRegion:    "us-west-2",
357			ExpectSigningNameDerived: true,
358		},
359		"UseDualStackEndpoint applies to all services": {
360			Service:                  "ec2",
361			Region:                   "us-west-2",
362			Options:                  UseDualStackEndpointOption,
363			ExpectedURL:              "https://api.ec2.us-west-2.aws",
364			ExpectedSigningName:      "ec2",
365			ExpectedSigningRegion:    "us-west-2",
366			ExpectSigningNameDerived: true,
367		},
368		"UseDualStackEndpoint applies to s3": {
369			Service:                  "s3",
370			Region:                   "us-west-2",
371			Options:                  UseDualStackEndpointOption,
372			ExpectedURL:              "https://s3.dualstack.us-west-2.amazonaws.com",
373			ExpectedSigningName:      "s3",
374			ExpectedSigningRegion:    "us-west-2",
375			ExpectSigningNameDerived: true,
376		},
377		"UseDualStackEndpoint applies to s3-control": {
378			Service:                  "s3-control",
379			Region:                   "us-west-2",
380			Options:                  UseDualStackEndpointOption,
381			ExpectedURL:              "https://s3-control.dualstack.us-west-2.amazonaws.com",
382			ExpectedSigningName:      "s3-control",
383			ExpectedSigningRegion:    "us-west-2",
384			ExpectSigningNameDerived: true,
385		},
386		"UseDualStackEndpoint (disabled) setting has higher precedence then UseDualStack for s3": {
387			Service: "s3",
388			Region:  "us-west-2",
389			Options: func(options *Options) {
390				options.UseDualStack = true
391				options.UseDualStackEndpoint = DualStackEndpointStateDisabled
392			},
393			ExpectedURL:              "https://s3.us-west-2.amazonaws.com",
394			ExpectedSigningName:      "s3",
395			ExpectedSigningRegion:    "us-west-2",
396			ExpectSigningNameDerived: true,
397		},
398		"UseDualStackEndpoint (disabled) setting has higher precedence then UseDualStack for s3-control": {
399			Service: "s3-control",
400			Region:  "us-west-2",
401			Options: func(options *Options) {
402				options.UseDualStack = true
403				options.UseDualStackEndpoint = DualStackEndpointStateDisabled
404			},
405			ExpectedURL:              "https://s3-control.us-west-2.amazonaws.com",
406			ExpectedSigningName:      "s3-control",
407			ExpectedSigningRegion:    "us-west-2",
408			ExpectSigningNameDerived: true,
409		},
410		"UseDualStackEndpoint in partition with no partition or service defaults": {
411			Service:   "service1",
412			Region:    "cn-north-2",
413			Options:   UseDualStackEndpointOption,
414			ExpectErr: true,
415		},
416	}
417
418	for name, tt := range cases {
419		t.Run(name, func(t *testing.T) {
420			if tt.Options == nil {
421				tt.Options = func(options *Options) {}
422			}
423
424			resolved, err := AwsPartition().EndpointFor(tt.Service, tt.Region, tt.Options)
425			if tt.ExpectErr != (err != nil) {
426				t.Fatalf("ExpectErr=%v, got err=%v", tt.ExpectErr, err)
427			}
428
429			assertEndpoint(t, resolved, tt.ExpectedURL, tt.ExpectedSigningName, tt.ExpectedSigningRegion)
430
431			if e, a := tt.ExpectSigningNameDerived, resolved.SigningNameDerived; e != a {
432				t.Errorf("ExpectSigningNameDerived(%v) != SigningNameDerived(%v)", e, a)
433			}
434		})
435	}
436}
437
438func TestResolveEndpoint_HTTPProtocol(t *testing.T) {
439	resolved, err := testPartitions.EndpointFor("httpService", "us-west-2")
440
441	if err != nil {
442		t.Fatalf("expect no error, got %v", err)
443	}
444	if e, a := "http://httpService.us-west-2.amazonaws.com", resolved.URL; e != a {
445		t.Errorf("expect %v, got %v", e, a)
446	}
447	if e, a := "us-west-2", resolved.SigningRegion; e != a {
448		t.Errorf("expect %v, got %v", e, a)
449	}
450	if e, a := "httpService", resolved.SigningName; e != a {
451		t.Errorf("expect %v, got %v", e, a)
452	}
453	if !resolved.SigningNameDerived {
454		t.Errorf("expect the signing name to be derived")
455	}
456}
457
458func TestResolveEndpoint_UnknownService(t *testing.T) {
459	_, err := testPartitions.EndpointFor("unknownservice", "us-west-2")
460
461	if err == nil {
462		t.Errorf("expect error, got none")
463	}
464
465	_, ok := err.(UnknownServiceError)
466	if !ok {
467		t.Errorf("expect error to be UnknownServiceError")
468	}
469}
470
471func TestResolveEndpoint_ResolveUnknownService(t *testing.T) {
472	resolved, err := testPartitions.EndpointFor("unknown-service", "us-region-1",
473		ResolveUnknownServiceOption)
474
475	if err != nil {
476		t.Fatalf("expect no error, got %v", err)
477	}
478
479	if e, a := "https://unknown-service.us-region-1.amazonaws.com", resolved.URL; e != a {
480		t.Errorf("expect %v, got %v", e, a)
481	}
482	if e, a := "us-region-1", resolved.SigningRegion; e != a {
483		t.Errorf("expect %v, got %v", e, a)
484	}
485	if e, a := "unknown-service", resolved.SigningName; e != a {
486		t.Errorf("expect %v, got %v", e, a)
487	}
488	if !resolved.SigningNameDerived {
489		t.Errorf("expect the signing name to be derived")
490	}
491}
492
493func TestResolveEndpoint_UnknownMatchedRegion(t *testing.T) {
494	resolved, err := testPartitions.EndpointFor("s3", "us-region-1")
495
496	if err != nil {
497		t.Fatalf("expect no error, got %v", err)
498	}
499	if e, a := "https://s3.us-region-1.amazonaws.com", resolved.URL; e != a {
500		t.Errorf("expect %v, got %v", e, a)
501	}
502	if e, a := "us-region-1", resolved.SigningRegion; e != a {
503		t.Errorf("expect %v, got %v", e, a)
504	}
505	if e, a := "s3", resolved.SigningName; e != a {
506		t.Errorf("expect %v, got %v", e, a)
507	}
508}
509
510func TestResolveEndpoint_UnknownRegion(t *testing.T) {
511	resolved, err := testPartitions.EndpointFor("s3", "unknownregion")
512
513	if err != nil {
514		t.Fatalf("expect no error, got %v", err)
515	}
516	if e, a := "https://s3.unknownregion.amazonaws.com", resolved.URL; e != a {
517		t.Errorf("expect %v, got %v", e, a)
518	}
519	if e, a := "unknownregion", resolved.SigningRegion; e != a {
520		t.Errorf("expect %v, got %v", e, a)
521	}
522	if e, a := "s3", resolved.SigningName; e != a {
523		t.Errorf("expect %v, got %v", e, a)
524	}
525}
526
527func TestResolveEndpoint_StrictPartitionUnknownEndpoint(t *testing.T) {
528	_, err := testPartitions[0].EndpointFor("s3", "unknownregion", StrictMatchingOption)
529
530	if err == nil {
531		t.Errorf("expect error, got none")
532	}
533
534	_, ok := err.(UnknownEndpointError)
535	if !ok {
536		t.Errorf("expect error to be UnknownEndpointError")
537	}
538}
539
540func TestResolveEndpoint_StrictPartitionsUnknownEndpoint(t *testing.T) {
541	_, err := testPartitions.EndpointFor("s3", "us-region-1", StrictMatchingOption)
542
543	if err == nil {
544		t.Errorf("expect error, got none")
545	}
546
547	_, ok := err.(UnknownEndpointError)
548	if !ok {
549		t.Errorf("expect error to be UnknownEndpointError")
550	}
551}
552
553func TestResolveEndpoint_NotRegionalized(t *testing.T) {
554	resolved, err := testPartitions.EndpointFor("globalService", "us-west-2")
555
556	if err != nil {
557		t.Fatalf("expect no error, got %v", err)
558	}
559	if e, a := "https://globalService.amazonaws.com", resolved.URL; e != a {
560		t.Errorf("expect %v, got %v", e, a)
561	}
562	if e, a := "us-east-1", resolved.SigningRegion; e != a {
563		t.Errorf("expect %v, got %v", e, a)
564	}
565	if e, a := "globalService", resolved.SigningName; e != a {
566		t.Errorf("expect %v, got %v", e, a)
567	}
568	if !resolved.SigningNameDerived {
569		t.Errorf("expect the signing name to be derived")
570	}
571}
572
573func TestResolveEndpoint_AwsGlobal(t *testing.T) {
574	resolved, err := testPartitions.EndpointFor("globalService", "aws-global")
575
576	if err != nil {
577		t.Fatalf("expect no error, got %v", err)
578	}
579	if e, a := "https://globalService.amazonaws.com", resolved.URL; e != a {
580		t.Errorf("expect %v, got %v", e, a)
581	}
582	if e, a := "us-east-1", resolved.SigningRegion; e != a {
583		t.Errorf("expect %v, got %v", e, a)
584	}
585	if e, a := "globalService", resolved.SigningName; e != a {
586		t.Errorf("expect %v, got %v", e, a)
587	}
588	if !resolved.SigningNameDerived {
589		t.Errorf("expect the signing name to be derived")
590	}
591}
592
593func TestEndpointFor_RegionalFlag(t *testing.T) {
594	// AwsPartition resolver for STS regional endpoints in AWS Partition
595	resolver := AwsPartition()
596
597	cases := map[string]struct {
598		service, region                                     string
599		regional                                            bool
600		ExpectURL, ExpectSigningMethod, ExpectSigningRegion string
601		ExpectSigningNameDerived                            bool
602	}{
603		"acm/ap-northeast-1/regional": {
604			service:                  "acm",
605			region:                   "ap-northeast-1",
606			regional:                 true,
607			ExpectURL:                "https://acm.ap-northeast-1.amazonaws.com",
608			ExpectSigningMethod:      "v4",
609			ExpectSigningNameDerived: true,
610			ExpectSigningRegion:      "ap-northeast-1",
611		},
612		"acm/ap-northeast-1/legacy": {
613			service:                  "acm",
614			region:                   "ap-northeast-1",
615			regional:                 false,
616			ExpectURL:                "https://acm.ap-northeast-1.amazonaws.com",
617			ExpectSigningMethod:      "v4",
618			ExpectSigningNameDerived: true,
619			ExpectSigningRegion:      "ap-northeast-1",
620		},
621	}
622
623	for name, c := range cases {
624		t.Run(name, func(t *testing.T) {
625			var optionSlice []func(o *Options)
626			optionSlice = append(optionSlice, func(o *Options) {
627				if c.regional {
628					o.STSRegionalEndpoint = RegionalSTSEndpoint
629				}
630			})
631
632			actual, err := resolver.EndpointFor(c.service, c.region, optionSlice...)
633			if err != nil {
634				t.Fatalf("failed to resolve endpoint, %v", err)
635			}
636
637			if e, a := c.ExpectURL, actual.URL; e != a {
638				t.Errorf("expect %v, got %v", e, a)
639			}
640
641			if e, a := c.ExpectSigningMethod, actual.SigningMethod; e != a {
642				t.Errorf("expect %v, got %v", e, a)
643			}
644
645			if e, a := c.ExpectSigningNameDerived, actual.SigningNameDerived; e != a {
646				t.Errorf("expect %v, got %v", e, a)
647			}
648
649			if e, a := c.ExpectSigningRegion, actual.SigningRegion; e != a {
650				t.Errorf("expect %v, got %v", e, a)
651			}
652
653		})
654	}
655}
656
657func TestEndpointFor_EmptyRegion(t *testing.T) {
658	// skip this test for partitions outside `aws` partition
659	if DefaultPartitions()[0].id != "aws" {
660		t.Skip()
661	}
662
663	cases := map[string]struct {
664		Service    string
665		Region     string
666		RealRegion string
667		ExpectErr  string
668	}{
669		// Legacy services that previous accepted empty region
670		"budgets":       {Service: "budgets", RealRegion: "aws-global"},
671		"ce":            {Service: "ce", RealRegion: "aws-global"},
672		"chime":         {Service: "chime", RealRegion: "aws-global"},
673		"ec2metadata":   {Service: "ec2metadata", RealRegion: "aws-global"},
674		"iam":           {Service: "iam", RealRegion: "aws-global"},
675		"importexport":  {Service: "importexport", RealRegion: "aws-global"},
676		"organizations": {Service: "organizations", RealRegion: "aws-global"},
677		"route53":       {Service: "route53", RealRegion: "aws-global"},
678		"sts":           {Service: "sts", RealRegion: "aws-global"},
679		"support":       {Service: "support", RealRegion: "aws-global"},
680		"waf":           {Service: "waf", RealRegion: "aws-global"},
681
682		// Other services
683		"s3":           {Service: "s3", Region: "us-east-1", RealRegion: "us-east-1"},
684		"s3 no region": {Service: "s3", ExpectErr: "could not resolve endpoint"},
685	}
686
687	for name, c := range cases {
688		t.Run(name, func(t *testing.T) {
689			actual, err := DefaultResolver().EndpointFor(c.Service, c.Region)
690			if len(c.ExpectErr) != 0 {
691				if e, a := c.ExpectErr, err.Error(); !strings.Contains(a, e) {
692					t.Errorf("expect %q error in %q", e, a)
693				}
694				return
695			}
696			if err != nil {
697				t.Fatalf("expect no error got, %v", err)
698			}
699
700			expect, err := DefaultResolver().EndpointFor(c.Service, c.RealRegion)
701			if err != nil {
702				t.Fatalf("failed to get endpoint for default resolver")
703			}
704			if e, a := expect.URL, actual.URL; e != a {
705				t.Errorf("expect %v URL, got %v", e, a)
706			}
707			if e, a := expect.SigningRegion, actual.SigningRegion; e != a {
708				t.Errorf("expect %v signing region, got %v", e, a)
709			}
710
711		})
712	}
713}
714
715func TestRegionValidator(t *testing.T) {
716	cases := []struct {
717		Region string
718		Valid  bool
719	}{
720		0: {
721			Region: "us-east-1",
722			Valid:  true,
723		},
724		1: {
725			Region: "invalid.com",
726			Valid:  false,
727		},
728		2: {
729			Region: "@invalid.com/%23",
730			Valid:  false,
731		},
732		3: {
733			Region: "local",
734			Valid:  true,
735		},
736		4: {
737			Region: "9-west-1",
738			Valid:  true,
739		},
740	}
741
742	for i, tt := range cases {
743		t.Run(strconv.Itoa(i), func(t *testing.T) {
744			if e, a := tt.Valid, validateInputRegion(tt.Region); e != a {
745				t.Errorf("expected %v, got %v", e, a)
746			}
747		})
748	}
749}
750
751func TestResolveEndpoint_FipsAwsGlobal(t *testing.T) {
752	resolved, err := AwsPartition().EndpointFor("route53", "fips-aws-global")
753
754	if err != nil {
755		t.Fatalf("expect no error, got %v", err)
756	}
757	if e, a := "https://route53-fips.amazonaws.com", resolved.URL; e != a {
758		t.Errorf("expect %v, got %v", e, a)
759	}
760	if e, a := "us-east-1", resolved.SigningRegion; e != a {
761		t.Errorf("expect %v, got %v", e, a)
762	}
763	if e, a := "route53", resolved.SigningName; e != a {
764		t.Errorf("expect %v, got %v", e, a)
765	}
766	if !resolved.SigningNameDerived {
767		t.Errorf("expect the signing name to be derived")
768	}
769}
770
771func TestEC2MetadataService(t *testing.T) {
772	unmodelled := partition{
773		ID:   "unmodelled",
774		Name: "partition with unmodelled ec2metadata",
775		Services: map[string]service{
776			"foo": {
777				Endpoints: serviceEndpoints{
778					endpointKey{Region: "us-west-2"}: endpoint{
779						Hostname:          "foo.us-west-2.amazonaws.com",
780						Protocols:         []string{"http"},
781						SignatureVersions: []string{"v4"},
782					},
783				},
784			},
785		},
786		Regions: map[string]region{
787			"us-west-2": {Description: "us-west-2 region"},
788		},
789	}
790
791	modelled := partition{
792		ID:   "modelled",
793		Name: "partition with modelled ec2metadata",
794		Services: map[string]service{
795			"ec2metadata": {
796				Endpoints: serviceEndpoints{
797					endpointKey{Region: "us-west-2"}: endpoint{
798						Hostname:          "custom.localhost/latest",
799						Protocols:         []string{"http"},
800						SignatureVersions: []string{"v4"},
801					},
802				},
803			},
804			"foo": {
805				Endpoints: serviceEndpoints{
806					endpointKey{Region: "us-west-2"}: endpoint{
807						Hostname:          "foo.us-west-2.amazonaws.com",
808						Protocols:         []string{"http"},
809						SignatureVersions: []string{"v4"},
810					},
811				},
812			},
813		},
814		Regions: map[string]region{
815			"us-west-2": {Description: "us-west-2 region"},
816		},
817	}
818
819	uServices := unmodelled.Partition().Services()
820
821	if s, ok := uServices[Ec2metadataServiceID]; !ok {
822		t.Errorf("expect ec2metadata to be present")
823	} else {
824		if regions := s.Regions(); len(regions) != 0 {
825			t.Errorf("expect no regions for ec2metadata, got %v", len(regions))
826		}
827		if resolved, err := unmodelled.EndpointFor(Ec2metadataServiceID, "us-west-2"); err != nil {
828			t.Errorf("expect no error, got %v", err)
829		} else if e, a := ec2MetadataEndpointIPv4, resolved.URL; e != a {
830			t.Errorf("expect %v, got %v", e, a)
831		}
832	}
833
834	if s, ok := uServices["foo"]; !ok {
835		t.Errorf("expect foo to be present")
836	} else if regions := s.Regions(); len(regions) == 0 {
837		t.Errorf("expect region endpoints for foo. got none")
838	}
839
840	mServices := modelled.Partition().Services()
841
842	if s, ok := mServices[Ec2metadataServiceID]; !ok {
843		t.Errorf("expect ec2metadata to be present")
844	} else if regions := s.Regions(); len(regions) == 0 {
845		t.Errorf("expect region for ec2metadata, got none")
846	} else {
847		if resolved, err := modelled.EndpointFor(Ec2metadataServiceID, "us-west-2"); err != nil {
848			t.Errorf("expect no error, got %v", err)
849		} else if e, a := "http://custom.localhost/latest", resolved.URL; e != a {
850			t.Errorf("expect %v, got %v", e, a)
851		}
852	}
853
854	if s, ok := mServices["foo"]; !ok {
855		t.Errorf("expect foo to be present")
856	} else if regions := s.Regions(); len(regions) == 0 {
857		t.Errorf("expect region endpoints for foo, got none")
858	}
859}
860
861func TestEndpointVariants(t *testing.T) {
862	modelFile, err := os.Open(filepath.Join("testdata", "variants_model.json"))
863	if err != nil {
864		t.Fatal(err)
865	}
866	defer modelFile.Close()
867
868	resolver, err := DecodeModel(modelFile)
869	if err != nil {
870		t.Fatal(err)
871	}
872
873	type testCase struct {
874		Service   string `json:"service"`
875		Region    string `json:"region"`
876		FIPS      bool   `json:"FIPS"`
877		DualStack bool   `json:"DualStack"`
878		Endpoint  string `json:"Endpoint"`
879	}
880
881	casesBytes, err := ioutil.ReadFile(filepath.Join("testdata", "variants_cases.json"))
882	if err != nil {
883		t.Fatal(err)
884	}
885
886	var cases []testCase
887	if err := json.Unmarshal(casesBytes, &cases); err != nil {
888		panic(err)
889	}
890
891	for i, tt := range cases {
892		t.Run(strconv.Itoa(i), func(t *testing.T) {
893			options := Options{}
894
895			if tt.FIPS {
896				options.UseFIPSEndpoint = FIPSEndpointStateEnabled
897			}
898			if tt.DualStack {
899				options.UseDualStackEndpoint = DualStackEndpointStateEnabled
900			}
901
902			resolvedEndpoint, err := resolver.EndpointFor(tt.Service, tt.Region, func(o *Options) {
903				*o = options
904			})
905			if err != nil {
906				t.Errorf("expect no error, got %v", err)
907				return
908			}
909
910			parsed, err := url.Parse(resolvedEndpoint.URL)
911			if err != nil {
912				t.Errorf("expect no error, got %v", err)
913				return
914			}
915
916			if e, a := parsed.Host, tt.Endpoint; e != a {
917				t.Errorf("expect %v, got %v", e, a)
918			}
919		})
920	}
921}
922
923func TestLogDeprecated(t *testing.T) {
924	partitions := partitions{
925		partition{
926			ID: "aws",
927			RegionRegex: regionRegex{
928				Regexp: regexp.MustCompile("^(us|eu|ap|sa|ca)\\-\\w+\\-\\d+$"),
929			},
930			Defaults: map[defaultKey]endpoint{
931				{}: {
932					Hostname:  "foo.{region}.bar.tld",
933					Protocols: []string{"https", "http"},
934				},
935				{
936					Variant: fipsVariant,
937				}: {
938					Hostname: "foo-fips.{region}.bar.tld",
939				},
940			},
941			Services: map[string]service{
942				"service": {
943					Endpoints: map[endpointKey]endpoint{
944						{
945							Region: "foo",
946						}: {},
947						{
948							Region: "bar",
949						}: {
950							Deprecated: boxedTrue,
951						},
952						{
953							Region:  "bar",
954							Variant: fipsVariant,
955						}: {
956							Deprecated: boxedTrue,
957						},
958					},
959				},
960			},
961		},
962	}
963
964	cases := []struct {
965		Region      string
966		Options     Options
967		Expected    ResolvedEndpoint
968		SetupLogger func() (Logger, func(*testing.T))
969		WantErr     bool
970	}{
971		{
972			Region: "foo",
973			Expected: ResolvedEndpoint{
974				URL:                "https://foo.foo.bar.tld",
975				PartitionID:        "aws",
976				SigningName:        "service",
977				SigningRegion:      "foo",
978				SigningMethod:      "v4",
979				SigningNameDerived: true,
980			},
981		},
982		{
983			Region: "bar",
984			Options: Options{
985				LogDeprecated: true,
986			},
987			Expected: ResolvedEndpoint{
988				URL:                "https://foo.bar.bar.tld",
989				PartitionID:        "aws",
990				SigningName:        "service",
991				SigningRegion:      "bar",
992				SigningMethod:      "v4",
993				SigningNameDerived: true,
994			},
995		},
996		{
997			Region: "bar",
998			Options: Options{
999				LogDeprecated:   true,
1000				UseFIPSEndpoint: FIPSEndpointStateEnabled,
1001			},
1002			Expected: ResolvedEndpoint{
1003				URL:                "https://foo-fips.bar.bar.tld",
1004				PartitionID:        "aws",
1005				SigningName:        "service",
1006				SigningRegion:      "bar",
1007				SigningMethod:      "v4",
1008				SigningNameDerived: true,
1009			},
1010		},
1011		{
1012			Region: "bar",
1013			Options: Options{
1014				LogDeprecated: true,
1015			},
1016			SetupLogger: func() (Logger, func(*testing.T)) {
1017				buffer := bytes.NewBuffer(nil)
1018				logger := log.New(buffer, "", 0)
1019				return LoggerFunc(func(i ...interface{}) {
1020						logger.Println(i...)
1021					}), func(t *testing.T) {
1022						if e, a := "endpoint identifier \"bar\", url \"https://foo.bar.bar.tld\" marked as deprecated\n", buffer.String(); e != a {
1023							t.Errorf("expect %v, got %v", e, a)
1024						}
1025					}
1026			},
1027			Expected: ResolvedEndpoint{
1028				URL:                "https://foo.bar.bar.tld",
1029				PartitionID:        "aws",
1030				SigningName:        "service",
1031				SigningRegion:      "bar",
1032				SigningMethod:      "v4",
1033				SigningNameDerived: true,
1034			},
1035		},
1036		{
1037			Region: "bar",
1038			Options: Options{
1039				LogDeprecated:   true,
1040				UseFIPSEndpoint: FIPSEndpointStateEnabled,
1041			},
1042			SetupLogger: func() (Logger, func(*testing.T)) {
1043				buffer := bytes.NewBuffer(nil)
1044				logger := log.New(buffer, "", 0)
1045				return LoggerFunc(func(i ...interface{}) {
1046						logger.Println(i...)
1047					}), func(t *testing.T) {
1048						if e, a := "endpoint identifier \"bar\", url \"https://foo-fips.bar.bar.tld\" marked as deprecated\n", buffer.String(); e != a {
1049							t.Errorf("expect %v, got %v", e, a)
1050						}
1051					}
1052			},
1053			Expected: ResolvedEndpoint{
1054				URL:                "https://foo-fips.bar.bar.tld",
1055				PartitionID:        "aws",
1056				SigningName:        "service",
1057				SigningRegion:      "bar",
1058				SigningMethod:      "v4",
1059				SigningNameDerived: true,
1060			},
1061		},
1062	}
1063
1064	for i, tt := range cases {
1065		t.Run(strconv.Itoa(i), func(t *testing.T) {
1066			var verifyLog func(*testing.T)
1067			if tt.SetupLogger != nil {
1068				tt.Options.Logger, verifyLog = tt.SetupLogger()
1069			}
1070
1071			endpoint, err := partitions.EndpointFor("service", tt.Region, func(options *Options) {
1072				*options = tt.Options
1073			})
1074			if (err != nil) != tt.WantErr {
1075				t.Errorf("WantErr(%v), got error %v", tt.WantErr, err)
1076			}
1077
1078			if !reflect.DeepEqual(tt.Expected, endpoint) {
1079				t.Errorf("expect %v, got %v", tt.Expected, endpoint)
1080			}
1081
1082			if verifyLog != nil {
1083				verifyLog(t)
1084			}
1085		})
1086	}
1087}
1088
1089func TestPartitionVariantMerging(t *testing.T) {
1090	partition := partition{
1091		ID:        "aws-iso",
1092		Name:      "AWS ISO (US)",
1093		DNSSuffix: "c2s.ic.gov",
1094		RegionRegex: regionRegex{
1095			Regexp: func() *regexp.Regexp {
1096				reg, _ := regexp.Compile("^us\\-iso\\-\\w+\\-\\d+$")
1097				return reg
1098			}(),
1099		},
1100		Defaults: endpointDefaults{
1101			{}: {
1102				Hostname:          "{service}.{region}.{dnsSuffix}",
1103				Protocols:         []string{"https"},
1104				SignatureVersions: []string{"v4"},
1105			},
1106			{Variant: dualStackVariant}: {
1107				DNSSuffix:         "dualstack.foo.bar",
1108				Hostname:          "{service}.{region}.{dnsSuffix}",
1109				Protocols:         []string{"https"},
1110				SignatureVersions: []string{"v4"},
1111			},
1112		},
1113		Regions: regions{
1114			"us-iso-east-1": region{
1115				Description: "US ISO East",
1116			},
1117			"us-iso-west-1": region{
1118				Description: "US ISO WEST",
1119			},
1120		},
1121		Services: services{
1122			"service1": {},
1123			"service2": {
1124				Defaults: map[defaultKey]endpoint{
1125					{}: {
1126						CredentialScope: credentialScope{
1127							Service: "service-two",
1128						},
1129					},
1130					{Variant: fipsVariant}: {
1131						Hostname:  "{service}-fips.{region}.{dnsSuffix}",
1132						DNSSuffix: "foo.bar",
1133						CredentialScope: credentialScope{
1134							Service: "service-two",
1135						},
1136					},
1137				},
1138			},
1139		},
1140	}
1141
1142	cases := []struct {
1143		Service          string
1144		Region           string
1145		Options          Options
1146		WantErr          bool
1147		ExpectedEndpoint ResolvedEndpoint
1148	}{
1149		{
1150			Service: "service1",
1151			Region:  "us-iso-east-1",
1152			Options: Options{
1153				UseFIPSEndpoint: FIPSEndpointStateEnabled,
1154			},
1155			WantErr: true,
1156		},
1157		{
1158			Service: "service1",
1159			Region:  "us-iso-east-1",
1160			Options: Options{
1161				UseDualStackEndpoint: DualStackEndpointStateEnabled,
1162			},
1163			ExpectedEndpoint: ResolvedEndpoint{
1164				URL:                "https://service1.us-iso-east-1.dualstack.foo.bar",
1165				PartitionID:        "aws-iso",
1166				SigningRegion:      "us-iso-east-1",
1167				SigningName:        "service1",
1168				SigningNameDerived: true,
1169				SigningMethod:      "v4",
1170			},
1171		},
1172		{
1173			Service: "service1",
1174			Region:  "us-iso-east-1",
1175			ExpectedEndpoint: ResolvedEndpoint{
1176				URL:                "https://service1.us-iso-east-1.c2s.ic.gov",
1177				PartitionID:        "aws-iso",
1178				SigningRegion:      "us-iso-east-1",
1179				SigningName:        "service1",
1180				SigningNameDerived: true,
1181				SigningMethod:      "v4",
1182			},
1183		},
1184		{
1185			Service: "service2",
1186			Region:  "us-iso-east-1",
1187			Options: Options{
1188				UseFIPSEndpoint: FIPSEndpointStateEnabled,
1189			},
1190			ExpectedEndpoint: ResolvedEndpoint{
1191				URL:           "https://service2-fips.us-iso-east-1.foo.bar",
1192				PartitionID:   "aws-iso",
1193				SigningRegion: "us-iso-east-1",
1194				SigningName:   "service-two",
1195				SigningMethod: "v4",
1196			},
1197		},
1198		{
1199			Service: "service2",
1200			Region:  "us-iso-east-1",
1201			Options: Options{
1202				UseDualStackEndpoint: DualStackEndpointStateEnabled,
1203			},
1204			ExpectedEndpoint: ResolvedEndpoint{
1205				URL:           "https://service2.us-iso-east-1.dualstack.foo.bar",
1206				PartitionID:   "aws-iso",
1207				SigningRegion: "us-iso-east-1",
1208				SigningName:   "service-two",
1209				SigningMethod: "v4",
1210			},
1211		},
1212	}
1213
1214	for i, tt := range cases {
1215		t.Run(strconv.Itoa(i), func(t *testing.T) {
1216			resolved, err := partition.EndpointFor(tt.Service, tt.Region, func(options *Options) {
1217				*options = tt.Options
1218			})
1219			if (err != nil) != tt.WantErr {
1220				t.Errorf("WantErr(%v) got Err(%v)", tt.WantErr, err)
1221				return
1222			}
1223			if tt.WantErr {
1224				return
1225			}
1226			if e, a := tt.ExpectedEndpoint, resolved; !reflect.DeepEqual(e, a) {
1227				t.Errorf("expect %v, got %v", e, a)
1228			}
1229		})
1230	}
1231
1232}
1233