/*
Copyright 2021 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 prow

import (
	"reflect"
	"strings"
	"testing"

	"github.com/GoogleCloudPlatform/testgrid/config/yamlcfg"
	"github.com/GoogleCloudPlatform/testgrid/pb/config"

	prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
	prowConfig "sigs.k8s.io/prow/pkg/config"
	"sigs.k8s.io/prow/pkg/pjutil"
)

const ProwDefaultGCSPath = "pathPrefix/"
const ProwJobName = "TestJob"
const ProwJobSourcePath = "jobs/org/repo/testjob.yaml"
const ProwJobURLPrefix = "https://go.k8s.io/prowjobs/"
const ExampleRepository = "test/repo"
const ProwJobDefaultDescription = "prowjob_name: " + ProwJobName

func Test_applySingleProwjobAnnotations(t *testing.T) {
	tests := []*struct {
		name              string
		initialConfig     config.Configuration
		updateDescription bool
		prowJobURLPrefix  string
		prowJobType       prowapi.ProwJobType
		annotations       map[string]string
		expectedConfig    config.Configuration
		expectError       bool
	}{
		{
			name:           "Presubmit with no Annotations: no change",
			prowJobType:    prowapi.PresubmitJob,
			expectedConfig: config.Configuration{},
		},
		{
			name:        "Non-presubmit with no Annotations: test group only",
			prowJobType: prowapi.PostsubmitJob,
			expectedConfig: config.Configuration{
				TestGroups: []*config.TestGroup{
					{
						Name:      ProwJobName,
						GcsPrefix: ProwDefaultGCSPath + "logs/" + ProwJobName,
					},
				},
			},
		},
		{
			name:        "Presubmit forcing test group creation: sets hardcoded default",
			prowJobType: prowapi.PresubmitJob,
			annotations: map[string]string{
				"testgrid-create-test-group": "true",
			},
			expectedConfig: config.Configuration{
				TestGroups: []*config.TestGroup{
					{
						Name:             ProwJobName,
						GcsPrefix:        ProwDefaultGCSPath + "pr-logs/directory/" + ProwJobName,
						NumColumnsRecent: 20,
					},
				},
			},
		},
		{
			name:        "Set columns below hardcoded minimum: annotation overwrites",
			prowJobType: prowapi.PresubmitJob,
			annotations: map[string]string{
				"testgrid-create-test-group":  "true",
				"testgrid-num-columns-recent": "10",
			},
			expectedConfig: config.Configuration{
				TestGroups: []*config.TestGroup{
					{
						Name:             ProwJobName,
						GcsPrefix:        ProwDefaultGCSPath + "pr-logs/directory/" + ProwJobName,
						NumColumnsRecent: 10,
					},
				},
			},
		},
		{
			name:        "Non-presubmit excluding test group",
			prowJobType: prowapi.PostsubmitJob,
			annotations: map[string]string{
				"testgrid-create-test-group": "false",
			},
			expectedConfig: config.Configuration{},
		},
		{
			name: "Force-add job to existing test group: fails",
			initialConfig: config.Configuration{
				TestGroups: []*config.TestGroup{
					{Name: ProwJobName},
				},
			},
			prowJobType: prowapi.PostsubmitJob,
			annotations: map[string]string{
				"testgrid-create-test-group": "true",
			},
			expectError: true,
		},
		{
			name: "Add job to existing dashboard",
			initialConfig: config.Configuration{
				Dashboards: []*config.Dashboard{
					{Name: "Wash"},
				},
			},
			prowJobType: prowapi.PostsubmitJob,
			annotations: map[string]string{
				"testgrid-dashboards": "Wash",
			},
			expectedConfig: config.Configuration{
				TestGroups: []*config.TestGroup{
					{
						Name:      ProwJobName,
						GcsPrefix: ProwDefaultGCSPath + "logs/" + ProwJobName,
					},
				},
				Dashboards: []*config.Dashboard{
					{
						Name: "Wash",
						DashboardTab: []*config.DashboardTab{
							{
								Name:          ProwJobName,
								Description:   ProwJobDefaultDescription,
								TestGroupName: ProwJobName,
								CodeSearchUrlTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/compare/<start-custom-0>...<end-custom-0>",
								},
								OpenBugTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/issues/",
								},
							},
						},
					},
				},
			},
		},
		{
			name:        "Add job to new dashboard: fails",
			prowJobType: prowapi.PostsubmitJob,
			annotations: map[string]string{
				"testgrid-dashboards": "Black",
			},
			expectError: true,
		},
		{
			name: "Add email to multiple dashboards: Two tabs, one email",
			initialConfig: config.Configuration{
				Dashboards: []*config.Dashboard{
					{Name: "Dart"},
					{Name: "Peg"},
				},
			},
			prowJobType: prowapi.PostsubmitJob,
			annotations: map[string]string{
				"testgrid-dashboards":  "Dart, Peg",
				"testgrid-alert-email": "test@example.com",
			},
			expectedConfig: config.Configuration{
				TestGroups: []*config.TestGroup{
					{
						Name:      ProwJobName,
						GcsPrefix: ProwDefaultGCSPath + "logs/" + ProwJobName,
					},
				},
				Dashboards: []*config.Dashboard{
					{
						Name: "Dart",
						DashboardTab: []*config.DashboardTab{
							{
								Name:          ProwJobName,
								Description:   ProwJobDefaultDescription,
								TestGroupName: ProwJobName,
								CodeSearchUrlTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/compare/<start-custom-0>...<end-custom-0>",
								},
								OpenBugTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/issues/",
								},
								AlertOptions: &config.DashboardTabAlertOptions{
									AlertMailToAddresses: "test@example.com",
								},
							},
						},
					},
					{
						Name: "Peg",
						DashboardTab: []*config.DashboardTab{
							{
								Name:          ProwJobName,
								Description:   ProwJobDefaultDescription,
								TestGroupName: ProwJobName,
								CodeSearchUrlTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/compare/<start-custom-0>...<end-custom-0>",
								},
								OpenBugTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/issues/",
								},
							},
						},
					},
				},
			},
		},
		{
			name: "Add job that already exists: keeps test group, makes duplicate tab",
			initialConfig: config.Configuration{
				TestGroups: []*config.TestGroup{
					{
						Name:      ProwJobName,
						GcsPrefix: "CustomFoo",
					},
				},
				Dashboards: []*config.Dashboard{
					{
						Name: "Surf",
						DashboardTab: []*config.DashboardTab{
							{
								Name:          ProwJobName,
								Description:   "RegularHumanDescription",
								TestGroupName: ProwJobName,
							},
						},
					},
				},
			},
			prowJobType: prowapi.PostsubmitJob,
			annotations: map[string]string{
				"testgrid-dashboards": "Surf",
			},
			expectedConfig: config.Configuration{
				TestGroups: []*config.TestGroup{
					{
						Name:      ProwJobName,
						GcsPrefix: "CustomFoo",
					},
				},
				Dashboards: []*config.Dashboard{
					{
						Name: "Surf",
						DashboardTab: []*config.DashboardTab{
							{
								Name:          ProwJobName,
								Description:   "RegularHumanDescription",
								TestGroupName: ProwJobName,
							},
							{
								Name:          ProwJobName,
								Description:   ProwJobDefaultDescription,
								TestGroupName: ProwJobName,
								CodeSearchUrlTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/compare/<start-custom-0>...<end-custom-0>",
								},
								OpenBugTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/issues/",
								},
							},
						},
					},
				},
			},
		},
		{
			name: "Add job to existing dashboard with --prowjob-url-prefix configured",
			initialConfig: config.Configuration{
				Dashboards: []*config.Dashboard{
					{Name: "Wash"},
				},
			},
			prowJobURLPrefix: ProwJobURLPrefix,
			prowJobType:      prowapi.PostsubmitJob,
			annotations: map[string]string{
				"testgrid-dashboards": "Wash",
			},
			expectedConfig: config.Configuration{
				TestGroups: []*config.TestGroup{
					{
						Name:      ProwJobName,
						GcsPrefix: ProwDefaultGCSPath + "logs/" + ProwJobName,
					},
				},
				Dashboards: []*config.Dashboard{
					{
						Name: "Wash",
						DashboardTab: []*config.DashboardTab{
							{
								Name:          ProwJobName,
								Description:   ProwJobDefaultDescription + "\nprowjob_config_url: " + ProwJobURLPrefix + ProwJobSourcePath,
								TestGroupName: ProwJobName,
								CodeSearchUrlTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/compare/<start-custom-0>...<end-custom-0>",
								},
								OpenBugTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/issues/",
								},
							},
						},
					},
				},
			},
		},
		{
			name: "Full Annotations with --update-description enabled",
			initialConfig: config.Configuration{
				Dashboards: []*config.Dashboard{
					{Name: "Ouija"},
				},
			},
			updateDescription: true,
			prowJobType:       prowapi.PostsubmitJob,
			annotations: map[string]string{
				"testgrid-dashboards":                "Ouija",
				"testgrid-tab-name":                  "Planchette",
				"testgrid-alert-email":               "ghost@example.com",
				"description":                        "spooky scary",
				"testgrid-num-columns-recent":        "13",
				"testgrid-num-failures-to-alert":     "4",
				"testgrid-alert-stale-results-hours": "24",
				"testgrid-days-of-results":           "30",
				"testgrid-in-cell-metric":            "haunted-house",
				"testgrid-disable-prowjob-analysis":  "true",
				"testgrid-base-options":              "exclude-filter-by-regex=^kubetest.Test$",
				"testgrid-broken-column-threshold":   "0.5",
			},
			expectedConfig: config.Configuration{
				TestGroups: []*config.TestGroup{
					{
						Name:                   ProwJobName,
						GcsPrefix:              ProwDefaultGCSPath + "logs/" + ProwJobName,
						NumColumnsRecent:       13,
						DaysOfResults:          30,
						ShortTextMetric:        "haunted-house",
						DisableProwjobAnalysis: true,
						NumFailuresToAlert:     4,
					},
				},
				Dashboards: []*config.Dashboard{
					{
						Name: "Ouija",
						DashboardTab: []*config.DashboardTab{
							{
								Name:          "Planchette",
								Description:   ProwJobDefaultDescription + "\nprowjob_description: spooky scary",
								TestGroupName: ProwJobName,
								AlertOptions: &config.DashboardTabAlertOptions{
									AlertMailToAddresses:   "ghost@example.com",
									NumFailuresToAlert:     4,
									AlertStaleResultsHours: 24,
								},
								CodeSearchUrlTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/compare/<start-custom-0>...<end-custom-0>",
								},
								OpenBugTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/issues/",
								},
								BaseOptions:           "exclude-filter-by-regex=^kubetest.Test$",
								BrokenColumnThreshold: 0.5,
							},
						},
					},
				},
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			pac := ProwAwareConfigurator{
				ProwConfig:            fakeProwConfig(),
				DefaultTestgridConfig: nil,
				ProwJobURLPrefix:      test.prowJobURLPrefix,
				UpdateDescription:     test.updateDescription,
			}
			jobBase := prowConfig.JobBase{
				Name:        ProwJobName,
				Annotations: test.annotations,
				SourcePath:  ProwJobSourcePath,
			}

			pj := genProwJob(jobBase, test.prowJobType, ExampleRepository)

			err := pac.ApplySingleProwjobAnnotations(&test.initialConfig, jobBase, pj)

			if test.expectError {
				if err == nil {
					t.Error("Expected an error, but got none")
				}
			} else {
				if err != nil {
					t.Errorf("Unexpected error: %v", err)
				}

				if !reflect.DeepEqual(&test.initialConfig, &test.expectedConfig) {
					t.Errorf("Configurations did not match; got %s, expected %s", test.initialConfig.String(), test.expectedConfig.String())
				}
			}
		})
	}
}

func Test_applySingleProwjobAnnotation_WithDefaults(t *testing.T) {

	defaultConfig := &yamlcfg.DefaultConfiguration{
		DefaultTestGroup: &config.TestGroup{
			GcsPrefix:        "originalConfigPrefix", //Default is Overwritten
			DaysOfResults:    5,                      //Default is Kept
			NumColumnsRecent: 10,                     //Sometimes Overwritten; see test
		},
		DefaultDashboardTab: &config.DashboardTab{
			Name:        "DefaultTab",          //Overwritten
			Description: "Default Description", //Overwritten
			ResultsText: "Default Text",        //Kept
			AlertOptions: &config.DashboardTabAlertOptions{
				AlertMailToAddresses: "default_admin@example.com", //Kept; see test
			},
			CodeSearchUrlTemplate: &config.LinkTemplate{ //Overwritten
				Url: "https://example.com/code_search",
			},
			OpenBugTemplate: &config.LinkTemplate{ //Overwritten
				Url: "https://example.com/open_bug",
			},
		},
	}

	tests := []struct {
		name           string
		initialConfig  *config.Configuration
		prowJobType    prowapi.ProwJobType
		annotations    map[string]string
		expectedConfig *config.Configuration
	}{
		{
			name:           "Presubmit with no Annotations: no change",
			prowJobType:    prowapi.PresubmitJob,
			expectedConfig: &config.Configuration{},
		},
		{
			name:        "Non-presubmit with no Annotations: test group with assumed defaults",
			prowJobType: prowapi.PostsubmitJob,
			expectedConfig: &config.Configuration{
				TestGroups: []*config.TestGroup{
					{
						Name:                ProwJobName,
						GcsPrefix:           ProwDefaultGCSPath + "logs/" + ProwJobName,
						DaysOfResults:       5,
						NumColumnsRecent:    10,
						UseKubernetesClient: true,
						IsExternal:          true,
					},
				},
			},
		},
		{
			name:        "Presubmit forcing test group creation: hardcoded default over config default",
			prowJobType: prowapi.PresubmitJob,
			annotations: map[string]string{
				"testgrid-create-test-group": "true",
			},
			expectedConfig: &config.Configuration{
				TestGroups: []*config.TestGroup{
					{
						Name:                ProwJobName,
						GcsPrefix:           ProwDefaultGCSPath + "pr-logs/directory/" + ProwJobName,
						DaysOfResults:       5,
						NumColumnsRecent:    20,
						UseKubernetesClient: true,
						IsExternal:          true,
					},
				},
			},
		},
		{
			name: "Add job to existing dashboard: merge with defaults",
			initialConfig: &config.Configuration{
				Dashboards: []*config.Dashboard{
					{Name: "Wash"},
				},
			},
			prowJobType: prowapi.PostsubmitJob,
			annotations: map[string]string{
				"testgrid-dashboards": "Wash",
			},
			expectedConfig: &config.Configuration{
				TestGroups: []*config.TestGroup{
					{
						Name:                ProwJobName,
						GcsPrefix:           ProwDefaultGCSPath + "logs/" + ProwJobName,
						DaysOfResults:       5,
						NumColumnsRecent:    10,
						UseKubernetesClient: true,
						IsExternal:          true,
					},
				},
				Dashboards: []*config.Dashboard{
					{
						Name: "Wash",
						DashboardTab: []*config.DashboardTab{
							{
								Name:          ProwJobName,
								Description:   ProwJobDefaultDescription,
								TestGroupName: ProwJobName,
								ResultsText:   "Default Text",
								CodeSearchUrlTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/compare/<start-custom-0>...<end-custom-0>",
								},
								OpenBugTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/issues/",
								},
								AlertOptions: &config.DashboardTabAlertOptions{
									AlertMailToAddresses: "default_admin@example.com",
								},
							},
						},
					},
				},
			},
		},
		{
			name: "Add email to multiple dashboards: Two tabs, Different Emails",
			initialConfig: &config.Configuration{
				Dashboards: []*config.Dashboard{
					{Name: "Dart"},
					{Name: "Peg"},
				},
			},
			prowJobType: prowapi.PostsubmitJob,
			annotations: map[string]string{
				"testgrid-dashboards":  "Dart, Peg",
				"testgrid-alert-email": "test@example.com",
			},
			expectedConfig: &config.Configuration{
				TestGroups: []*config.TestGroup{
					{
						Name:                ProwJobName,
						GcsPrefix:           ProwDefaultGCSPath + "logs/" + ProwJobName,
						DaysOfResults:       5,
						NumColumnsRecent:    10,
						UseKubernetesClient: true,
						IsExternal:          true,
					},
				},
				Dashboards: []*config.Dashboard{
					{
						Name: "Dart",
						DashboardTab: []*config.DashboardTab{
							{
								Name:          ProwJobName,
								Description:   ProwJobDefaultDescription,
								TestGroupName: ProwJobName,
								ResultsText:   "Default Text",
								CodeSearchUrlTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/compare/<start-custom-0>...<end-custom-0>",
								},
								OpenBugTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/issues/",
								},
								AlertOptions: &config.DashboardTabAlertOptions{
									AlertMailToAddresses: "test@example.com",
								},
							},
						},
					},
					{
						Name: "Peg",
						DashboardTab: []*config.DashboardTab{
							{
								Name:          ProwJobName,
								Description:   ProwJobDefaultDescription,
								TestGroupName: ProwJobName,
								ResultsText:   "Default Text",
								CodeSearchUrlTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/compare/<start-custom-0>...<end-custom-0>",
								},
								OpenBugTemplate: &config.LinkTemplate{
									Url: "https://github.com/test/repo/issues/",
								},
								AlertOptions: &config.DashboardTabAlertOptions{
									AlertMailToAddresses: "default_admin@example.com",
								},
							},
						},
					},
				},
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {

			if test.initialConfig == nil {
				test.initialConfig = &config.Configuration{}
			}

			pac := ProwAwareConfigurator{
				ProwConfig:            fakeProwConfig(),
				DefaultTestgridConfig: defaultConfig,
			}

			jobBase := prowConfig.JobBase{
				Name:        ProwJobName,
				Annotations: test.annotations,
			}

			pj := genProwJob(jobBase, test.prowJobType, ExampleRepository)

			err := pac.ApplySingleProwjobAnnotations(test.initialConfig, jobBase, pj)

			if test.expectedConfig == nil {
				if err == nil {
					t.Error("Expected an error, but got none")
				}
			} else {
				if err != nil {
					t.Errorf("Unexpected error: %v", err)
				}

				if !reflect.DeepEqual(test.initialConfig, test.expectedConfig) {
					t.Errorf("Configurations did not match; got %s, expected %s", test.initialConfig.String(), test.expectedConfig.String())
				}
			}
		})
	}

}

func Test_applySingleProwjobAnnotations_OpenTestTemplate(t *testing.T) {
	tests := []*struct {
		name                     string
		jobURLPrefixConfig       map[string]string
		defaultConfig            *yamlcfg.DefaultConfiguration
		expectedOpenTestTemplate *config.LinkTemplate
	}{
		{
			name: "job url prefix without specific suffix",
			jobURLPrefixConfig: map[string]string{
				"*": "https://config.go.k8s.io/",
			},
			expectedOpenTestTemplate: &config.LinkTemplate{
				Url: "https://config.go.k8s.io/gs/<gcs_prefix>/<changelist>",
			},
		},
		{
			name: "job url prefix ends in /view, kept",
			jobURLPrefixConfig: map[string]string{
				"*": "https://config.go.k8s.io/view",
			},
			expectedOpenTestTemplate: &config.LinkTemplate{
				Url: "https://config.go.k8s.io/view/gs/<gcs_prefix>/<changelist>",
			},
		},
		{
			name: "job url prefix for org is preferred over *",
			jobURLPrefixConfig: map[string]string{
				"*":    "https://some.other.url",
				"test": "https://config.go.k8s.io/",
			},
			expectedOpenTestTemplate: &config.LinkTemplate{
				Url: "https://config.go.k8s.io/gs/<gcs_prefix>/<changelist>",
			},
		},
		{
			name: "job url prefix for org/repo is preferred over org and *",
			jobURLPrefixConfig: map[string]string{
				"*":         "https://some.other.url",
				"test":      "https://even.another.url",
				"test/repo": "https://config.go.k8s.io/",
			},
			expectedOpenTestTemplate: &config.LinkTemplate{
				Url: "https://config.go.k8s.io/gs/<gcs_prefix>/<changelist>",
			},
		},
		{
			name: "default config is overwritten",
			jobURLPrefixConfig: map[string]string{
				"*": "https://config.go.k8s.io/",
			},
			defaultConfig: &yamlcfg.DefaultConfiguration{
				DefaultTestGroup: &config.TestGroup{},
				DefaultDashboardTab: &config.DashboardTab{
					OpenTestTemplate: &config.LinkTemplate{ //Overwritten
						Url: "https://example.com/open_test",
					},
				},
			},
			expectedOpenTestTemplate: &config.LinkTemplate{
				Url: "https://config.go.k8s.io/gs/<gcs_prefix>/<changelist>",
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			initialConfig := &config.Configuration{
				Dashboards: []*config.Dashboard{
					{Name: "Pizza"},
				},
			}
			annotations := map[string]string{
				"testgrid-dashboards": "Pizza",
			}

			prowCfg := fakeProwConfig()
			prowCfg.Plank.JobURLPrefixConfig = test.jobURLPrefixConfig
			pac := ProwAwareConfigurator{
				ProwConfig: prowCfg,
			}
			if test.defaultConfig != nil {
				pac.DefaultTestgridConfig = test.defaultConfig
			}

			jobBase := prowConfig.JobBase{
				Name:        ProwJobName,
				Annotations: annotations,
				SourcePath:  ProwJobSourcePath,
			}

			pj := genProwJob(jobBase, prowapi.PresubmitJob, ExampleRepository)

			err := pac.ApplySingleProwjobAnnotations(initialConfig, jobBase, pj)

			if err != nil {
				t.Errorf("Unexpected error: %v", err)
			}

			actual := initialConfig.Dashboards[0].DashboardTab[0].OpenTestTemplate
			if !reflect.DeepEqual(actual, test.expectedOpenTestTemplate) {
				t.Errorf("Configurations did not match; got %s, expected %s", actual.String(), test.expectedOpenTestTemplate.String())
			}
		})
	}
}

func TestSortPresubmitRepoOrder(t *testing.T) {
	tests := []struct {
		name          string
		presubmits    map[string][]prowConfig.Presubmit
		expectedRepos []string
	}{
		{
			name:          "empty list of presubmits",
			presubmits:    map[string][]prowConfig.Presubmit{},
			expectedRepos: []string{},
		},
		{
			name: "unordered list of presubmits",
			presubmits: map[string][]prowConfig.Presubmit{
				"istio/proxy": {
					prowConfig.Presubmit{
						JobBase: prowConfig.JobBase{
							Name: "lint_release-1.5",
						},
					},
					prowConfig.Presubmit{
						JobBase: prowConfig.JobBase{
							Name: "gen_check_master",
						},
					},
					prowConfig.Presubmit{
						JobBase: prowConfig.JobBase{
							Name: "lint_master",
						},
					},
				},
				"kubernetes/test-infra": {
					prowConfig.Presubmit{
						JobBase: prowConfig.JobBase{
							Name: "pull-test-bazel",
						},
					},
				},
				"helm/helm": {
					prowConfig.Presubmit{
						JobBase: prowConfig.JobBase{
							Name: "pull-test-go",
						},
					},
				},
			},
			expectedRepos: []string{"helm/helm", "istio/proxy", "kubernetes/test-infra"},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			actualRepos := sortPresubmits(test.presubmits)

			if !reflect.DeepEqual(test.expectedRepos, actualRepos) {
				t.Fatalf("Presubmit repos do not match; actual: %v\n expected %v\n", test.expectedRepos, actualRepos)
			}
		})
	}
}

func TestSortPostsubmitRepoOrder(t *testing.T) {
	tests := []struct {
		name          string
		postsubmits   map[string][]prowConfig.Postsubmit
		expectedRepos []string
	}{
		{
			name:          "empty list of postsubmits",
			postsubmits:   map[string][]prowConfig.Postsubmit{},
			expectedRepos: []string{},
		},
		{
			name: "unordered list of postsubmits",
			postsubmits: map[string][]prowConfig.Postsubmit{
				"GoogleCloudPlatform/oss-test-infra": {
					prowConfig.Postsubmit{
						JobBase: prowConfig.JobBase{
							Name: "pull-test-infra-go-test",
						},
					},
				},
				"kubernetes/kubernetes": {
					prowConfig.Postsubmit{
						JobBase: prowConfig.JobBase{
							Name: "ci-kubernetes-e2e",
						},
					},
					prowConfig.Postsubmit{
						JobBase: prowConfig.JobBase{
							Name: "ci-kubernetes-unit",
						},
					},
				},
				"containerd/cri": {
					prowConfig.Postsubmit{
						JobBase: prowConfig.JobBase{
							Name: "pull-cri-containerd-build",
						},
					},
				},
			},
			expectedRepos: []string{"GoogleCloudPlatform/oss-test-infra", "containerd/cri", "kubernetes/kubernetes"},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			actualRepos := sortPostsubmits(test.postsubmits)

			if !reflect.DeepEqual(test.expectedRepos, actualRepos) {
				t.Fatalf("Postsubmit repos do not match; actual: %v\n expected %v\n", test.expectedRepos, actualRepos)
			}
		})
	}
}

func TestSortPeriodicJobOrder(t *testing.T) {
	tests := []struct {
		name              string
		periodics         []prowConfig.Periodic
		expectedPeriodics []prowConfig.Periodic
	}{
		{
			name:              "empty list of periodics",
			periodics:         []prowConfig.Periodic{},
			expectedPeriodics: []prowConfig.Periodic{},
		},
		{
			name: "unordered list of periodics",
			periodics: []prowConfig.Periodic{
				{
					JobBase: prowConfig.JobBase{
						Name: "ESPv2-continuous-build",
					},
				},
				{
					JobBase: prowConfig.JobBase{
						Name: "everlast-bump",
					},
				},
				{
					JobBase: prowConfig.JobBase{
						Name: "ci-oss-test-infra-autobump-prow",
					},
				},
			},
			expectedPeriodics: []prowConfig.Periodic{
				{
					JobBase: prowConfig.JobBase{
						Name: "ESPv2-continuous-build",
					},
				},
				{
					JobBase: prowConfig.JobBase{
						Name: "ci-oss-test-infra-autobump-prow",
					},
				},
				{
					JobBase: prowConfig.JobBase{
						Name: "everlast-bump",
					},
				},
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			sortPeriodics(test.periodics)

			if !reflect.DeepEqual(test.expectedPeriodics, test.periodics) {
				t.Fatalf("Periodic jobs do not match; actual: %v\n expected %v\n", test.expectedPeriodics, test.periodics)
			}
		})
	}
}

func TestSortPresubmitJobOrder(t *testing.T) {
	tests := []struct {
		name               string
		presubmits         map[string][]prowConfig.Presubmit
		expectedPresubmits map[string][]prowConfig.Presubmit
	}{
		{
			name:               "empty list of presubmits",
			presubmits:         map[string][]prowConfig.Presubmit{},
			expectedPresubmits: map[string][]prowConfig.Presubmit{},
		},
		{
			name: "unordered list of presubmits",
			presubmits: map[string][]prowConfig.Presubmit{
				"istio/proxy": {
					prowConfig.Presubmit{
						JobBase: prowConfig.JobBase{
							Name: "lint_release-1.5",
						},
					},
					prowConfig.Presubmit{
						JobBase: prowConfig.JobBase{
							Name: "gen_check_master",
						},
					},
					prowConfig.Presubmit{
						JobBase: prowConfig.JobBase{
							Name: "lint_master",
						},
					},
				},
				"kubernetes/test-infra": {
					prowConfig.Presubmit{
						JobBase: prowConfig.JobBase{
							Name: "pull-test-go",
						},
					},
					prowConfig.Presubmit{
						JobBase: prowConfig.JobBase{
							Name: "pull-test-bazel",
						},
					},
				},
			},
			expectedPresubmits: map[string][]prowConfig.Presubmit{
				"istio/proxy": {
					prowConfig.Presubmit{
						JobBase: prowConfig.JobBase{
							Name: "gen_check_master",
						},
					},
					prowConfig.Presubmit{
						JobBase: prowConfig.JobBase{
							Name: "lint_master",
						},
					},
					prowConfig.Presubmit{
						JobBase: prowConfig.JobBase{
							Name: "lint_release-1.5",
						},
					},
				},
				"kubernetes/test-infra": {
					prowConfig.Presubmit{
						JobBase: prowConfig.JobBase{
							Name: "pull-test-bazel",
						},
					},
					prowConfig.Presubmit{
						JobBase: prowConfig.JobBase{
							Name: "pull-test-go",
						},
					},
				},
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			sortPresubmits(test.presubmits)

			for orgrepo := range test.expectedPresubmits {
				if !reflect.DeepEqual(test.expectedPresubmits[orgrepo], test.presubmits[orgrepo]) {
					t.Fatalf("Presubmit jobs do not match for repo: %s; actual: %v\n expected %v\n", orgrepo, test.expectedPresubmits[orgrepo], test.presubmits[orgrepo])
				}
			}
		})
	}
}

func TestSortPostsubmitJobOrder(t *testing.T) {
	tests := []struct {
		name                string
		postsubmits         map[string][]prowConfig.Postsubmit
		expectedPostsubmits map[string][]prowConfig.Postsubmit
	}{
		{
			name:                "empty list of postsubmits",
			postsubmits:         map[string][]prowConfig.Postsubmit{},
			expectedPostsubmits: map[string][]prowConfig.Postsubmit{},
		},
		{
			name: "unordered list of postsubmits",
			postsubmits: map[string][]prowConfig.Postsubmit{
				"GoogleCloudPlatform/oss-test-infra": {
					prowConfig.Postsubmit{
						JobBase: prowConfig.JobBase{
							Name: "pull-test-infra-go-test",
						},
					},
					prowConfig.Postsubmit{
						JobBase: prowConfig.JobBase{
							Name: "pull-cri-containerd-build",
						},
					},
				},
				"kubernetes/kubernetes": {
					prowConfig.Postsubmit{
						JobBase: prowConfig.JobBase{
							Name: "ci-kubernetes-e2e",
						},
					},
					prowConfig.Postsubmit{
						JobBase: prowConfig.JobBase{
							Name: "ci-kubernetes-unit",
						},
					},
				},
			},
			expectedPostsubmits: map[string][]prowConfig.Postsubmit{
				"GoogleCloudPlatform/oss-test-infra": {
					prowConfig.Postsubmit{
						JobBase: prowConfig.JobBase{
							Name: "pull-cri-containerd-build",
						},
					},
					prowConfig.Postsubmit{
						JobBase: prowConfig.JobBase{
							Name: "pull-test-infra-go-test",
						},
					},
				},
				"kubernetes/kubernetes": {
					prowConfig.Postsubmit{
						JobBase: prowConfig.JobBase{
							Name: "ci-kubernetes-e2e",
						},
					},
					prowConfig.Postsubmit{
						JobBase: prowConfig.JobBase{
							Name: "ci-kubernetes-unit",
						},
					},
				},
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			sortPostsubmits(test.postsubmits)

			for orgrepo := range test.expectedPostsubmits {
				if !reflect.DeepEqual(test.expectedPostsubmits[orgrepo], test.postsubmits[orgrepo]) {
					t.Fatalf("Postsubmit jobs do not match for repo: %s; actual: %v\n expected %v\n", orgrepo, test.expectedPostsubmits[orgrepo], test.postsubmits[orgrepo])
				}
			}
		})
	}
}

func fakeProwConfig() *prowConfig.Config {
	return &prowConfig.Config{
		ProwConfig: prowConfig.ProwConfig{
			Plank: prowConfig.Plank{
				DefaultDecorationConfigs: []*prowConfig.DefaultDecorationConfigEntry{
					{
						Config: &prowapi.DecorationConfig{
							GCSConfiguration: &prowapi.GCSConfiguration{
								PathPrefix: ProwDefaultGCSPath,
							},
						},
					},
				},
			},
		},
	}
}

func genProwJob(jobBase prowConfig.JobBase, jobType prowapi.ProwJobType, orgrepo string) prowapi.ProwJob {
	if jobType == prowapi.PeriodicJob {
		pjSpec := pjutil.PeriodicSpec(prowConfig.Periodic{JobBase: jobBase})
		return pjutil.NewProwJob(pjSpec, nil, nil)
	}

	items := strings.Split(orgrepo, "/")
	if jobType == prowapi.PostsubmitJob {
		pjSpec := pjutil.PostsubmitSpec(
			prowConfig.Postsubmit{JobBase: jobBase},
			prowapi.Refs{
				Org:  items[0],
				Repo: items[1],
			},
		)
		return pjutil.NewProwJob(pjSpec, nil, nil)
	}

	pjSpec := pjutil.PresubmitSpec(
		prowConfig.Presubmit{JobBase: jobBase},
		prowapi.Refs{
			Org:  items[0],
			Repo: items[1],
		},
	)
	return pjutil.NewProwJob(pjSpec, nil, nil)
}
