package dockerfile // import "github.com/docker/docker/builder/dockerfile"

import (
	"context"
	"os"
	"path/filepath"
	"testing"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/pkg/idtools"
	"gotest.tools/v3/assert"
	is "gotest.tools/v3/assert/cmp"
)

func TestChownFlagParsing(t *testing.T) {
	testFiles := map[string]string{
		"passwd": `root:x:0:0::/bin:/bin/false
bin:x:1:1::/bin:/bin/false
wwwwww:x:21:33::/bin:/bin/false
unicorn:x:1001:1002::/bin:/bin/false
		`,
		"group": `root:x:0:
bin:x:1:
wwwwww:x:33:
unicorn:x:1002:
somegrp:x:5555:
othergrp:x:6666:
		`,
	}
	// test mappings for validating use of maps
	idMaps := []idtools.IDMap{
		{
			ContainerID: 0,
			HostID:      100000,
			Size:        65536,
		},
	}
	remapped := idtools.IdentityMapping{UIDMaps: idMaps, GIDMaps: idMaps}
	unmapped := idtools.IdentityMapping{}

	contextDir, cleanup := createTestTempDir(t, "", "builder-chown-parse-test")
	defer cleanup()

	if err := os.Mkdir(filepath.Join(contextDir, "etc"), 0o755); err != nil {
		t.Fatalf("error creating test directory: %v", err)
	}

	for filename, content := range testFiles {
		createTestTempFile(t, filepath.Join(contextDir, "etc"), filename, content, 0o644)
	}

	// positive tests
	for _, testcase := range []struct {
		builder   *Builder
		name      string
		chownStr  string
		idMapping idtools.IdentityMapping
		state     *dispatchState
		expected  idtools.Identity
	}{
		{
			builder:   &Builder{options: &types.ImageBuildOptions{Platform: "linux"}},
			name:      "UIDNoMap",
			chownStr:  "1",
			idMapping: unmapped,
			state:     &dispatchState{},
			expected:  idtools.Identity{UID: 1, GID: 1},
		},
		{
			builder:   &Builder{options: &types.ImageBuildOptions{Platform: "linux"}},
			name:      "UIDGIDNoMap",
			chownStr:  "0:1",
			idMapping: unmapped,
			state:     &dispatchState{},
			expected:  idtools.Identity{UID: 0, GID: 1},
		},
		{
			builder:   &Builder{options: &types.ImageBuildOptions{Platform: "linux"}},
			name:      "UIDWithMap",
			chownStr:  "0",
			idMapping: remapped,
			state:     &dispatchState{},
			expected:  idtools.Identity{UID: 100000, GID: 100000},
		},
		{
			builder:   &Builder{options: &types.ImageBuildOptions{Platform: "linux"}},
			name:      "UIDGIDWithMap",
			chownStr:  "1:33",
			idMapping: remapped,
			state:     &dispatchState{},
			expected:  idtools.Identity{UID: 100001, GID: 100033},
		},
		{
			builder:   &Builder{options: &types.ImageBuildOptions{Platform: "linux"}},
			name:      "UserNoMap",
			chownStr:  "bin:5555",
			idMapping: unmapped,
			state:     &dispatchState{},
			expected:  idtools.Identity{UID: 1, GID: 5555},
		},
		{
			builder:   &Builder{options: &types.ImageBuildOptions{Platform: "linux"}},
			name:      "GroupWithMap",
			chownStr:  "0:unicorn",
			idMapping: remapped,
			state:     &dispatchState{},
			expected:  idtools.Identity{UID: 100000, GID: 101002},
		},
		{
			builder:   &Builder{options: &types.ImageBuildOptions{Platform: "linux"}},
			name:      "UserOnlyWithMap",
			chownStr:  "unicorn",
			idMapping: remapped,
			state:     &dispatchState{},
			expected:  idtools.Identity{UID: 101001, GID: 101002},
		},
	} {
		t.Run(testcase.name, func(t *testing.T) {
			idPair, err := parseChownFlag(context.TODO(), testcase.builder, testcase.state, testcase.chownStr, contextDir, testcase.idMapping)
			assert.NilError(t, err, "Failed to parse chown flag: %q", testcase.chownStr)
			assert.Check(t, is.DeepEqual(testcase.expected, idPair), "chown flag mapping failure")
		})
	}

	// error tests
	for _, testcase := range []struct {
		builder   *Builder
		name      string
		chownStr  string
		idMapping idtools.IdentityMapping
		state     *dispatchState
		descr     string
	}{
		{
			builder:   &Builder{options: &types.ImageBuildOptions{Platform: "linux"}},
			name:      "BadChownFlagFormat",
			chownStr:  "bob:1:555",
			idMapping: unmapped,
			state:     &dispatchState{},
			descr:     "invalid chown string format: bob:1:555",
		},
		{
			builder:   &Builder{options: &types.ImageBuildOptions{Platform: "linux"}},
			name:      "UserNoExist",
			chownStr:  "bob",
			idMapping: unmapped,
			state:     &dispatchState{},
			descr:     "can't find uid for user bob: no such user: bob",
		},
		{
			builder:   &Builder{options: &types.ImageBuildOptions{Platform: "linux"}},
			name:      "GroupNoExist",
			chownStr:  "root:bob",
			idMapping: unmapped,
			state:     &dispatchState{},
			descr:     "can't find gid for group bob: no such group: bob",
		},
	} {
		t.Run(testcase.name, func(t *testing.T) {
			_, err := parseChownFlag(context.TODO(), testcase.builder, testcase.state, testcase.chownStr, contextDir, testcase.idMapping)
			assert.Check(t, is.Error(err, testcase.descr), "Expected error string doesn't match")
		})
	}
}
