1// Copyright 2017 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package acme
6
7import (
8	"errors"
9	"net/http"
10	"reflect"
11	"testing"
12	"time"
13)
14
15func TestExternalAccountBindingString(t *testing.T) {
16	eab := ExternalAccountBinding{
17		KID: "kid",
18		Key: []byte("key"),
19	}
20	got := eab.String()
21	want := `&{KID: "kid", Key: redacted}`
22	if got != want {
23		t.Errorf("eab.String() = %q, want: %q", got, want)
24	}
25}
26
27func TestRateLimit(t *testing.T) {
28	now := time.Date(2017, 04, 27, 10, 0, 0, 0, time.UTC)
29	f := timeNow
30	defer func() { timeNow = f }()
31	timeNow = func() time.Time { return now }
32
33	h120, hTime := http.Header{}, http.Header{}
34	h120.Set("Retry-After", "120")
35	hTime.Set("Retry-After", "Tue Apr 27 11:00:00 2017")
36
37	err1 := &Error{
38		ProblemType: "urn:ietf:params:acme:error:nolimit",
39		Header:      h120,
40	}
41	err2 := &Error{
42		ProblemType: "urn:ietf:params:acme:error:rateLimited",
43		Header:      h120,
44	}
45	err3 := &Error{
46		ProblemType: "urn:ietf:params:acme:error:rateLimited",
47		Header:      nil,
48	}
49	err4 := &Error{
50		ProblemType: "urn:ietf:params:acme:error:rateLimited",
51		Header:      hTime,
52	}
53
54	tt := []struct {
55		err error
56		res time.Duration
57		ok  bool
58	}{
59		{nil, 0, false},
60		{errors.New("dummy"), 0, false},
61		{err1, 0, false},
62		{err2, 2 * time.Minute, true},
63		{err3, 0, true},
64		{err4, time.Hour, true},
65	}
66	for i, test := range tt {
67		res, ok := RateLimit(test.err)
68		if ok != test.ok {
69			t.Errorf("%d: RateLimit(%+v): ok = %v; want %v", i, test.err, ok, test.ok)
70			continue
71		}
72		if res != test.res {
73			t.Errorf("%d: RateLimit(%+v) = %v; want %v", i, test.err, res, test.res)
74		}
75	}
76}
77
78func TestAuthorizationError(t *testing.T) {
79	tests := []struct {
80		desc string
81		err  *AuthorizationError
82		msg  string
83	}{
84		{
85			desc: "when auth error identifier is set",
86			err: &AuthorizationError{
87				Identifier: "domain.com",
88				Errors: []error{
89					(&wireError{
90						Status: 403,
91						Type:   "urn:ietf:params:acme:error:caa",
92						Detail: "CAA record for domain.com prevents issuance",
93					}).error(nil),
94				},
95			},
96			msg: "acme: authorization error for domain.com: 403 urn:ietf:params:acme:error:caa: CAA record for domain.com prevents issuance",
97		},
98
99		{
100			desc: "when auth error identifier is unset",
101			err: &AuthorizationError{
102				Errors: []error{
103					(&wireError{
104						Status: 403,
105						Type:   "urn:ietf:params:acme:error:caa",
106						Detail: "CAA record for domain.com prevents issuance",
107					}).error(nil),
108				},
109			},
110			msg: "acme: authorization error: 403 urn:ietf:params:acme:error:caa: CAA record for domain.com prevents issuance",
111		},
112	}
113
114	for _, tt := range tests {
115		if tt.err.Error() != tt.msg {
116			t.Errorf("got: %s\nwant: %s", tt.err, tt.msg)
117		}
118	}
119}
120
121func TestSubproblems(t *testing.T) {
122	tests := []struct {
123		wire        wireError
124		expectedOut Error
125	}{
126		{
127			wire: wireError{
128				Status: 1,
129				Type:   "urn:error",
130				Detail: "it's an error",
131			},
132			expectedOut: Error{
133				StatusCode:  1,
134				ProblemType: "urn:error",
135				Detail:      "it's an error",
136			},
137		},
138		{
139			wire: wireError{
140				Status: 1,
141				Type:   "urn:error",
142				Detail: "it's an error",
143				Subproblems: []Subproblem{
144					{
145						Type:   "urn:error:sub",
146						Detail: "it's a subproblem",
147					},
148				},
149			},
150			expectedOut: Error{
151				StatusCode:  1,
152				ProblemType: "urn:error",
153				Detail:      "it's an error",
154				Subproblems: []Subproblem{
155					{
156						Type:   "urn:error:sub",
157						Detail: "it's a subproblem",
158					},
159				},
160			},
161		},
162		{
163			wire: wireError{
164				Status: 1,
165				Type:   "urn:error",
166				Detail: "it's an error",
167				Subproblems: []Subproblem{
168					{
169						Type:       "urn:error:sub",
170						Detail:     "it's a subproblem",
171						Identifier: &AuthzID{Type: "dns", Value: "example"},
172					},
173				},
174			},
175			expectedOut: Error{
176				StatusCode:  1,
177				ProblemType: "urn:error",
178				Detail:      "it's an error",
179				Subproblems: []Subproblem{
180					{
181						Type:       "urn:error:sub",
182						Detail:     "it's a subproblem",
183						Identifier: &AuthzID{Type: "dns", Value: "example"},
184					},
185				},
186			},
187		},
188	}
189
190	for _, tc := range tests {
191		out := tc.wire.error(nil)
192		if !reflect.DeepEqual(*out, tc.expectedOut) {
193			t.Errorf("Unexpected error: wanted %v, got %v", tc.expectedOut, *out)
194		}
195	}
196}
197
198func TestErrorStringerWithSubproblems(t *testing.T) {
199	err := Error{
200		StatusCode:  1,
201		ProblemType: "urn:error",
202		Detail:      "it's an error",
203		Subproblems: []Subproblem{
204			{
205				Type:   "urn:error:sub",
206				Detail: "it's a subproblem",
207			},
208			{
209				Type:       "urn:error:sub",
210				Detail:     "it's a subproblem",
211				Identifier: &AuthzID{Type: "dns", Value: "example"},
212			},
213		},
214	}
215	expectedStr := "1 urn:error: it's an error; subproblems:\n\turn:error:sub: it's a subproblem\n\turn:error:sub: [dns: example] it's a subproblem"
216	if err.Error() != expectedStr {
217		t.Errorf("Unexpected error string: wanted %q, got %q", expectedStr, err.Error())
218	}
219}
220