package packagejson_test

import (
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson"
	ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
)

func TestParse(t *testing.T) {
	vectors := []struct {
		name      string
		inputFile string
		want      packagejson.Package
		wantErr   string
	}{
		{
			name:      "happy path",
			inputFile: "testdata/package.json",

			// docker run --name composer --rm -it node:12-alpine sh
			// npm init --force
			// npm install --save promise jquery
			// npm ls | grep -E -o "\S+@\S+" | awk -F@ 'NR>0 {printf("{\""$1"\", \""$2"\"},\n")}'
			want: packagejson.Package{
				Package: ftypes.Package{
					ID:       "bootstrap@5.0.2",
					Name:     "bootstrap",
					Version:  "5.0.2",
					Licenses: []string{"MIT"},
				},
				Dependencies: map[string]string{
					"js-tokens": "^4.0.0",
				},
				OptionalDependencies: map[string]string{
					"colors": "^1.4.0",
				},
				DevDependencies: map[string]string{
					"@babel/cli":  "^7.14.5",
					"@babel/core": "^7.14.6",
				},
				Workspaces: []string{
					"packages/*",
					"backend",
				},
			},
		},
		{
			name:      "happy path - legacy license",
			inputFile: "testdata/legacy_package.json",
			want: packagejson.Package{
				Package: ftypes.Package{
					ID:       "angular@4.1.2",
					Name:     "angular",
					Version:  "4.1.2",
					Licenses: []string{"ISC"},
				},
				Dependencies: make(map[string]string),
				DevDependencies: map[string]string{
					"@babel/cli":  "^7.14.5",
					"@babel/core": "^7.14.6",
				},
			},
		},
		{
			name:      "happy path - legacy array of objects license",
			inputFile: "testdata/legacy_array_package.json",
			want: packagejson.Package{
				Package: ftypes.Package{
					ID:       "tv4@1.3.0",
					Name:     "tv4",
					Version:  "1.3.0",
					Licenses: []string{"Public Domain", "MIT"},
				},
				Dependencies: make(map[string]string),
			},
		},
		{
			name:      "empty array license",
			inputFile: "testdata/empty_array_license_package.json",
			want: packagejson.Package{
				Package: ftypes.Package{
					ID:      "empty-array-license@1.0.0",
					Name:    "empty-array-license",
					Version: "1.0.0",
				},
				Dependencies: make(map[string]string),
			},
		},
		{
			name:      "mixed array of objects with empty type",
			inputFile: "testdata/mixed_array_license_package.json",
			want: packagejson.Package{
				Package: ftypes.Package{
					ID:       "mixed-array-license@1.0.0",
					Name:     "mixed-array-license",
					Version:  "1.0.0",
					Licenses: []string{"MIT"},
				},
				Dependencies: make(map[string]string),
			},
		},
		{
			name:      "invalid license value (number)",
			inputFile: "testdata/invalid_license_package.json",
			want: packagejson.Package{
				Package: ftypes.Package{
					ID:      "weird-license@1.0.0",
					Name:    "weird-license",
					Version: "1.0.0",
				},
				Dependencies: make(map[string]string),
			},
		},
		{
			name:      "null license value",
			inputFile: "testdata/null_license_package.json",
			want: packagejson.Package{
				Package: ftypes.Package{
					ID:      "null-license@1.0.0",
					Name:    "null-license",
					Version: "1.0.0",
				},
				Dependencies: make(map[string]string),
			},
		},
		{
			name:      "unknown license object",
			inputFile: "testdata/unknown_license_object_package.json",
			want: packagejson.Package{
				Package: ftypes.Package{
					ID:      "unknown-license-object@1.0.0",
					Name:    "unknown-license-object",
					Version: "1.0.0",
				},
				Dependencies: make(map[string]string),
			},
		},
		{
			name:      "happy path - version doesn't exist",
			inputFile: "testdata/without_version_package.json",
			want: packagejson.Package{
				Package: ftypes.Package{
					ID:   "",
					Name: "angular",
				},
			},
		},
		{
			name:      "happy path - workspace as struct",
			inputFile: "testdata/workspace_as_map_package.json",
			want: packagejson.Package{
				Package: ftypes.Package{
					ID:      "example@1.0.0",
					Name:    "example",
					Version: "1.0.0",
				},
				Workspaces: []string{
					"packages/*",
				},
			},
		},
		{
			name:      "invalid package name",
			inputFile: "testdata/invalid_name.json",
			wantErr:   "Name can only contain URL-friendly characters",
		},
		{
			name:      "sad path",
			inputFile: "testdata/invalid_package.json",

			// docker run --name composer --rm -it node:12-alpine sh
			// npm init --force
			// npm install --save promise jquery
			// npm ls | grep -E -o "\S+@\S+" | awk -F@ 'NR>0 {printf("{\""$1"\", \""$2"\"},\n")}'
			wantErr: "JSON decode error",
		},
		{
			name:      "without name and version",
			inputFile: "testdata/without_name_and_version_package.json",
			want: packagejson.Package{
				Package: ftypes.Package{
					Licenses: []string{"MIT"},
				},
			},
		},
	}

	for _, v := range vectors {
		t.Run(v.name, func(t *testing.T) {
			f, err := os.Open(v.inputFile)
			require.NoError(t, err)
			defer f.Close()

			got, err := packagejson.NewParser().Parse(f)
			if v.wantErr != "" {
				assert.ErrorContains(t, err, v.wantErr)
				return
			}

			require.NoError(t, err)
			assert.Equal(t, v.want, got)
		})
	}
}

func TestIsValidName(t *testing.T) {
	tests := []struct {
		name string
		want bool
	}{
		{
			name: "",
			want: true,
		},
		{
			name: "test_package",
			want: true,
		},
		{
			name: "test.package",
			want: true,
		},
		{
			name: "test-package",
			want: true,
		},
		{
			name: "@test/package",
			want: true,
		},
		{
			name: "test@package",
			want: false,
		},
		{
			name: "test?package",
			want: false,
		},
		{
			name: "test/package",
			want: false,
		},
		{
			name: "package/",
			want: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			valid := packagejson.IsValidName(tt.name)
			require.Equal(t, tt.want, valid)
		})
	}
}
