//go:build !windows
// +build !windows

package daemon // import "github.com/docker/docker/daemon"

import (
	"encoding/json"
	"fmt"
	"reflect"
	"testing"

	containertypes "github.com/docker/docker/api/types/container"
	mounttypes "github.com/docker/docker/api/types/mount"
	"github.com/docker/docker/container"
	volumemounts "github.com/docker/docker/volume/mounts"
)

func TestBackportMountSpec(t *testing.T) {
	d := Daemon{containers: container.NewMemoryStore()}

	c := &container.Container{
		State: &container.State{},
		MountPoints: map[string]*volumemounts.MountPoint{
			"/apple":      {Destination: "/apple", Source: "/var/lib/docker/volumes/12345678", Name: "12345678", RW: true, CopyData: true}, // anonymous volume
			"/banana":     {Destination: "/banana", Source: "/var/lib/docker/volumes/data", Name: "data", RW: true, CopyData: true},        // named volume
			"/cherry":     {Destination: "/cherry", Source: "/var/lib/docker/volumes/data", Name: "data", CopyData: true},                  // RO named volume
			"/dates":      {Destination: "/dates", Source: "/var/lib/docker/volumes/data", Name: "data"},                                   // named volume nocopy
			"/elderberry": {Destination: "/elderberry", Source: "/var/lib/docker/volumes/data", Name: "data"},                              // masks anon vol
			"/fig":        {Destination: "/fig", Source: "/data", RW: true},                                                                // RW bind
			"/guava":      {Destination: "/guava", Source: "/data", RW: false, Propagation: "shared"},                                      // RO bind + propagation
			"/kumquat":    {Destination: "/kumquat", Name: "data", RW: false, CopyData: true},                                              // volumes-from

			// partially configured mountpoint due to #32613
			// specifically, `mp.Spec.Source` is not set
			"/honeydew": {
				Type:        mounttypes.TypeVolume,
				Destination: "/honeydew",
				Name:        "data",
				Source:      "/var/lib/docker/volumes/data",
				Spec:        mounttypes.Mount{Type: mounttypes.TypeVolume, Target: "/honeydew", VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true}},
			},

			// from hostconfig.Mounts
			"/jambolan": {
				Type:        mounttypes.TypeVolume,
				Destination: "/jambolan",
				Source:      "/var/lib/docker/volumes/data",
				RW:          true,
				Name:        "data",
				Spec:        mounttypes.Mount{Type: mounttypes.TypeVolume, Target: "/jambolan", Source: "data"},
			},
		},
		HostConfig: &containertypes.HostConfig{
			Binds: []string{
				"data:/banana",
				"data:/cherry:ro",
				"data:/dates:ro,nocopy",
				"data:/elderberry:ro,nocopy",
				"/data:/fig",
				"/data:/guava:ro,shared",
				"data:/honeydew:nocopy",
			},
			VolumesFrom: []string{"1:ro"},
			Mounts: []mounttypes.Mount{
				{Type: mounttypes.TypeVolume, Target: "/jambolan"},
			},
		},
		Config: &containertypes.Config{Volumes: map[string]struct{}{
			"/apple":      {},
			"/elderberry": {},
		}},
	}

	d.containers.Add("1", &container.Container{
		State: &container.State{},
		ID:    "1",
		MountPoints: map[string]*volumemounts.MountPoint{
			"/kumquat": {Destination: "/kumquat", Name: "data", RW: false, CopyData: true},
		},
		HostConfig: &containertypes.HostConfig{
			Binds: []string{
				"data:/kumquat:ro",
			},
		},
	})

	type expected struct {
		mp      *volumemounts.MountPoint
		comment string
	}

	pretty := func(mp *volumemounts.MountPoint) string {
		b, err := json.MarshalIndent(mp, "\t", "    ")
		if err != nil {
			return fmt.Sprintf("%#v", mp)
		}
		return string(b)
	}

	for _, x := range []expected{
		{
			mp: &volumemounts.MountPoint{
				Type:        mounttypes.TypeVolume,
				Destination: "/apple",
				RW:          true,
				Name:        "12345678",
				Source:      "/var/lib/docker/volumes/12345678",
				CopyData:    true,
				Spec: mounttypes.Mount{
					Type:   mounttypes.TypeVolume,
					Source: "",
					Target: "/apple",
				},
			},
			comment: "anonymous volume",
		},
		{
			mp: &volumemounts.MountPoint{
				Type:        mounttypes.TypeVolume,
				Destination: "/banana",
				RW:          true,
				Name:        "data",
				Source:      "/var/lib/docker/volumes/data",
				CopyData:    true,
				Spec: mounttypes.Mount{
					Type:   mounttypes.TypeVolume,
					Source: "data",
					Target: "/banana",
				},
			},
			comment: "named volume",
		},
		{
			mp: &volumemounts.MountPoint{
				Type:        mounttypes.TypeVolume,
				Destination: "/cherry",
				Name:        "data",
				Source:      "/var/lib/docker/volumes/data",
				CopyData:    true,
				Spec: mounttypes.Mount{
					Type:     mounttypes.TypeVolume,
					Source:   "data",
					Target:   "/cherry",
					ReadOnly: true,
				},
			},
			comment: "read-only named volume",
		},
		{
			mp: &volumemounts.MountPoint{
				Type:        mounttypes.TypeVolume,
				Destination: "/dates",
				Name:        "data",
				Source:      "/var/lib/docker/volumes/data",
				Spec: mounttypes.Mount{
					Type:          mounttypes.TypeVolume,
					Source:        "data",
					Target:        "/dates",
					ReadOnly:      true,
					VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true},
				},
			},
			comment: "named volume with nocopy",
		},
		{
			mp: &volumemounts.MountPoint{
				Type:        mounttypes.TypeVolume,
				Destination: "/elderberry",
				Name:        "data",
				Source:      "/var/lib/docker/volumes/data",
				Spec: mounttypes.Mount{
					Type:          mounttypes.TypeVolume,
					Source:        "data",
					Target:        "/elderberry",
					ReadOnly:      true,
					VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true},
				},
			},
			comment: "masks an anonymous volume",
		},
		{
			mp: &volumemounts.MountPoint{
				Type:        mounttypes.TypeBind,
				Destination: "/fig",
				Source:      "/data",
				RW:          true,
				Spec: mounttypes.Mount{
					Type:   mounttypes.TypeBind,
					Source: "/data",
					Target: "/fig",
				},
			},
			comment: "bind mount with read/write",
		},
		{
			mp: &volumemounts.MountPoint{
				Type:        mounttypes.TypeBind,
				Destination: "/guava",
				Source:      "/data",
				RW:          false,
				Propagation: "shared",
				Spec: mounttypes.Mount{
					Type:        mounttypes.TypeBind,
					Source:      "/data",
					Target:      "/guava",
					ReadOnly:    true,
					BindOptions: &mounttypes.BindOptions{Propagation: "shared"},
				},
			},
			comment: "bind mount with read/write + shared propagation",
		},
		{
			mp: &volumemounts.MountPoint{
				Type:        mounttypes.TypeVolume,
				Destination: "/honeydew",
				Source:      "/var/lib/docker/volumes/data",
				RW:          true,
				Propagation: "shared",
				Spec: mounttypes.Mount{
					Type:          mounttypes.TypeVolume,
					Source:        "data",
					Target:        "/honeydew",
					VolumeOptions: &mounttypes.VolumeOptions{NoCopy: true},
				},
			},
			comment: "partially configured named volume caused by #32613",
		},
		{
			mp:      &(*c.MountPoints["/jambolan"]), // copy the mountpoint, expect no changes
			comment: "volume defined in mounts API",
		},
		{
			mp: &volumemounts.MountPoint{
				Type:        mounttypes.TypeVolume,
				Destination: "/kumquat",
				Source:      "/var/lib/docker/volumes/data",
				RW:          false,
				Name:        "data",
				Spec: mounttypes.Mount{
					Type:     mounttypes.TypeVolume,
					Source:   "data",
					Target:   "/kumquat",
					ReadOnly: true,
				},
			},
			comment: "partially configured named volume caused by #32613",
		},
	} {

		mp := c.MountPoints[x.mp.Destination]
		d.backportMountSpec(c)

		if !reflect.DeepEqual(mp.Spec, x.mp.Spec) {
			t.Fatalf("%s\nexpected:\n\t%s\n\ngot:\n\t%s", x.comment, pretty(x.mp), pretty(mp))
		}
	}
}
