// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package acme

import (
	"errors"
	"net/http"
	"reflect"
	"testing"
	"time"
)

func TestExternalAccountBindingString(t *testing.T) {
	eab := ExternalAccountBinding{
		KID: "kid",
		Key: []byte("key"),
	}
	got := eab.String()
	want := `&{KID: "kid", Key: redacted}`
	if got != want {
		t.Errorf("eab.String() = %q, want: %q", got, want)
	}
}

func TestRateLimit(t *testing.T) {
	now := time.Date(2017, 04, 27, 10, 0, 0, 0, time.UTC)
	f := timeNow
	defer func() { timeNow = f }()
	timeNow = func() time.Time { return now }

	h120, hTime := http.Header{}, http.Header{}
	h120.Set("Retry-After", "120")
	hTime.Set("Retry-After", "Tue Apr 27 11:00:00 2017")

	err1 := &Error{
		ProblemType: "urn:ietf:params:acme:error:nolimit",
		Header:      h120,
	}
	err2 := &Error{
		ProblemType: "urn:ietf:params:acme:error:rateLimited",
		Header:      h120,
	}
	err3 := &Error{
		ProblemType: "urn:ietf:params:acme:error:rateLimited",
		Header:      nil,
	}
	err4 := &Error{
		ProblemType: "urn:ietf:params:acme:error:rateLimited",
		Header:      hTime,
	}

	tt := []struct {
		err error
		res time.Duration
		ok  bool
	}{
		{nil, 0, false},
		{errors.New("dummy"), 0, false},
		{err1, 0, false},
		{err2, 2 * time.Minute, true},
		{err3, 0, true},
		{err4, time.Hour, true},
	}
	for i, test := range tt {
		res, ok := RateLimit(test.err)
		if ok != test.ok {
			t.Errorf("%d: RateLimit(%+v): ok = %v; want %v", i, test.err, ok, test.ok)
			continue
		}
		if res != test.res {
			t.Errorf("%d: RateLimit(%+v) = %v; want %v", i, test.err, res, test.res)
		}
	}
}

func TestAuthorizationError(t *testing.T) {
	tests := []struct {
		desc string
		err  *AuthorizationError
		msg  string
	}{
		{
			desc: "when auth error identifier is set",
			err: &AuthorizationError{
				Identifier: "domain.com",
				Errors: []error{
					(&wireError{
						Status: 403,
						Type:   "urn:ietf:params:acme:error:caa",
						Detail: "CAA record for domain.com prevents issuance",
					}).error(nil),
				},
			},
			msg: "acme: authorization error for domain.com: 403 urn:ietf:params:acme:error:caa: CAA record for domain.com prevents issuance",
		},

		{
			desc: "when auth error identifier is unset",
			err: &AuthorizationError{
				Errors: []error{
					(&wireError{
						Status: 403,
						Type:   "urn:ietf:params:acme:error:caa",
						Detail: "CAA record for domain.com prevents issuance",
					}).error(nil),
				},
			},
			msg: "acme: authorization error: 403 urn:ietf:params:acme:error:caa: CAA record for domain.com prevents issuance",
		},
	}

	for _, tt := range tests {
		if tt.err.Error() != tt.msg {
			t.Errorf("got: %s\nwant: %s", tt.err, tt.msg)
		}
	}
}

func TestSubproblems(t *testing.T) {
	tests := []struct {
		wire        wireError
		expectedOut Error
	}{
		{
			wire: wireError{
				Status: 1,
				Type:   "urn:error",
				Detail: "it's an error",
			},
			expectedOut: Error{
				StatusCode:  1,
				ProblemType: "urn:error",
				Detail:      "it's an error",
			},
		},
		{
			wire: wireError{
				Status: 1,
				Type:   "urn:error",
				Detail: "it's an error",
				Subproblems: []Subproblem{
					{
						Type:   "urn:error:sub",
						Detail: "it's a subproblem",
					},
				},
			},
			expectedOut: Error{
				StatusCode:  1,
				ProblemType: "urn:error",
				Detail:      "it's an error",
				Subproblems: []Subproblem{
					{
						Type:   "urn:error:sub",
						Detail: "it's a subproblem",
					},
				},
			},
		},
		{
			wire: wireError{
				Status: 1,
				Type:   "urn:error",
				Detail: "it's an error",
				Subproblems: []Subproblem{
					{
						Type:       "urn:error:sub",
						Detail:     "it's a subproblem",
						Identifier: &AuthzID{Type: "dns", Value: "example"},
					},
				},
			},
			expectedOut: Error{
				StatusCode:  1,
				ProblemType: "urn:error",
				Detail:      "it's an error",
				Subproblems: []Subproblem{
					{
						Type:       "urn:error:sub",
						Detail:     "it's a subproblem",
						Identifier: &AuthzID{Type: "dns", Value: "example"},
					},
				},
			},
		},
	}

	for _, tc := range tests {
		out := tc.wire.error(nil)
		if !reflect.DeepEqual(*out, tc.expectedOut) {
			t.Errorf("Unexpected error: wanted %v, got %v", tc.expectedOut, *out)
		}
	}
}

func TestErrorStringerWithSubproblems(t *testing.T) {
	err := Error{
		StatusCode:  1,
		ProblemType: "urn:error",
		Detail:      "it's an error",
		Subproblems: []Subproblem{
			{
				Type:   "urn:error:sub",
				Detail: "it's a subproblem",
			},
			{
				Type:       "urn:error:sub",
				Detail:     "it's a subproblem",
				Identifier: &AuthzID{Type: "dns", Value: "example"},
			},
		},
	}
	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"
	if err.Error() != expectedStr {
		t.Errorf("Unexpected error string: wanted %q, got %q", expectedStr, err.Error())
	}
}

func TestOrderErrorProblem(t *testing.T) {
	oe := &OrderError{
		OrderURL: "url",
		Status:   "invalid",
		Problem: &Error{
			StatusCode:  400,
			ProblemType: "type",
			Detail:      "detail",
		},
	}
	want := "acme: order url status: invalid; problem: 400 type: detail"
	if got := oe.Error(); got != want {
		t.Errorf("oe.Error() = %q, want %q", got, want)
	}
}
