/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package deliver_test

import (
	"time"

	cb "github.com/hyperledger/fabric-protos-go/common"
	"github.com/hyperledger/fabric/common/deliver"
	"github.com/hyperledger/fabric/common/deliver/mock"
	"github.com/hyperledger/fabric/protoutil"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/pkg/errors"
)

var _ = Describe("SessionAccessControl", func() {
	var (
		fakeChain         *mock.Chain
		envelope          *cb.Envelope
		fakePolicyChecker *mock.PolicyChecker
		expiresAt         deliver.ExpiresAtFunc
	)

	BeforeEach(func() {
		envelope = &cb.Envelope{
			Payload: protoutil.MarshalOrPanic(&cb.Payload{
				Header: &cb.Header{},
			}),
		}

		fakeChain = &mock.Chain{}
		fakePolicyChecker = &mock.PolicyChecker{}
		expiresAt = func([]byte) time.Time { return time.Time{} }
	})

	It("evaluates the policy", func() {
		sac, err := deliver.NewSessionAC(fakeChain, envelope, fakePolicyChecker, "chain-id", expiresAt)
		Expect(err).NotTo(HaveOccurred())

		err = sac.Evaluate()
		Expect(err).NotTo(HaveOccurred())

		Expect(fakePolicyChecker.CheckPolicyCallCount()).To(Equal(1))
		env, cid := fakePolicyChecker.CheckPolicyArgsForCall(0)
		Expect(env).To(Equal(envelope))
		Expect(cid).To(Equal("chain-id"))
	})

	Context("when policy evaluation returns an error", func() {
		BeforeEach(func() {
			fakePolicyChecker.CheckPolicyReturns(errors.New("no-access-for-you"))
		})

		It("returns the evaluation error", func() {
			sac, err := deliver.NewSessionAC(fakeChain, envelope, fakePolicyChecker, "chain-id", expiresAt)
			Expect(err).NotTo(HaveOccurred())

			err = sac.Evaluate()
			Expect(err).To(MatchError("no-access-for-you"))
		})
	})

	It("caches positive policy evaluation", func() {
		sac, err := deliver.NewSessionAC(fakeChain, envelope, fakePolicyChecker, "chain-id", expiresAt)
		Expect(err).NotTo(HaveOccurred())

		for i := 0; i < 5; i++ {
			err = sac.Evaluate()
			Expect(err).NotTo(HaveOccurred())
		}
		Expect(fakePolicyChecker.CheckPolicyCallCount()).To(Equal(1))
	})

	Context("when the config sequence changes", func() {
		BeforeEach(func() {
			fakePolicyChecker.CheckPolicyReturnsOnCall(2, errors.New("access-now-denied"))
		})

		It("re-evaluates the policy", func() {
			sac, err := deliver.NewSessionAC(fakeChain, envelope, fakePolicyChecker, "chain-id", expiresAt)
			Expect(err).NotTo(HaveOccurred())

			Expect(sac.Evaluate()).To(Succeed())
			Expect(fakePolicyChecker.CheckPolicyCallCount()).To(Equal(1))
			Expect(sac.Evaluate()).To(Succeed())
			Expect(fakePolicyChecker.CheckPolicyCallCount()).To(Equal(1))

			fakeChain.SequenceReturns(2)
			Expect(sac.Evaluate()).To(Succeed())
			Expect(fakePolicyChecker.CheckPolicyCallCount()).To(Equal(2))
			Expect(sac.Evaluate()).To(Succeed())
			Expect(fakePolicyChecker.CheckPolicyCallCount()).To(Equal(2))

			fakeChain.SequenceReturns(3)
			Expect(sac.Evaluate()).To(MatchError("access-now-denied"))
			Expect(fakePolicyChecker.CheckPolicyCallCount()).To(Equal(3))
		})
	})

	Context("when an identity expires", func() {
		BeforeEach(func() {
			expiresAt = func([]byte) time.Time {
				return time.Now().Add(250 * time.Millisecond)
			}
		})

		It("returns an identity expired error", func() {
			sac, err := deliver.NewSessionAC(fakeChain, envelope, fakePolicyChecker, "chain-id", expiresAt)
			Expect(err).NotTo(HaveOccurred())

			err = sac.Evaluate()
			Expect(err).NotTo(HaveOccurred())

			Eventually(sac.Evaluate).Should(MatchError(ContainSubstring("client identity expired")))
		})
	})

	Context("when the envelope cannot be represented as signed data", func() {
		BeforeEach(func() {
			envelope = &cb.Envelope{}
		})

		It("returns an error", func() {
			_, expectedError := protoutil.EnvelopeAsSignedData(envelope)
			Expect(expectedError).To(HaveOccurred())

			_, err := deliver.NewSessionAC(fakeChain, envelope, fakePolicyChecker, "chain-id", expiresAt)
			Expect(err).To(Equal(expectedError))
		})
	})
})
