/*
Copyright 2018 The Kubernetes 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 common_controller

import (
	"errors"
	"testing"
	"time"

	crdv1 "github.com/kubernetes-csi/external-snapshotter/v2/pkg/apis/volumesnapshot/v1beta1"
	"github.com/kubernetes-csi/external-snapshotter/v2/pkg/utils"
	v1 "k8s.io/api/core/v1"
	storagev1beta1 "k8s.io/api/storage/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var metaTimeNow = &metav1.Time{
	Time: time.Now(),
}

var volumeErr = &storagev1beta1.VolumeError{
	Time:    *metaTimeNow,
	Message: "Failed to upload the snapshot",
}

var emptyString = ""

// Test single call to syncSnapshot and syncContent methods.
// 1. Fill in the controller with initial data
// 2. Call the tested function (syncSnapshot/syncContent) via
//    controllerTest.testCall *once*.
// 3. Compare resulting contents and snapshots with expected contents and snapshots.
func TestSync(t *testing.T) {
	size := int64(1)
	tests := []controllerTest{
		{
			// snapshot is bound to a non-existing content
			name:              "2-1 - (dynamic) snapshot is bound to a non-existing content",
			initialContents:   nocontents,
			expectedContents:  nocontents,
			initialSnapshots:  newSnapshotArray("snap2-1", "snapuid2-1", "claim2-1", "", validSecretClass, "content2-1", &True, nil, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap2-1", "snapuid2-1", "claim2-1", "", validSecretClass, "content2-1", &False, nil, nil, newVolumeError("VolumeSnapshotContent is missing"), false, true, nil),
			expectedEvents:    []string{"Warning SnapshotContentMissing"},
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			name:              "2-2 - (static) snapshot points to a content but content does not point to snapshot(VolumeSnapshotRef does not match)",
			initialContents:   newContentArray("content2-2", "snapuid2-2-x", "snap2-2", "sid2-2", validSecretClass, "sid2-2", "", deletionPolicy, nil, nil, false),
			expectedContents:  newContentArray("content2-2", "snapuid2-2-x", "snap2-2", "sid2-2", validSecretClass, "sid2-2", "", deletionPolicy, nil, nil, false),
			initialSnapshots:  newSnapshotArray("snap2-2", "snapuid2-2", "", "content2-2", validSecretClass, "content2-2", &False, nil, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap2-2", "snapuid2-2", "", "content2-2", validSecretClass, "content2-2", &False, nil, nil, newVolumeError("VolumeSnapshotContent [content2-2] is bound to a different snapshot"), false, true, nil),
			expectedEvents:    []string{"Warning SnapshotContentMisbound"},
			errors:            noerrors,
			test:              testSyncSnapshotError,
		},
		{
			name:              "2-3 - (dynamic) success bind snapshot and content but not ready, no status changed",
			initialContents:   newContentArray("snapcontent-snapuid2-3", "snapuid2-3", "snap2-3", "sid2-3", validSecretClass, "", "pv-handle2-3", deletionPolicy, nil, nil, false),
			expectedContents:  newContentArrayWithReadyToUse("snapcontent-snapuid2-3", "snapuid2-3", "snap2-3", "sid2-3", validSecretClass, "", "pv-handle2-3", deletionPolicy, &timeNowStamp, nil, &True, false),
			initialSnapshots:  newSnapshotArray("snap2-3", "snapuid2-3", "claim2-3", "", validSecretClass, "", &False, metaTimeNow, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap2-3", "snapuid2-3", "claim2-3", "", validSecretClass, "snapcontent-snapuid2-3", &True, metaTimeNow, nil, nil, false, true, nil),
			initialClaims:     newClaimArray("claim2-3", "pvc-uid2-3", "1Gi", "volume2-3", v1.ClaimBound, &classEmpty),
			initialVolumes:    newVolumeArray("volume2-3", "pv-uid2-3", "pv-handle2-3", "1Gi", "pvc-uid2-3", "claim2-3", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
			initialSecrets:    []*v1.Secret{secret()},
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			// nothing changed
			name:              "2-4 - (static) noop",
			initialContents:   newContentArray("content2-4", "snapuid2-4", "snap2-4", "sid2-4", validSecretClass, "sid2-4", "", deletionPolicy, nil, nil, false),
			expectedContents:  newContentArray("content2-4", "snapuid2-4", "snap2-4", "sid2-4", validSecretClass, "sid2-4", "", deletionPolicy, nil, nil, false),
			initialSnapshots:  newSnapshotArray("snap2-4", "snapuid2-4", "", "content2-4", validSecretClass, "content2-4", &True, metaTimeNow, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap2-4", "snapuid2-4", "", "content2-4", validSecretClass, "content2-4", &True, metaTimeNow, nil, nil, false, true, nil),
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			name:              "2-5 - (dynamic) snapshot and content bound, status ready false -> true",
			initialContents:   newContentArrayWithReadyToUse("snapcontent-snapuid2-5", "snapuid2-5", "snap2-5", "sid2-5", validSecretClass, "", "pv-handle2-5", deletionPolicy, &timeNowStamp, nil, &False, false),
			expectedContents:  newContentArrayWithReadyToUse("snapcontent-snapuid2-5", "snapuid2-5", "snap2-5", "sid2-5", validSecretClass, "", "pv-handle2-5", deletionPolicy, &timeNowStamp, nil, &False, false),
			initialSnapshots:  newSnapshotArray("snap2-5", "snapuid2-5", "claim2-5", "", validSecretClass, "snapcontent-snapuid2-5", &False, metaTimeNow, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap2-5", "snapuid2-5", "claim2-5", "", validSecretClass, "snapcontent-snapuid2-5", &False, metaTimeNow, nil, nil, false, true, nil),
			initialClaims:     newClaimArray("claim2-5", "pvc-uid2-5", "1Gi", "volume2-5", v1.ClaimBound, &classEmpty),
			initialVolumes:    newVolumeArray("volume2-5", "pv-uid2-5", "pv-handle2-5", "1Gi", "pvc-uid2-5", "claim2-5", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
			initialSecrets:    []*v1.Secret{secret()},
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			name:              "2-6 - (static) snapshot bound to content correctly, status ready false -> true, ref.UID '' -> 'snapuid2-6'",
			initialContents:   newContentArrayWithReadyToUse("content2-6", "", "snap2-6", "sid2-6", validSecretClass, "sid2-6", "", deletionPolicy, &timeNowStamp, nil, &False, false),
			expectedContents:  newContentArrayWithReadyToUse("content2-6", "snapuid2-6", "snap2-6", "sid2-6", validSecretClass, "sid2-6", "", deletionPolicy, &timeNowStamp, nil, &False, false),
			initialSnapshots:  newSnapshotArray("snap2-6", "snapuid2-6", "", "content2-6", validSecretClass, "content2-6", &False, metaTimeNow, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap2-6", "snapuid2-6", "", "content2-6", validSecretClass, "content2-6", &False, metaTimeNow, nil, nil, false, true, nil),
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			name:              "2-8 - snapshot and content bound, apiserver update status error",
			initialContents:   newContentArrayWithReadyToUse("content2-8", "snapuid2-8", "snap2-8", "sid2-8", validSecretClass, "", "", deletionPolicy, &timeNowStamp, nil, &False, false),
			expectedContents:  newContentArrayWithReadyToUse("content2-8", "snapuid2-8", "snap2-8", "sid2-8", validSecretClass, "", "", deletionPolicy, &timeNowStamp, nil, &False, false),
			initialSnapshots:  newSnapshotArray("snap2-8", "snapuid2-8", "claim2-8", "", validSecretClass, "content2-8", &False, metaTimeNow, nil, nil, false, false, nil),
			expectedSnapshots: newSnapshotArray("snap2-8", "snapuid2-8", "claim2-8", "", validSecretClass, "content2-8", &False, metaTimeNow, nil, nil, false, false, nil),
			expectedEvents:    []string{"Warning SnapshotFinalizerError"},
			initialClaims:     newClaimArray("claim2-8", "pvc-uid2-8", "1Gi", "volume2-8", v1.ClaimBound, &classEmpty),
			initialVolumes:    newVolumeArray("volume2-8", "pv-uid2-8", "pv-handle2-8", "1Gi", "pvc-uid2-8", "claim2-8", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
			initialSecrets:    []*v1.Secret{secret()},
			errors: []reactorError{
				// Inject error to the first client.VolumesnapshotV1beta1().VolumeSnapshots().Update call.
				// All other calls will succeed.
				{"update", "volumesnapshots", errors.New("mock update error")},
			},
			test: testSyncSnapshotError,
		},
		{
			name:              "2-9 - fail on status update as there is not pvc provided",
			initialContents:   nocontents,
			expectedContents:  nocontents,
			initialSnapshots:  newSnapshotArray("snap2-9", "snapuid2-9", "claim2-9", "", validSecretClass, "", &False, nil, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap2-9", "snapuid2-9", "claim2-9", "", validSecretClass, "", &False, nil, nil, newVolumeError("Failed to create snapshot content with error snapshot controller failed to update snap2-9 on API server: cannot get claim from snapshot"), false, true, nil),
			errors: []reactorError{
				{"get", "persistentvolumeclaims", errors.New("mock update error")},
				{"get", "persistentvolumeclaims", errors.New("mock update error")},
				{"get", "persistentvolumeclaims", errors.New("mock update error")},
			}, test: testSyncSnapshot,
		},
		{
			name:              "2-10 - (static) do not bind content does not point to the snapshot",
			initialContents:   newContentArray("content2-10", "snapuid2-10-x", "snap2-10", "sid2-10", validSecretClass, "sid2-10", "", deletionPolicy, nil, nil, false),
			expectedContents:  newContentArray("content2-10", "snapuid2-10-x", "snap2-10", "sid2-10", validSecretClass, "sid2-10", "", deletionPolicy, nil, nil, false),
			initialSnapshots:  newSnapshotArray("snap2-10", "snapuid2-10", "", "content2-10", validSecretClass, "", &False, nil, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap2-10", "snapuid2-10", "", "content2-10", validSecretClass, "", &False, nil, nil, newVolumeError("VolumeSnapshotContent [content2-10] is bound to a different snapshot"), false, true, nil),
			expectedEvents:    []string{"Warning SnapshotContentMisbound"},
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			name:              "2-11 - (static) successful bind snapshot content with content classname updated",
			initialContents:   withContentSpecSnapshotClassName(newContentArray("content2-11", "snapuid2-11", "snap2-11", "sid2-11", validSecretClass, "sid2-11", "", deletionPolicy, nil, nil, false), nil),
			expectedContents:  newContentArray("content2-11", "snapuid2-11", "snap2-11", "sid2-11", validSecretClass, "sid2-11", "", deletionPolicy, nil, nil, false),
			initialSnapshots:  newSnapshotArray("snap2-11", "snapuid2-11", "", "content2-11", validSecretClass, "content2-11", &False, nil, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap2-11", "snapuid2-11", "", "content2-11", validSecretClass, "content2-11", &True, nil, nil, nil, false, true, nil),
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			name:              "2-12 - (static) fail bind snapshot content with volume snapshot classname due to API call failed",
			initialContents:   withContentSpecSnapshotClassName(newContentArray("content2-12", "snapuid2-12", "snap2-12", "sid2-12", validSecretClass, "sid2-12", "", deletionPolicy, nil, nil, false), nil),
			expectedContents:  withContentSpecSnapshotClassName(newContentArray("content2-12", "snapuid2-12", "snap2-12", "sid2-12", validSecretClass, "sid2-12", "", deletionPolicy, nil, nil, false), nil),
			initialSnapshots:  newSnapshotArray("snap2-12", "snapuid2-12", "", "content2-12", validSecretClass, "content2-12", &False, nil, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap2-12", "snapuid2-12", "", "content2-12", validSecretClass, "content2-12", &False, nil, nil, newVolumeError("Snapshot failed to bind VolumeSnapshotContent, mock update error"), false, true, nil),
			errors: []reactorError{
				// Inject error to the forth client.VolumesnapshotV1beta1().VolumeSnapshots().Update call.
				{"update", "volumesnapshotcontents", errors.New("mock update error")},
			},
			test: testSyncSnapshot,
		},
		{
			name:              "2-13 - (dynamic) snapshot expects a dynamically provisioned content but found one which is pre-proviioned, bind should fail",
			initialContents:   newContentArray("snapcontent-snapuid2-13", "snapuid2-13", "snap2-13", "sid2-13", validSecretClass, "sid2-13", "", deletionPolicy, nil, nil, false),
			expectedContents:  newContentArrayWithReadyToUse("snapcontent-snapuid2-13", "snapuid2-13", "snap2-13", "sid2-13", validSecretClass, "sid2-13", "", deletionPolicy, &timeNowStamp, nil, &True, false),
			initialSnapshots:  newSnapshotArray("snap2-13", "snapuid2-13", "claim2-13", "", validSecretClass, "", &False, metaTimeNow, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap2-13", "snapuid2-13", "claim2-13", "", validSecretClass, "", &False, metaTimeNow, nil, newVolumeError("VolumeSnapshotContent snapcontent-snapuid2-13 is pre-provisioned while expecting a dynamically provisioned one"), false, true, nil),
			initialClaims:     newClaimArray("claim2-13", "pvc-uid2-13", "1Gi", "volume2-13", v1.ClaimBound, &classEmpty),
			initialVolumes:    newVolumeArray("volume2-13", "pv-uid2-13", "pv-handle2-13", "1Gi", "pvc-uid2-13", "claim2-13", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
			expectedEvents:    []string{"Warning SnapshotContentMismatch"},
			initialSecrets:    []*v1.Secret{secret()},
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			// nothing changed
			name:              "2-14 - (dynamic) noop",
			initialContents:   newContentArray("snapcontent-snapuid2-14", "snapuid2-14", "snap2-14", "sid2-14", validSecretClass, "", "pv-handle-2-14", deletionPolicy, nil, nil, false),
			expectedContents:  newContentArray("snapcontent-snapuid2-14", "snapuid2-14", "snap2-14", "sid2-14", validSecretClass, "", "pv-handle-2-14", deletionPolicy, nil, nil, false),
			initialSnapshots:  newSnapshotArray("snap2-14", "snapuid2-14", "claim2-14", "", validSecretClass, "snapcontent-snapuid2-14", &True, metaTimeNow, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap2-14", "snapuid2-14", "claim2-14", "", validSecretClass, "snapcontent-snapuid2-14", &True, metaTimeNow, nil, nil, false, true, nil),
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			name:              "3-1 - (dynamic) ready snapshot lost reference to VolumeSnapshotContent",
			initialContents:   nocontents,
			expectedContents:  nocontents,
			initialSnapshots:  newSnapshotArray("snap3-1", "snapuid3-1", "claim3-1", "", validSecretClass, "snapcontent-snapuid3-1", &True, metaTimeNow, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap3-1", "snapuid3-1", "claim3-1", "", validSecretClass, "snapcontent-snapuid3-1", &False, metaTimeNow, nil, newVolumeError("VolumeSnapshotContent is missing"), false, true, nil),
			errors:            noerrors,
			expectedEvents:    []string{"Warning SnapshotContentMissing"},
			test:              testSyncSnapshot,
		},
		{
			name:              "3-2 - (static) ready snapshot bound to none-exist content",
			initialContents:   nocontents,
			expectedContents:  nocontents,
			initialSnapshots:  newSnapshotArray("snap3-2", "snapuid3-2", "", "content3-2", validSecretClass, "content3-2", &True, metaTimeNow, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap3-2", "snapuid3-2", "", "content3-2", validSecretClass, "content3-2", &False, metaTimeNow, nil, newVolumeError("VolumeSnapshotContent is missing"), false, true, nil),
			errors:            noerrors,
			expectedEvents:    []string{"Warning SnapshotContentMissing"},
			test:              testSyncSnapshot,
		},
		{
			name:              "3-3 - (static) ready snapshot(everything is well, do nothing)",
			initialContents:   newContentArray("content3-3", "snapuid3-3", "snap3-3", "sid3-3", validSecretClass, "sid3-3", "", deletionPolicy, nil, nil, false),
			expectedContents:  newContentArray("content3-3", "snapuid3-3", "snap3-3", "sid3-3", validSecretClass, "sid3-3", "", deletionPolicy, nil, nil, false),
			initialSnapshots:  newSnapshotArray("snap3-3", "snapuid3-3", "", "content3-3", validSecretClass, "content3-3", &True, metaTimeNow, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap3-3", "snapuid3-3", "", "content3-3", validSecretClass, "content3-3", &True, metaTimeNow, nil, nil, false, true, nil),
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			name:              "3-4 - (static) ready snapshot misbound to VolumeSnapshotContent",
			initialContents:   newContentArray("content3-4", "snapuid3-4-x", "snap3-4", "sid3-4", validSecretClass, "sid3-4", "", deletionPolicy, nil, nil, false),
			expectedContents:  newContentArray("content3-4", "snapuid3-4-x", "snap3-4", "sid3-4", validSecretClass, "sid3-4", "", deletionPolicy, nil, nil, false),
			initialSnapshots:  newSnapshotArray("snap3-4", "snapuid3-4", "", "content3-4", validSecretClass, "content3-4", &True, metaTimeNow, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap3-4", "snapuid3-4", "", "content3-4", validSecretClass, "content3-4", &False, metaTimeNow, nil, newVolumeError("VolumeSnapshotContent [content3-4] is bound to a different snapshot"), false, true, nil),
			expectedEvents:    []string{"Warning SnapshotContentMisbound"},
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			name:              "3-5 - (dynamic) ready snapshot(everything is well, do nothing)",
			initialContents:   newContentArray("snapcontent-snapuid3-5", "snapuid3-5", "snap3-5", "sid3-5", validSecretClass, "", "volume-handle-3-5", deletionPolicy, nil, nil, false),
			expectedContents:  newContentArray("snapcontent-snapuid3-5", "snapuid3-5", "snap3-5", "sid3-5", validSecretClass, "", "volume-handle-3-5", deletionPolicy, nil, nil, false),
			initialSnapshots:  newSnapshotArray("snap3-5", "snapuid3-5", "claim3-5", "", validSecretClass, "snapcontent-snapuid3-5", &True, metaTimeNow, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap3-5", "snapuid3-5", "claim3-5", "", validSecretClass, "snapcontent-snapuid3-5", &True, metaTimeNow, nil, nil, false, true, nil),
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			name:              "3-6 - (dynamic) ready snapshot misbound to VolumeSnapshotContent",
			initialContents:   newContentArray("snapcontent-snapuid3-6", "snapuid3-6-x", "snap3-6", "sid3-6", validSecretClass, "", "volume-handle-3-6", deletionPolicy, nil, nil, false),
			expectedContents:  newContentArray("snapcontent-snapuid3-6", "snapuid3-6-x", "snap3-6", "sid3-6", validSecretClass, "", "volume-handle-3-6", deletionPolicy, nil, nil, false),
			initialSnapshots:  newSnapshotArray("snap3-6", "snapuid3-6", "claim3-6", "", validSecretClass, "snapcontent-snapuid3-6", &True, metaTimeNow, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap3-6", "snapuid3-6", "claim3-6", "", validSecretClass, "snapcontent-snapuid3-6", &False, metaTimeNow, nil, newVolumeError("VolumeSnapshotContent [snapcontent-snapuid3-6] is bound to a different snapshot"), false, true, nil),
			expectedEvents:    []string{"Warning SnapshotContentMisbound"},
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			name:              "4-1 - (dynamic) content bound to snapshot, snapshot status missing and rebuilt",
			initialContents:   newContentArrayWithReadyToUse("snapcontent-snapuid4-1", "snapuid4-1", "snap4-1", "sid4-1", validSecretClass, "", "pv-handle4-1", deletionPolicy, nil, &size, &True, false),
			expectedContents:  newContentArrayWithReadyToUse("snapcontent-snapuid4-1", "snapuid4-1", "snap4-1", "sid4-1", validSecretClass, "", "pv-handle4-1", deletionPolicy, nil, &size, &True, false),
			initialSnapshots:  newSnapshotArray("snap4-1", "snapuid4-1", "claim4-1", "", validSecretClass, "", &False, nil, nil, nil, true, true, nil),
			expectedSnapshots: newSnapshotArray("snap4-1", "snapuid4-1", "claim4-1", "", validSecretClass, "snapcontent-snapuid4-1", &True, nil, getSize(1), nil, false, true, nil),
			initialClaims:     newClaimArray("claim4-1", "pvc-uid4-1", "1Gi", "volume4-1", v1.ClaimBound, &classEmpty),
			initialVolumes:    newVolumeArray("volume4-1", "pv-uid4-1", "pv-handle4-1", "1Gi", "pvc-uid4-1", "claim4-1", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
			initialSecrets:    []*v1.Secret{secret()},
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			name:              "4-2 - (dynamic) snapshot and content bound, ReadyToUse in snapshot status missing and rebuilt",
			initialContents:   newContentArrayWithReadyToUse("snapcontent-snapuid4-2", "snapuid4-2", "snap4-2", "sid4-2", validSecretClass, "", "pv-handle4-2", deletionPolicy, nil, nil, &True, false),
			expectedContents:  newContentArrayWithReadyToUse("snapcontent-snapuid4-2", "snapuid4-2", "snap4-2", "sid4-2", validSecretClass, "", "pv-handle4-2", deletionPolicy, nil, nil, &True, false),
			initialSnapshots:  newSnapshotArray("snap4-2", "snapuid4-2", "claim4-2", "", validSecretClass, "snapcontent-snapuid4-2", &False, nil, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap4-2", "snapuid4-2", "claim4-2", "", validSecretClass, "snapcontent-snapuid4-2", &True, nil, nil, nil, false, true, nil),
			initialClaims:     newClaimArray("claim4-2", "pvc-uid4-2", "1Gi", "volume4-2", v1.ClaimBound, &classEmpty),
			initialVolumes:    newVolumeArray("volume4-2", "pv-uid4-2", "pv-handle4-2", "1Gi", "pvc-uid4-2", "claim4-2", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
			initialSecrets:    []*v1.Secret{secret()},
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			name:              "4-3 - (dynamic) content bound to snapshot, fields in snapshot status missing and rebuilt",
			initialContents:   newContentArrayWithReadyToUse("snapcontent-snapuid4-3", "snapuid4-3", "snap4-3", "sid4-3", validSecretClass, "", "pv-handle4-3", deletionPolicy, nil, &size, &True, false),
			expectedContents:  newContentArrayWithReadyToUse("snapcontent-snapuid4-3", "snapuid4-3", "snap4-3", "sid4-3", validSecretClass, "", "pv-handle4-3", deletionPolicy, nil, &size, &True, false),
			initialSnapshots:  newSnapshotArray("snap4-3", "snapuid4-3", "claim4-3", "", validSecretClass, "", &False, nil, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap4-3", "snapuid4-3", "claim4-3", "", validSecretClass, "snapcontent-snapuid4-3", &True, nil, getSize(1), nil, false, true, nil),
			initialClaims:     newClaimArray("claim4-3", "pvc-uid4-3", "1Gi", "volume4-3", v1.ClaimBound, &classEmpty),
			initialVolumes:    newVolumeArray("volume4-3", "pv-uid4-3", "pv-handle4-3", "1Gi", "pvc-uid4-3", "claim4-3", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
			initialSecrets:    []*v1.Secret{secret()},
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			name:              "4-4 - (dynamic) content bound to snapshot, fields in snapshot status missing and rebuilt",
			initialContents:   newContentArrayWithReadyToUse("content4-4", "snapuid4-4", "snap4-4", "sid4-4", validSecretClass, "sid4-4", "", deletionPolicy, nil, &size, &True, false),
			expectedContents:  newContentArrayWithReadyToUse("content4-4", "snapuid4-4", "snap4-4", "sid4-4", validSecretClass, "sid4-4", "", deletionPolicy, nil, &size, &True, false),
			initialSnapshots:  newSnapshotArray("snap4-4", "snapuid4-4", "", "content4-4", validSecretClass, "", &False, nil, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap4-4", "snapuid4-4", "", "content4-4", validSecretClass, "content4-4", &True, nil, getSize(1), nil, false, true, nil),
			initialSecrets:    []*v1.Secret{secret()},
			errors:            noerrors,
			test:              testSyncSnapshot,
		},
		{
			name:             "5-1 - content missing finalizer is updated to have finalizer",
			initialContents:  newContentArray("content5-1", "snapuid5-1", "snap5-1", "sid5-1", validSecretClass, "", "pv-handle5-1", deletionPolicy, nil, nil, false),
			expectedContents: newContentArray("content5-1", "snapuid5-1", "snap5-1", "sid5-1", validSecretClass, "", "pv-handle5-1", deletionPolicy, nil, nil, true),
			initialClaims:    newClaimArray("claim5-1", "pvc-uid5-1", "1Gi", "volume5-1", v1.ClaimBound, &classEmpty),
			initialVolumes:   newVolumeArray("volume5-1", "pv-uid5-1", "pv-handle5-1", "1Gi", "pvc-uid5-1", "claim5-1", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
			initialSecrets:   []*v1.Secret{secret()},
			errors:           noerrors,
			test:             testSyncContent,
		},
		{
			name:             "5-2 - content missing finalizer update attempt fails because of failed API call",
			initialContents:  newContentArray("content5-2", "snapuid5-2", "snap5-2", "sid5-2", validSecretClass, "", "pv-handle5-2", deletionPolicy, nil, nil, false),
			expectedContents: newContentArray("content5-2", "snapuid5-2", "snap5-2", "sid5-2", validSecretClass, "", "pv-handle5-2", deletionPolicy, nil, nil, false),
			initialClaims:    newClaimArray("claim5-2", "pvc-uid5-2", "1Gi", "volume5-2", v1.ClaimBound, &classEmpty),
			initialVolumes:   newVolumeArray("volume5-2", "pv-uid5-2", "pv-handle5-2", "1Gi", "pvc-uid5-2", "claim5-2", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
			initialSecrets:   []*v1.Secret{secret()},
			errors: []reactorError{
				// Inject error to the forth client.VolumesnapshotV1beta1().VolumeSnapshots().Update call.
				{"update", "volumesnapshotcontents", errors.New("mock update error")},
			},
			expectSuccess: false,
			test:          testSyncContentError,
		},
		{
			name:              "5-3 - (dynamic) snapshot deletion candidate marked for deletion",
			initialSnapshots:  newSnapshotArray("snap5-3", "snapuid5-3", "claim5-3", "", validSecretClass, "snapcontent-snapuid5-3", &False, nil, nil, nil, false, true, &timeNowMetav1),
			expectedSnapshots: newSnapshotArray("snap5-3", "snapuid5-3", "claim5-3", "", validSecretClass, "snapcontent-snapuid5-3", &False, nil, nil, nil, false, true, &timeNowMetav1),
			initialContents:   newContentArray("snapcontent-snapuid5-3", "snapuid5-3", "snap5-3", "sid5-3", validSecretClass, "", "pv-handle5-3", deletionPolicy, nil, nil, true),
			expectedContents:  withContentAnnotations(newContentArray("snapcontent-snapuid5-3", "snapuid5-3", "snap5-3", "sid5-3", validSecretClass, "", "pv-handle5-3", deletionPolicy, nil, nil, true), map[string]string{utils.AnnVolumeSnapshotBeingDeleted: "yes"}),
			initialClaims:     newClaimArray("claim5-3", "pvc-uid5-3", "1Gi", "volume5-3", v1.ClaimBound, &classEmpty),
			initialVolumes:    newVolumeArray("volume5-3", "pv-uid5-3", "pv-handle5-3", "1Gi", "pvc-uid5-3", "claim5-3", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
			initialSecrets:    []*v1.Secret{secret()},
			expectSuccess:     true,
			test:              testSyncContent,
		},
		{
			name:              "5-4 - (dynamic) snapshot deletion candidate fail to mark for deletion due to failed API call",
			initialSnapshots:  newSnapshotArray("snap5-4", "snapuid5-4", "claim5-4", "", validSecretClass, "snapcontent-snapuid5-4", &False, nil, nil, nil, false, true, &timeNowMetav1),
			expectedSnapshots: newSnapshotArray("snap5-4", "snapuid5-4", "claim5-4", "", validSecretClass, "snapcontent-snapuid5-4", &False, nil, nil, nil, false, true, &timeNowMetav1),
			initialContents:   newContentArray("snapcontent-snapuid5-4", "snapuid5-4", "snap5-4", "sid5-4", validSecretClass, "", "pv-handle5-4", deletionPolicy, nil, nil, true),
			// result of the test framework - annotation is still set in memory, but update call fails.
			expectedContents: withContentAnnotations(newContentArray("snapcontent-snapuid5-4", "snapuid5-4", "snap5-4", "sid5-4", validSecretClass, "", "pv-handle5-4", deletionPolicy, nil, nil, true), map[string]string{utils.AnnVolumeSnapshotBeingDeleted: "yes"}),
			initialClaims:    newClaimArray("claim5-4", "pvc-uid5-4", "1Gi", "volume5-4", v1.ClaimBound, &classEmpty),
			initialVolumes:   newVolumeArray("volume5-4", "pv-uid5-4", "pv-handle5-4", "1Gi", "pvc-uid5-4", "claim5-4", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
			initialSecrets:   []*v1.Secret{secret()},
			errors: []reactorError{
				// Inject error to the forth client.VolumesnapshotV1beta1().VolumeSnapshots().Update call.
				{"update", "volumesnapshotcontents", errors.New("mock update error")},
			},
			expectSuccess: false,
			test:          testSyncContentError,
		},
		{
			name:              "5-5 - (dynamic) snapshot deletion candidate marked for deletion by syncSnapshot",
			initialSnapshots:  newSnapshotArray("snap5-5", "snapuid5-5", "claim5-5", "", validSecretClass, "snapcontent-snapuid5-5", &False, nil, nil, nil, false, true, &timeNowMetav1),
			expectedSnapshots: newSnapshotArray("snap5-5", "snapuid5-5", "claim5-5", "", validSecretClass, "snapcontent-snapuid5-5", &False, nil, nil, nil, false, false, &timeNowMetav1),
			initialContents:   newContentArray("snapcontent-snapuid5-5", "snapuid5-5", "snap5-5", "sid5-5", validSecretClass, "", "pv-handle5-5", crdv1.VolumeSnapshotContentRetain, nil, nil, true),
			expectedContents:  withContentAnnotations(newContentArray("snapcontent-snapuid5-5", "snapuid5-5", "snap5-5", "sid5-5", validSecretClass, "", "pv-handle5-5", crdv1.VolumeSnapshotContentRetain, nil, nil, true), map[string]string{utils.AnnVolumeSnapshotBeingDeleted: "yes"}),
			initialClaims:     newClaimArray("claim5-5", "pvc-uid5-5", "1Gi", "volume5-5", v1.ClaimBound, &classEmpty),
			initialVolumes:    newVolumeArray("volume5-5", "pv-uid5-5", "pv-handle5-5", "1Gi", "pvc-uid5-5", "claim5-5", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
			initialSecrets:    []*v1.Secret{secret()},
			expectSuccess:     true,
			test:              testSyncSnapshot,
		},
		{
			name:              "5-6 - (static) snapshot deletion candidate marked for deletion",
			initialSnapshots:  newSnapshotArray("snap5-6", "snapuid5-6", "", "content5-6", validSecretClass, "content5-6", &False, nil, nil, nil, false, true, &timeNowMetav1),
			expectedSnapshots: newSnapshotArray("snap5-6", "snapuid5-6", "", "content5-6", validSecretClass, "content5-6", &False, nil, nil, nil, false, true, &timeNowMetav1),
			initialContents:   newContentArray("content5-6", "snapuid5-6", "snap5-6", "sid5-6", validSecretClass, "sid5-6", "", deletionPolicy, nil, nil, true),
			expectedContents:  withContentAnnotations(newContentArray("content5-6", "snapuid5-6", "snap5-6", "sid5-6", validSecretClass, "sid5-6", "", deletionPolicy, nil, nil, true), map[string]string{utils.AnnVolumeSnapshotBeingDeleted: "yes"}),
			initialSecrets:    []*v1.Secret{secret()},
			expectSuccess:     true,
			test:              testSyncContent,
		},
		{
			name:              "5-7 - (static) snapshot deletion candidate fail to mark for deletion due to failed API call",
			initialSnapshots:  newSnapshotArray("snap5-7", "snapuid5-7", "", "content5-7", validSecretClass, "content5-7", &False, nil, nil, nil, false, true, &timeNowMetav1),
			expectedSnapshots: newSnapshotArray("snap5-7", "snapuid5-7", "", "content5-7", validSecretClass, "content5-7", &False, nil, nil, nil, false, true, &timeNowMetav1),
			initialContents:   newContentArray("content5-7", "snapuid5-7", "snap5-7", "sid5-7", validSecretClass, "sid5-7", "", deletionPolicy, nil, nil, true),
			// result of the test framework - annotation is still set in memory, but update call fails.
			expectedContents: withContentAnnotations(newContentArray("content5-7", "snapuid5-7", "snap5-7", "sid5-7", validSecretClass, "sid5-7", "", deletionPolicy, nil, nil, true), map[string]string{utils.AnnVolumeSnapshotBeingDeleted: "yes"}),
			initialSecrets:   []*v1.Secret{secret()},
			errors: []reactorError{
				// Inject error to the forth client.VolumesnapshotV1beta1().VolumeSnapshots().Update call.
				{"update", "volumesnapshotcontents", errors.New("mock update error")},
			},
			expectSuccess: false,
			test:          testSyncContentError,
		},
		{
			name:              "5-8 - (dynamic) snapshot deletion candidate marked for deletion by syncSnapshot",
			initialSnapshots:  newSnapshotArray("snap5-8", "snapuid5-8", "", "content5-8", validSecretClass, "content5-8", &False, nil, nil, nil, false, true, &timeNowMetav1),
			expectedSnapshots: newSnapshotArray("snap5-8", "snapuid5-8", "", "content5-8", validSecretClass, "content5-8", &False, nil, nil, nil, false, false, &timeNowMetav1),
			initialContents:   newContentArray("content5-8", "snapuid5-8", "snap5-8", "sid5-8", validSecretClass, "sid5-8", "", crdv1.VolumeSnapshotContentRetain, nil, nil, true),
			expectedContents:  withContentAnnotations(newContentArray("content5-8", "snapuid5-8", "snap5-8", "sid5-8", validSecretClass, "sid5-8", "", crdv1.VolumeSnapshotContentRetain, nil, nil, true), map[string]string{utils.AnnVolumeSnapshotBeingDeleted: "yes"}),
			initialSecrets:    []*v1.Secret{secret()},
			expectSuccess:     true,
			test:              testSyncSnapshot,
		},
		{
			name:              "7-1 - fail to create snapshot with non-existing snapshot class",
			initialContents:   nocontents,
			expectedContents:  nocontents,
			initialSnapshots:  newSnapshotArray("snap7-1", "snapuid7-1", "claim7-1", "", classNonExisting, "", &False, nil, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap7-1", "snapuid7-1", "claim7-1", "", classNonExisting, "", &False, nil, nil, newVolumeError("Failed to create snapshot content with error failed to get input parameters to create snapshot snap7-1: \"volumesnapshotclass.snapshot.storage.k8s.io \\\"non-existing\\\" not found\""), false, true, nil),
			initialClaims:     newClaimArray("claim7-1", "pvc-uid7-1", "1Gi", "volume7-1", v1.ClaimBound, &classEmpty),
			initialVolumes:    newVolumeArray("volume7-1", "pv-uid7-1", "pv-handle7-1", "1Gi", "pvc-uid7-1", "claim7-1", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
			expectedEvents:    []string{"Warning SnapshotContentCreationFailed"},
			errors:            noerrors,
			expectSuccess:     false,
			test:              testSyncSnapshot,
		},
		// TODO(xiangqian@): remove test cases 7-2/7-3 when webhooks are in place
		//                   https://github.com/kubernetes-csi/external-snapshotter/issues/187
		{
			name:              "7-2 - validation fail if neither VolumeSnapshotContentName nor PersistentVolumeClaimName has been specified in Snapshot.Spec.Source",
			initialContents:   nocontents,
			expectedContents:  nocontents,
			initialSnapshots:  newSnapshotArray("snap7-2", "snapuid7-2", "", "", validSecretClass, "", &False, nil, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap7-2", "snapuid7-2", "", "", validSecretClass, "", &False, nil, nil, newVolumeError("Exactly one of PersistentVolumeClaimName and VolumeSnapshotContentName should be specified"), false, true, nil),
			expectedEvents:    []string{"Warning SnapshotValidationError"},
			errors:            noerrors,
			expectSuccess:     false,
			test:              testSyncSnapshot,
		},
		{
			name:              "7-3 - validation fail if both VolumeSnapshotContentName and PersistentVolumeClaimName have been specified in Snapshot.Spec.Source",
			initialContents:   nocontents,
			expectedContents:  nocontents,
			initialSnapshots:  newSnapshotArray("snap7-3", "snapuid7-3", "claim7-3", "snaphandle7-3", validSecretClass, "", &False, nil, nil, nil, false, true, nil),
			expectedSnapshots: newSnapshotArray("snap7-3", "snapuid7-3", "claim7-3", "snaphandle7-3", validSecretClass, "", &False, nil, nil, newVolumeError("Exactly one of PersistentVolumeClaimName and VolumeSnapshotContentName should be specified"), false, true, nil),
			initialClaims:     newClaimArray("claim7-3", "pvc-uid7-3", "1Gi", "volume7-3", v1.ClaimBound, &classEmpty),
			initialVolumes:    newVolumeArray("volume7-3", "pv-uid7-3", "pv-handle7-3", "1Gi", "pvc-uid7-3", "claim7-3", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classEmpty),
			expectedEvents:    []string{"Warning SnapshotValidationError"},
			errors:            noerrors,
			expectSuccess:     false,
			test:              testSyncSnapshot,
		},
		{
			name:              "7-4 - validation fail if both SnapshotHandle and VolumeHandle have been specified in Content.Spec.Source",
			initialSnapshots:  nosnapshots,
			expectedSnapshots: nosnapshots,
			initialContents:   newContentArray("content7-4", "snapuid7-4", "snap7-4", "sid7-4", validSecretClass, "sid7-4", "pv-handle7-4", deletionPolicy, nil, nil, true),
			expectedContents:  newContentArray("content7-4", "snapuid7-4", "snap7-4", "sid7-4", validSecretClass, "sid7-4", "pv-handle7-4", deletionPolicy, nil, nil, true),
			expectedEvents:    []string{"Warning ContentValidationError"},
			errors:            noerrors,
			expectSuccess:     false,
			test:              testSyncContentError,
		},
		{
			name:              "7-5 - validation fail if neither SnapshotHandle or VolumeHandle has been specified in Content.Spec.Source",
			initialSnapshots:  nosnapshots,
			expectedSnapshots: nosnapshots,
			initialContents:   newContentArray("content7-4", "snapuid7-4", "snap7-4", "sid7-4", validSecretClass, "", "", deletionPolicy, nil, nil, true),
			expectedContents:  newContentArray("content7-4", "snapuid7-4", "snap7-4", "sid7-4", validSecretClass, "", "", deletionPolicy, nil, nil, true),
			expectedEvents:    []string{"Warning ContentValidationError"},
			errors:            noerrors,
			expectSuccess:     false,
			test:              testSyncContentError,
		},
	}

	runSyncTests(t, tests, snapshotClasses)
}
