// Copyright 2016 Google LLC. All Rights Reserved.
//
// 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 fixchain

import (
	"net/http"
	"testing"

	"github.com/google/certificate-transparency-go/x509"
)

var constructChainTests = []fixTest{
	// constructChain()
	{ // Correct chain returns chain
		cert:  googleLeaf,
		chain: []string{thawteIntermediate, verisignRoot},
		roots: []string{verisignRoot},

		function: "constructChain",
		expectedChains: [][]string{
			{"Google", "Thawte", "VeriSign"},
		},
	},
	{
		cert:  testLeaf,
		chain: []string{testIntermediate2, testIntermediate1, testRoot},
		roots: []string{testRoot},

		function: "constructChain",
		expectedChains: [][]string{
			{"Leaf", "Intermediate2", "Intermediate1", "CA"},
		},
	},
	{ // No roots results in an error
		cert:  googleLeaf,
		chain: []string{thawteIntermediate, verisignRoot},

		function:     "constructChain",
		expectedErrs: []errorType{VerifyFailed},
	},
	{ // Incomplete chain results in an error
		cert:  googleLeaf,
		roots: []string{verisignRoot},

		function:     "constructChain",
		expectedErrs: []errorType{VerifyFailed},
	},
	{ // The wrong intermediate and root results in an error
		cert:  megaLeaf,
		chain: []string{thawteIntermediate, verisignRoot},
		roots: []string{verisignRoot},

		function:     "constructChain",
		expectedErrs: []errorType{VerifyFailed},
	},
	{ // The wrong root results in an error
		cert:  megaLeaf,
		chain: []string{comodoIntermediate, verisignRoot},
		roots: []string{verisignRoot},

		function:     "constructChain",
		expectedErrs: []errorType{VerifyFailed},
	},
}

var fixChainTests = []fixTest{
	// fixChain()
	{ // Correct chain returns chain
		cert:  googleLeaf,
		chain: []string{thawteIntermediate, verisignRoot},
		roots: []string{verisignRoot},

		function: "fixChain",
		expectedChains: [][]string{
			{"Google", "Thawte", "VeriSign"},
		},
	},
	{ // No roots results in an error
		cert:  googleLeaf,
		chain: []string{thawteIntermediate, verisignRoot},

		function:     "fixChain",
		expectedErrs: []errorType{FixFailed},
	},
	{ // No roots where chain that will be built contains a loop results in error
		cert:  testC,
		chain: []string{testB, testA},

		function:     "fixChain",
		expectedErrs: []errorType{FixFailed},
	},
	{ // Incomplete chain returns fixed chain
		cert:  googleLeaf,
		roots: []string{verisignRoot},

		function: "fixChain",
		expectedChains: [][]string{
			{"Google", "Thawte", "VeriSign"},
		},
	},
	{
		cert:  testLeaf,
		chain: []string{testIntermediate2},
		roots: []string{testRoot},

		function: "fixChain",
		expectedChains: [][]string{
			{"Leaf", "Intermediate2", "Intermediate1", "CA"},
		},
	},
	{
		cert:  testLeaf,
		chain: []string{testIntermediate1},
		roots: []string{testRoot},

		function: "fixChain",
		expectedChains: [][]string{
			{"Leaf", "Intermediate2", "Intermediate1", "CA"},
		},
	},
	{
		cert:  testLeaf,
		roots: []string{testRoot},

		function: "fixChain",
		expectedChains: [][]string{
			{"Leaf", "Intermediate2", "Intermediate1", "CA"},
		},
	},
	{ // The wrong intermediate and root results in an error
		cert:  megaLeaf,
		chain: []string{thawteIntermediate, verisignRoot},
		roots: []string{verisignRoot},

		function:     "fixChain",
		expectedErrs: []errorType{FixFailed},
	},
	{ // The wrong root results in an error
		cert:  megaLeaf,
		chain: []string{comodoIntermediate, verisignRoot},
		roots: []string{verisignRoot},

		function:     "fixChain",
		expectedErrs: []errorType{FixFailed},
	},
	// TODO(katjoyce): Add test where cert has multiple URLs in AIA extension.
}

func setUpFix(t *testing.T, i int, ft *fixTest) *toFix {
	// Create & populate toFix to test from fixTest info
	fix := &toFix{
		cert:  GetTestCertificateFromPEM(t, ft.cert),
		chain: newDedupedChain(extractTestChain(t, i, ft.chain)),
		roots: extractTestRoots(t, i, ft.roots),
		cache: newURLCache(&http.Client{Transport: &testRoundTripper{}}, false),
	}

	intermediates := x509.NewCertPool()
	for j, cert := range ft.chain {
		ok := intermediates.AppendCertsFromPEM([]byte(cert))
		if !ok {
			t.Errorf("#%d: Failed to parse intermediate #%d", i, j)
		}
	}

	fix.opts = &x509.VerifyOptions{
		Intermediates:     intermediates,
		Roots:             fix.roots,
		DisableTimeChecks: true,
		KeyUsages:         []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
	}

	return fix
}

func testFixChainFunctions(t *testing.T, i int, ft *fixTest) {
	fix := setUpFix(t, i, ft)

	var chains [][]*x509.Certificate
	var ferrs []*FixError
	switch ft.function {
	case "constructChain":
		chains, ferrs = fix.constructChain()
	case "fixChain":
		chains, ferrs = fix.fixChain()
	case "handleChain":
		chains, ferrs = fix.handleChain()
	}

	matchTestChainList(t, i, ft.expectedChains, chains)
	matchTestErrorList(t, i, ft.expectedErrs, ferrs)
}

func TestFixChainFunctions(t *testing.T) {
	var allTests []fixTest
	allTests = append(allTests, constructChainTests...)
	allTests = append(allTests, fixChainTests...)
	allTests = append(allTests, handleChainTests...)
	for i, ft := range allTests {
		testFixChainFunctions(t, i, &ft)
	}
}

func TestFix(t *testing.T) {
	for i, test := range handleChainTests {
		chains, ferrs := Fix(GetTestCertificateFromPEM(t, test.cert),
			extractTestChain(t, i, test.chain),
			extractTestRoots(t, i, test.roots),
			&http.Client{Transport: &testRoundTripper{}})

		matchTestChainList(t, i, test.expectedChains, chains)
		matchTestErrorList(t, i, test.expectedErrs, ferrs)
	}
}
