/*
Copyright the Velero contributors.

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 output

import (
	"bytes"
	"testing"
	"text/tabwriter"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	corev1api "k8s.io/api/core/v1"

	"github.com/vmware-tanzu/velero/internal/volume"
	velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
	velerov2alpha1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v2alpha1"
	"github.com/vmware-tanzu/velero/pkg/builder"
	"github.com/vmware-tanzu/velero/pkg/itemoperation"
)

func TestDescribeUploaderConfig(t *testing.T) {
	input := builder.ForBackup("test-ns", "test-backup-1").ParallelFilesUpload(10).Result().Spec
	d := &Describer{
		Prefix: "",
		out:    &tabwriter.Writer{},
		buf:    &bytes.Buffer{},
	}
	d.out.Init(d.buf, 0, 8, 2, ' ', 0)
	DescribeUploaderConfigForBackup(d, input)
	d.out.Flush()
	expect := `Uploader config:
  Parallel files upload:  10
`
	assert.Equal(t, expect, d.buf.String())
}

func TestDescribeResourcePolicies(t *testing.T) {
	input := &corev1api.TypedLocalObjectReference{
		Kind: "configmap",
		Name: "test-resource-policy",
	}
	d := &Describer{
		Prefix: "",
		out:    &tabwriter.Writer{},
		buf:    &bytes.Buffer{},
	}
	d.out.Init(d.buf, 0, 8, 2, ' ', 0)
	DescribeResourcePolicies(d, input)
	d.out.Flush()
	expect := `Resource policies:
  Type:  configmap
  Name:  test-resource-policy
`
	assert.Equal(t, expect, d.buf.String())
}

func TestDescribeBackupSpec(t *testing.T) {
	input1 := builder.ForBackup("test-ns", "test-backup-1").
		IncludedNamespaces("inc-ns-1", "inc-ns-2").
		ExcludedNamespaces("exc-ns-1", "exc-ns-2").
		IncludedResources("inc-res-1", "inc-res-2").
		ExcludedResources("exc-res-1", "exc-res-2").
		StorageLocation("backup-location").
		TTL(72 * time.Hour).
		CSISnapshotTimeout(10 * time.Minute).
		DataMover("mover").
		Hooks(velerov1api.BackupHooks{
			Resources: []velerov1api.BackupResourceHookSpec{
				{
					Name: "hook-1",
					PreHooks: []velerov1api.BackupResourceHook{
						{
							Exec: &velerov1api.ExecHook{
								Container: "hook-container-1",
								Command:   []string{"pre"},
								OnError:   velerov1api.HookErrorModeContinue,
							},
						},
					},
					PostHooks: []velerov1api.BackupResourceHook{
						{
							Exec: &velerov1api.ExecHook{
								Container: "hook-container-1",
								Command:   []string{"post"},
								OnError:   velerov1api.HookErrorModeContinue,
							},
						},
					},
					IncludedNamespaces: []string{"hook-inc-ns-1", "hook-inc-ns-2"},
					ExcludedNamespaces: []string{"hook-exc-ns-1", "hook-exc-ns-2"},
					IncludedResources:  []string{"hook-inc-res-1", "hook-inc-res-2"},
					ExcludedResources:  []string{"hook-exc-res-1", "hook-exc-res-2"},
				},
			},
		}).Result().Spec

	expect1 := `Namespaces:
  Included:  inc-ns-1, inc-ns-2
  Excluded:  exc-ns-1, exc-ns-2

Resources:
  Included:        inc-res-1, inc-res-2
  Excluded:        exc-res-1, exc-res-2
  Cluster-scoped:  auto

Label selector:  <none>

Or label selector:  <none>

Storage Location:  backup-location

Velero-Native Snapshot PVs:  auto
Snapshot Move Data:          auto
Data Mover:                  mover

TTL:  72h0m0s

CSISnapshotTimeout:    10m0s
ItemOperationTimeout:  0s

Hooks:
  Resources:
    hook-1:
      Namespaces:
        Included:  hook-inc-ns-1, hook-inc-ns-2
        Excluded:  hook-exc-ns-1, hook-exc-ns-2

      Resources:
        Included:  hook-inc-res-1, hook-inc-res-2
        Excluded:  hook-exc-res-1, hook-exc-res-2

      Label selector:  <none>

      Pre Exec Hook:
        Container:  hook-container-1
        Command:    pre
        On Error:   Continue
        Timeout:    0s

      Post Exec Hook:
        Container:  hook-container-1
        Command:    post
        On Error:   Continue
        Timeout:    0s
`

	input2 := builder.ForBackup("test-ns", "test-backup-2").
		IncludedNamespaces("inc-ns-1", "inc-ns-2").
		ExcludedNamespaces("exc-ns-1", "exc-ns-2").
		IncludedClusterScopedResources("inc-cluster-res-1", "inc-cluster-res-2").
		IncludedNamespaceScopedResources("inc-ns-res-1", "inc-ns-res-2").
		ExcludedClusterScopedResources("exc-cluster-res-1", "exc-cluster-res-2").
		ExcludedNamespaceScopedResources("exc-ns-res-1", "exc-ns-res-2").
		StorageLocation("backup-location").
		TTL(72 * time.Hour).
		CSISnapshotTimeout(10 * time.Minute).
		DataMover("mover").
		Result().Spec

	expect2 := `Namespaces:
  Included:  inc-ns-1, inc-ns-2
  Excluded:  exc-ns-1, exc-ns-2

Resources:
  Included cluster-scoped:    inc-cluster-res-1, inc-cluster-res-2
  Excluded cluster-scoped:    exc-cluster-res-1, exc-cluster-res-2
  Included namespace-scoped:  inc-ns-res-1, inc-ns-res-2
  Excluded namespace-scoped:  exc-ns-res-1, exc-ns-res-2

Label selector:  <none>

Or label selector:  <none>

Storage Location:  backup-location

Velero-Native Snapshot PVs:  auto
Snapshot Move Data:          auto
Data Mover:                  mover

TTL:  72h0m0s

CSISnapshotTimeout:    10m0s
ItemOperationTimeout:  0s

Hooks:  <none>
`

	input3 := builder.ForBackup("test-ns", "test-backup-3").
		StorageLocation("backup-location").
		OrderedResources(map[string]string{
			"kind1": "rs1-1, rs1-2",
		}).Hooks(velerov1api.BackupHooks{
		Resources: []velerov1api.BackupResourceHookSpec{
			{
				Name: "hook-1",
				PreHooks: []velerov1api.BackupResourceHook{
					{
						Exec: &velerov1api.ExecHook{
							Container: "hook-container-1",
							Command:   []string{"pre"},
							OnError:   velerov1api.HookErrorModeContinue,
						},
					},
				},
				PostHooks: []velerov1api.BackupResourceHook{
					{
						Exec: &velerov1api.ExecHook{
							Container: "hook-container-1",
							Command:   []string{"post"},
							OnError:   velerov1api.HookErrorModeContinue,
						},
					},
				},
			},
		},
	}).Result().Spec

	expect3 := `Namespaces:
  Included:  *
  Excluded:  <none>

Resources:
  Included cluster-scoped:    <none>
  Excluded cluster-scoped:    <none>
  Included namespace-scoped:  *
  Excluded namespace-scoped:  <none>

Label selector:  <none>

Or label selector:  <none>

Storage Location:  backup-location

Velero-Native Snapshot PVs:  auto
Snapshot Move Data:          auto
Data Mover:                  velero

TTL:  0s

CSISnapshotTimeout:    0s
ItemOperationTimeout:  0s

Hooks:
  Resources:
    hook-1:
      Namespaces:
        Included:  *
        Excluded:  <none>

      Resources:
        Included:  *
        Excluded:  <none>

      Label selector:  <none>

      Pre Exec Hook:
        Container:  hook-container-1
        Command:    pre
        On Error:   Continue
        Timeout:    0s

      Post Exec Hook:
        Container:  hook-container-1
        Command:    post
        On Error:   Continue
        Timeout:    0s

OrderedResources:
  kind1: rs1-1, rs1-2
`
	input4 := builder.ForBackup("test-ns", "test-backup-4").
		DefaultVolumesToFsBackup(true).
		StorageLocation("backup-location").
		Result().Spec

	expect4 := `Namespaces:
  Included:  *
  Excluded:  <none>

Resources:
  Included cluster-scoped:    <none>
  Excluded cluster-scoped:    <none>
  Included namespace-scoped:  *
  Excluded namespace-scoped:  <none>

Label selector:  <none>

Or label selector:  <none>

Storage Location:  backup-location

Velero-Native Snapshot PVs:    auto
File System Backup (Default):  true
Snapshot Move Data:            auto
Data Mover:                    velero

TTL:  0s

CSISnapshotTimeout:    0s
ItemOperationTimeout:  0s

Hooks:  <none>
`

	input5 := builder.ForBackup("test-ns", "test-backup-5").
		DefaultVolumesToFsBackup(false).
		StorageLocation("backup-location").
		Result().Spec

	expect5 := `Namespaces:
  Included:  *
  Excluded:  <none>

Resources:
  Included cluster-scoped:    <none>
  Excluded cluster-scoped:    <none>
  Included namespace-scoped:  *
  Excluded namespace-scoped:  <none>

Label selector:  <none>

Or label selector:  <none>

Storage Location:  backup-location

Velero-Native Snapshot PVs:    auto
File System Backup (Default):  false
Snapshot Move Data:            auto
Data Mover:                    velero

TTL:  0s

CSISnapshotTimeout:    0s
ItemOperationTimeout:  0s

Hooks:  <none>
`

	testcases := []struct {
		name   string
		input  velerov1api.BackupSpec
		expect string
	}{
		{
			name:   "old resource filter with hooks",
			input:  input1,
			expect: expect1,
		},
		{
			name:   "new resource filter",
			input:  input2,
			expect: expect2,
		},
		{
			name:   "old resource filter with hooks and ordered resources",
			input:  input3,
			expect: expect3,
		},
		{
			name:   "DefaultVolumesToFsBackup is true",
			input:  input4,
			expect: expect4,
		},
		{
			name:   "DefaultVolumesToFsBackup is false",
			input:  input5,
			expect: expect5,
		},
	}

	for _, tc := range testcases {
		t.Run(tc.name, func(tt *testing.T) {
			d := &Describer{
				Prefix: "",
				out:    &tabwriter.Writer{},
				buf:    &bytes.Buffer{},
			}
			d.out.Init(d.buf, 0, 8, 2, ' ', 0)
			DescribeBackupSpec(d, tc.input)
			d.out.Flush()
			assert.Equal(tt, tc.expect, d.buf.String())
		})
	}
}

func TestDescribeNativeSnapshots(t *testing.T) {
	testcases := []struct {
		name         string
		volumeInfo   []*volume.BackupVolumeInfo
		inputDetails bool
		expect       string
	}{
		{
			name: "no details",
			volumeInfo: []*volume.BackupVolumeInfo{
				{
					BackupMethod: volume.NativeSnapshot,
					PVName:       "pv-1",
					NativeSnapshotInfo: &volume.NativeSnapshotInfo{
						SnapshotHandle: "snapshot-1",
						VolumeType:     "ebs",
						VolumeAZ:       "us-east-2",
						IOPS:           "1000 mbps",
					},
				},
			},
			expect: `  Velero-Native Snapshots:
    pv-1: specify --details for more information
`,
		},
		{
			name: "details",
			volumeInfo: []*volume.BackupVolumeInfo{
				{
					BackupMethod: volume.NativeSnapshot,
					PVName:       "pv-1",
					Result:       volume.VolumeResultSucceeded,
					NativeSnapshotInfo: &volume.NativeSnapshotInfo{
						SnapshotHandle: "snapshot-1",
						VolumeType:     "ebs",
						VolumeAZ:       "us-east-2",
						IOPS:           "1000 mbps",
					},
				},
			},
			inputDetails: true,
			expect: `  Velero-Native Snapshots:
    pv-1:
      Snapshot ID:        snapshot-1
      Type:               ebs
      Availability Zone:  us-east-2
      IOPS:               1000 mbps
      Result:             succeeded
`,
		},
	}

	for _, tc := range testcases {
		t.Run(tc.name, func(tt *testing.T) {
			d := &Describer{
				Prefix: "",
				out:    &tabwriter.Writer{},
				buf:    &bytes.Buffer{},
			}
			d.out.Init(d.buf, 0, 8, 2, ' ', 0)
			describeNativeSnapshots(d, tc.inputDetails, tc.volumeInfo)
			d.out.Flush()
			assert.Equal(t, tc.expect, d.buf.String())
		})
	}
}

func TestCSISnapshots(t *testing.T) {
	testcases := []struct {
		name             string
		volumeInfo       []*volume.BackupVolumeInfo
		inputDetails     bool
		expect           string
		legacyInfoSource bool
	}{
		{
			name:       "empty info, not legacy",
			volumeInfo: []*volume.BackupVolumeInfo{},
			expect: `  CSI Snapshots: <none included>
`,
		},
		{
			name:             "empty info, legacy",
			volumeInfo:       []*volume.BackupVolumeInfo{},
			legacyInfoSource: true,
			expect: `  CSI Snapshots: <none included or not detectable>
`,
		},
		{
			name: "no details, local snapshot",
			volumeInfo: []*volume.BackupVolumeInfo{
				{
					BackupMethod:          volume.CSISnapshot,
					PVCNamespace:          "pvc-ns-1",
					PVCName:               "pvc-1",
					PreserveLocalSnapshot: true,
					CSISnapshotInfo: &volume.CSISnapshotInfo{
						SnapshotHandle: "snapshot-1",
						Size:           1024,
						Driver:         "fake-driver",
						VSCName:        "vsc-1",
						OperationID:    "fake-operation-1",
					},
				},
			},
			expect: `  CSI Snapshots:
    pvc-ns-1/pvc-1:
      Snapshot: included, specify --details for more information
`,
		},
		{
			name: "details, local snapshot",
			volumeInfo: []*volume.BackupVolumeInfo{
				{
					BackupMethod:          volume.CSISnapshot,
					PVCNamespace:          "pvc-ns-2",
					PVCName:               "pvc-2",
					PreserveLocalSnapshot: true,
					Result:                volume.VolumeResultSucceeded,
					CSISnapshotInfo: &volume.CSISnapshotInfo{
						SnapshotHandle: "snapshot-2",
						Size:           1024,
						Driver:         "fake-driver",
						VSCName:        "vsc-2",
						OperationID:    "fake-operation-2",
					},
				},
			},
			inputDetails: true,
			expect: `  CSI Snapshots:
    pvc-ns-2/pvc-2:
      Snapshot:
        Operation ID: fake-operation-2
        Snapshot Content Name: vsc-2
        Storage Snapshot ID: snapshot-2
        Snapshot Size (bytes): 1024
        CSI Driver: fake-driver
        Result: succeeded
`,
		},
		{
			name: "no details, data movement",
			volumeInfo: []*volume.BackupVolumeInfo{
				{
					BackupMethod:      volume.CSISnapshot,
					PVCNamespace:      "pvc-ns-3",
					PVCName:           "pvc-3",
					SnapshotDataMoved: true,
					SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{
						DataMover:      "velero",
						UploaderType:   "fake-uploader",
						SnapshotHandle: "fake-repo-id-3",
						OperationID:    "fake-operation-3",
					},
				},
			},
			expect: `  CSI Snapshots:
    pvc-ns-3/pvc-3:
      Data Movement: included, specify --details for more information
`,
		},
		{
			name: "details, data movement",
			volumeInfo: []*volume.BackupVolumeInfo{
				{
					BackupMethod:      volume.CSISnapshot,
					PVCNamespace:      "pvc-ns-4",
					PVCName:           "pvc-4",
					SnapshotDataMoved: true,
					Result:            volume.VolumeResultSucceeded,
					SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{
						DataMover:      "velero",
						UploaderType:   "fake-uploader",
						SnapshotHandle: "fake-repo-id-4",
						OperationID:    "fake-operation-4",
					},
				},
			},
			inputDetails: true,
			expect: `  CSI Snapshots:
    pvc-ns-4/pvc-4:
      Data Movement:
        Operation ID: fake-operation-4
        Data Mover: velero
        Uploader Type: fake-uploader
        Moved data Size (bytes): 0
        Result: succeeded
`,
		},
		{
			name: "details, data movement, data mover is empty",
			volumeInfo: []*volume.BackupVolumeInfo{
				{
					BackupMethod:      volume.CSISnapshot,
					PVCNamespace:      "pvc-ns-5",
					PVCName:           "pvc-5",
					Result:            volume.VolumeResultFailed,
					SnapshotDataMoved: true,
					SnapshotDataMovementInfo: &volume.SnapshotDataMovementInfo{
						UploaderType:    "fake-uploader",
						SnapshotHandle:  "fake-repo-id-5",
						OperationID:     "fake-operation-5",
						Size:            100,
						IncrementalSize: 50,
						Phase:           velerov2alpha1.DataUploadPhaseFailed,
					},
				},
			},
			inputDetails: true,
			expect: `  CSI Snapshots:
    pvc-ns-5/pvc-5:
      Data Movement:
        Operation ID: fake-operation-5
        Data Mover: velero
        Uploader Type: fake-uploader
        Moved data Size (bytes): 100
        Incremental data Size (bytes): 50
        Result: failed
`,
		},
	}

	for _, tc := range testcases {
		t.Run(tc.name, func(tt *testing.T) {
			d := &Describer{
				Prefix: "",
				out:    &tabwriter.Writer{},
				buf:    &bytes.Buffer{},
			}
			d.out.Init(d.buf, 0, 8, 2, ' ', 0)
			describeCSISnapshots(d, tc.inputDetails, tc.volumeInfo, tc.legacyInfoSource)
			d.out.Flush()
			assert.Equal(t, tc.expect, d.buf.String())
		})
	}
}

func TestDescribePodVolumeBackups(t *testing.T) {
	pvb1 := builder.ForPodVolumeBackup("test-ns", "test-pvb1").
		UploaderType("kopia").
		Phase(velerov1api.PodVolumeBackupPhaseCompleted).
		BackupStorageLocation("bsl-1").
		Volume("vol-1").
		PodName("pod-1").
		PodNamespace("pod-ns-1").
		SnapshotID("snap-1").Result()
	pvb2 := builder.ForPodVolumeBackup("test-ns1", "test-pvb2").
		UploaderType("kopia").
		Phase(velerov1api.PodVolumeBackupPhaseCompleted).
		BackupStorageLocation("bsl-1").
		Volume("vol-2").
		PodName("pod-2").
		PodNamespace("pod-ns-1").
		SnapshotID("snap-2").Result()
	pvb3 := builder.ForPodVolumeBackup("test-ns1", "test-pvb3").
		UploaderType("kopia").
		Phase(velerov1api.PodVolumeBackupPhaseFailed).
		BackupStorageLocation("bsl-1").
		Volume("vol-3").
		PodName("pod-3").
		PodNamespace("pod-ns-1").
		SnapshotID("snap-3").Result()
	pvb4 := builder.ForPodVolumeBackup("test-ns1", "test-pvb4").
		UploaderType("kopia").
		Phase(velerov1api.PodVolumeBackupPhaseCanceled).
		BackupStorageLocation("bsl-1").
		Volume("vol-4").
		PodName("pod-4").
		PodNamespace("pod-ns-1").
		SnapshotID("snap-4").Result()
	pvb5 := builder.ForPodVolumeBackup("test-ns1", "test-pvb5").
		UploaderType("kopia").
		Phase(velerov1api.PodVolumeBackupPhaseInProgress).
		BackupStorageLocation("bsl-1").
		Volume("vol-5").
		PodName("pod-5").
		PodNamespace("pod-ns-1").
		SnapshotID("snap-5").Result()
	pvb6 := builder.ForPodVolumeBackup("test-ns1", "test-pvb6").
		UploaderType("kopia").
		Phase(velerov1api.PodVolumeBackupPhaseCanceling).
		BackupStorageLocation("bsl-1").
		Volume("vol-6").
		PodName("pod-6").
		PodNamespace("pod-ns-1").
		SnapshotID("snap-6").Result()
	pvb7 := builder.ForPodVolumeBackup("test-ns1", "test-pvb7").
		UploaderType("kopia").
		Phase(velerov1api.PodVolumeBackupPhasePrepared).
		BackupStorageLocation("bsl-1").
		Volume("vol-7").
		PodName("pod-7").
		PodNamespace("pod-ns-1").
		SnapshotID("snap-7").Result()
	pvb8 := builder.ForPodVolumeBackup("test-ns1", "test-pvb6").
		UploaderType("kopia").
		Phase(velerov1api.PodVolumeBackupPhaseAccepted).
		BackupStorageLocation("bsl-1").
		Volume("vol-8").
		PodName("pod-8").
		PodNamespace("pod-ns-1").
		SnapshotID("snap-8").Result()

	testcases := []struct {
		name         string
		inputPVBList []velerov1api.PodVolumeBackup
		inputDetails bool
		expect       string
	}{
		{
			name:         "empty list",
			inputPVBList: []velerov1api.PodVolumeBackup{},
			inputDetails: true,
			expect: `  Pod Volume Backups: <none included>
`,
		},
		{
			name:         "2 completed pvbs no details",
			inputPVBList: []velerov1api.PodVolumeBackup{*pvb1, *pvb2},
			inputDetails: false,
			expect: `  Pod Volume Backups - kopia (specify --details for more information):
    Completed:  2
`,
		},
		{
			name:         "2 completed pvbs with details",
			inputPVBList: []velerov1api.PodVolumeBackup{*pvb1, *pvb2},
			inputDetails: true,
			expect: `  Pod Volume Backups - kopia:
    Completed:
      pod-ns-1/pod-1: vol-1
      pod-ns-1/pod-2: vol-2
`,
		},
		{
			name:         "all phases with details",
			inputPVBList: []velerov1api.PodVolumeBackup{*pvb1, *pvb2, *pvb3, *pvb4, *pvb5, *pvb6, *pvb7, *pvb8},
			inputDetails: true,
			expect: `  Pod Volume Backups - kopia:
    Completed:
      pod-ns-1/pod-1: vol-1
      pod-ns-1/pod-2: vol-2
    Failed:
      pod-ns-1/pod-3: vol-3
    Canceled:
      pod-ns-1/pod-4: vol-4
    In Progress:
      pod-ns-1/pod-5: vol-5
    Canceling:
      pod-ns-1/pod-6: vol-6
    Prepared:
      pod-ns-1/pod-7: vol-7
    Accepted:
      pod-ns-1/pod-8: vol-8
`,
		},
	}

	for _, tc := range testcases {
		t.Run(tc.name, func(tt *testing.T) {
			d := &Describer{
				Prefix: "",
				out:    &tabwriter.Writer{},
				buf:    &bytes.Buffer{},
			}
			d.out.Init(d.buf, 0, 8, 2, ' ', 0)
			describePodVolumeBackups(d, tc.inputDetails, tc.inputPVBList)
			d.out.Flush()
			assert.Equal(tt, tc.expect, d.buf.String())
		})
	}
}

func TestDescribeDeleteBackupRequests(t *testing.T) {
	t1, err1 := time.Parse("2006-Jan-02", "2023-Jun-26")
	require.NoError(t, err1)
	dbr1 := builder.ForDeleteBackupRequest("velero", "dbr1").
		ObjectMeta(builder.WithCreationTimestamp(t1)).
		BackupName("bak-1").
		Phase(velerov1api.DeleteBackupRequestPhaseProcessed).
		Errors("some error").Result()
	t2, err2 := time.Parse("2006-Jan-02", "2023-Jun-25")
	require.NoError(t, err2)
	dbr2 := builder.ForDeleteBackupRequest("velero", "dbr2").
		ObjectMeta(builder.WithCreationTimestamp(t2)).
		BackupName("bak-2").
		Phase(velerov1api.DeleteBackupRequestPhaseInProgress).Result()

	testcases := []struct {
		name   string
		input  []velerov1api.DeleteBackupRequest
		expect string
	}{
		{
			name:  "empty list",
			input: []velerov1api.DeleteBackupRequest{},
			expect: `Deletion Attempts:
`,
		},
		{
			name:  "list with one failed and one in-progress request",
			input: []velerov1api.DeleteBackupRequest{*dbr1, *dbr2},
			expect: `Deletion Attempts (1 failed):
  2023-06-26 00:00:00 +0000 UTC: Processed
  Errors:
    some error

  2023-06-25 00:00:00 +0000 UTC: InProgress
`,
		},
	}
	for _, tc := range testcases {
		t.Run(tc.name, func(tt *testing.T) {
			d := &Describer{
				Prefix: "",
				out:    &tabwriter.Writer{},
				buf:    &bytes.Buffer{},
			}
			d.out.Init(d.buf, 0, 8, 2, ' ', 0)
			DescribeDeleteBackupRequests(d, tc.input)
			d.out.Flush()
			assert.Equal(tt, tc.expect, d.buf.String())
		})
	}
}

func TestDescribeBackupItemOperation(t *testing.T) {
	t1, err1 := time.Parse("2006-Jan-02", "2023-Jun-26")
	require.NoError(t, err1)
	t2, err2 := time.Parse("2006-Jan-02", "2023-Jun-25")
	require.NoError(t, err2)
	t3, err3 := time.Parse("2006-Jan-02", "2023-Jun-24")
	require.NoError(t, err3)
	input := builder.ForBackupOperation().
		BackupName("backup-1").
		OperationID("op-1").
		BackupItemAction("action-1").
		ResourceIdentifier("group", "rs-type", "ns", "rs-name").
		Status(*builder.ForOperationStatus().
			Phase(itemoperation.OperationPhaseFailed).
			Error("operation error").
			Progress(50, 100, "bytes").
			Description("operation description").
			Created(t3).
			Started(t2).
			Updated(t1).
			Result()).Result()
	expected := `  Operation for rs-type.group ns/rs-name:
    Backup Item Action Plugin:  action-1
    Operation ID:               op-1
    Phase:                      Failed
    Operation Error:            operation error
    Progress:                   50 of 100 complete (bytes)
    Progress description:       operation description
    Created:                    2023-06-24 00:00:00 +0000 UTC
    Started:                    2023-06-25 00:00:00 +0000 UTC
    Updated:                    2023-06-26 00:00:00 +0000 UTC
`
	d := &Describer{
		Prefix: "",
		out:    &tabwriter.Writer{},
		buf:    &bytes.Buffer{},
	}
	d.out.Init(d.buf, 0, 8, 2, ' ', 0)
	describeBackupItemOperation(d, input)
	d.out.Flush()
	assert.Equal(t, expected, d.buf.String())
}
