/*
Copyright 2020 The cert-manager Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package secret

import (
	"crypto/x509"
	"strings"
	"testing"
	"time"

	fakeclock "k8s.io/utils/clock/testing"

	"github.com/jetstack/cert-manager/pkg/util/pki"
)

const testCert = `-----BEGIN CERTIFICATE-----
MIICljCCAhugAwIBAgIUNAQr779ga/BNXyCpK7ddFbjAK98wCgYIKoZIzj0EAwMw
aTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh
biBGcmFuY2lzY28xHzAdBgNVBAoTFkludGVybmV0IFdpZGdldHMsIEluYy4xDDAK
BgNVBAsTA1dXVzAeFw0yMTAyMjYxMDM1MDBaFw0yMjAyMjYxMDM1MDBaMDMxCzAJ
BgNVBAYTAkdCMQ0wCwYDVQQKEwRjbmNmMRUwEwYDVQQLEwxjZXJ0LW1hbmFnZXIw
WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATd5gWH2rkzWBGrr1jCR6JDB0dZOizZ
jCt2gnzNfzZmEg3rqxPvIakfT1lsjL2HrQyBRMQGGZhj7RkN7/VUM+VUo4HWMIHT
MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUCUEeUFyT7U3e6zP4q4VYEr2x0KcwHwYD
VR0jBBgwFoAUFkKAaJ18Vg9xFx3K7d5b7HjoSSMwVAYDVR0RBE0wS4IRY2VydC1t
YW5hZ2VyLnRlc3SBFHRlc3RAY2VydC1tYW5hZ2VyLmlvhwQKAAABhhpzcGlmZmU6
Ly9jZXJ0LW1hbmFnZXIudGVzdDAKBggqhkjOPQQDAwNpADBmAjEA3Fv1aP+dBtBh
+DThW0QQO/Xl0CHQRKnJmJ8JjnleaMYFVdHf7dcf0ZeyOC26aUkdAjEA/fvxvhcz
Dtj+gY2rewoeJv5Pslli+SEObUslRaVtUMGxwUbmPU2fKuZHWBfe2FfA
-----END CERTIFICATE-----
`

func MustParseCertificate(t *testing.T, certData string) *x509.Certificate {
	x509Cert, err := pki.DecodeX509CertificateBytes([]byte(certData))
	if err != nil {
		t.Fatalf("error when parsing crt: %v", err)
	}

	return x509Cert
}

func Test_describeCRL(t *testing.T) {
	tests := []struct {
		name string
		cert *x509.Certificate
		want string
	}{
		{
			name: "Print cert without CRL",
			cert: MustParseCertificate(t, testCert),
			want: "No CRL endpoints set",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := describeCRL(tt.cert); got != tt.want {
				t.Errorf("describeCRL() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want))
			}
		})
	}
}

func Test_describeCertificate(t *testing.T) {
	tests := []struct {
		name string
		cert *x509.Certificate
		want string
	}{
		{
			name: "Describe test certificate",
			cert: MustParseCertificate(t, testCert),
			want: `Certificate:
	Signing Algorithm:	ECDSA-SHA384
	Public Key Algorithm: 	ECDSA
	Serial Number:	296960550473797734497458414367422077039506631647
	Fingerprints: 	FF:D0:A8:85:0B:A4:5A:E1:FC:55:40:E1:FC:07:09:F1:02:AE:B9:EB:28:C4:01:23:B9:4F:C8:FA:9B:EF:F4:C1
	Is a CA certificate: false
	CRL:	<none>
	OCSP:	<none>`,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := describeCertificate(tt.cert); got != tt.want {
				t.Errorf("describeCertificate() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want))
			}
		})
	}
}

func Test_describeDebugging(t *testing.T) {
	type args struct {
		cert          *x509.Certificate
		intermediates [][]byte
		ca            []byte
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			name: "Debug test cert without trusting CA",
			args: args{
				cert:          MustParseCertificate(t, testCert),
				intermediates: nil,
				ca:            nil,
			},
			want: `Debugging:
	Trusted by this computer:	no: x509: certificate signed by unknown authority
	CRL Status:	No CRL endpoints set
	OCSP Status:	Cannot check OCSP, does not have a CA or intermediate certificate provided`,
		},
		// TODO: add fake clock and test with trusting CA
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := describeDebugging(tt.args.cert, tt.args.intermediates, tt.args.ca); got != tt.want {
				t.Errorf("describeDebugging() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want))
			}
		})
	}
}

func Test_describeIssuedBy(t *testing.T) {
	tests := []struct {
		name string
		cert *x509.Certificate
		want string
	}{
		{
			name: "Describe test certificate",
			cert: MustParseCertificate(t, testCert),
			want: `Issued By:
	Common Name:	<none>
	Organization:	<none>
	OrganizationalUnit:	Internet Widgets, Inc.
	Country:	US`,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := describeIssuedBy(tt.cert); got != tt.want {
				t.Errorf("describeIssuedBy() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want))
			}
		})
	}
}

func Test_describeIssuedFor(t *testing.T) {
	tests := []struct {
		name string
		cert *x509.Certificate
		want string
	}{
		{
			name: "Describe test cert",
			cert: MustParseCertificate(t, testCert),
			want: `Issued For:
	Common Name:	<none>
	Organization:	<none>
	OrganizationalUnit:	cncf
	Country:	GB`,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := describeIssuedFor(tt.cert); got != tt.want {
				t.Errorf("describeIssuedFor() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want))
			}
		})
	}
}

func Test_describeOCSP(t *testing.T) {
	type args struct {
		cert          *x509.Certificate
		intermediates [][]byte
		ca            []byte
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			name: "Describe cert with no OCSP",
			args: args{
				cert: MustParseCertificate(t, testCert),
			},
			want: "Cannot check OCSP, does not have a CA or intermediate certificate provided",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := describeOCSP(tt.args.cert, tt.args.intermediates, tt.args.ca); got != tt.want {
				t.Errorf("describeOCSP() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want))
			}
		})
	}
}

func Test_describeTrusted(t *testing.T) {
	// set clock to when our test cert was trusted
	t1, _ := time.Parse("Thu, 27 Nov 2020 10:00:00 UTC", time.RFC1123)
	clock = fakeclock.NewFakeClock(t1)
	type args struct {
		cert          *x509.Certificate
		intermediates [][]byte
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			name: "Describe test certificate",
			args: args{
				cert:          MustParseCertificate(t, testCert),
				intermediates: nil,
			},
			want: "no: x509: certificate signed by unknown authority",
		},
		{
			name: "Describe test certificate with adding it to the trust store",
			args: args{
				cert:          MustParseCertificate(t, testCert),
				intermediates: [][]byte{[]byte(testCert)},
			},
			want: "yes",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := describeTrusted(tt.args.cert, tt.args.intermediates); got != tt.want {
				t.Errorf("describeTrusted() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want))
			}
		})
	}
}

func Test_describeValidFor(t *testing.T) {
	tests := []struct {
		name string
		cert *x509.Certificate
		want string
	}{
		{
			name: "Describe test certificate",
			cert: MustParseCertificate(t, testCert),
			want: `Valid for:
	DNS Names: 
		- cert-manager.test
	URIs: 
		- spiffe://cert-manager.test
	IP Addresses: 
		- 10.0.0.1
	Email Addresses: 
		- test@cert-manager.io
	Usages: 
		- digital signature
		- key encipherment
		- server auth
		- client auth`,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := describeValidFor(tt.cert); got != tt.want {
				t.Errorf("describeValidFor() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want))
			}
		})
	}
}

func Test_describeValidityPeriod(t *testing.T) {
	tests := []struct {
		name string
		cert *x509.Certificate
		want string
	}{
		{
			name: "Describe test certificate",
			cert: MustParseCertificate(t, testCert),
			want: `Validity period:
	Not Before: Fri, 26 Feb 2021 10:35:00 UTC
	Not After: Sat, 26 Feb 2022 10:35:00 UTC`,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := describeValidityPeriod(tt.cert); got != tt.want {
				t.Errorf("describeValidityPeriod() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want))
			}
		})
	}
}

func makeInvisibleVisible(in string) string {
	in = strings.Replace(in, "\n", "\\n\n", -1)
	in = strings.Replace(in, "\t", "\\t", -1)

	return in
}
