github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/cd-service/pkg/mapper/environments_config_test.go (about)

     1  /*This file is part of kuberpult.
     2  
     3  Kuberpult is free software: you can redistribute it and/or modify
     4  it under the terms of the Expat(MIT) License as published by
     5  the Free Software Foundation.
     6  
     7  Kuberpult is distributed in the hope that it will be useful,
     8  but WITHOUT ANY WARRANTY; without even the implied warranty of
     9  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    10  MIT License for more details.
    11  
    12  You should have received a copy of the MIT License
    13  along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>.
    14  
    15  Copyright 2023 freiheit.com*/
    16  
    17  package mapper
    18  
    19  import (
    20  	api "github.com/freiheit-com/kuberpult/pkg/api/v1"
    21  	"github.com/google/go-cmp/cmp/cmpopts"
    22  
    23  	"testing"
    24  
    25  	"github.com/freiheit-com/kuberpult/services/cd-service/pkg/config"
    26  	"github.com/google/go-cmp/cmp"
    27  )
    28  
    29  func makeUpstreamLatest() *api.EnvironmentConfig_Upstream {
    30  	f := true
    31  	return &api.EnvironmentConfig_Upstream{
    32  		Latest: &f,
    33  	}
    34  }
    35  
    36  func makeUpstreamEnvironment(env string) *api.EnvironmentConfig_Upstream {
    37  	return &api.EnvironmentConfig_Upstream{
    38  		Environment: &env,
    39  	}
    40  }
    41  
    42  var nameStagingDe = "staging-de"
    43  var nameOtherDe = "other-de"
    44  var nameDevDe = "dev-de"
    45  var nameCanaryDe = "canary-de"
    46  var nameProdDe = "prod-de"
    47  var nameWhoKnowsDe = "whoknows-de"
    48  var nameTestDe = "test-de"
    49  
    50  var nameStagingFr = "staging-fr"
    51  var nameDevFr = "dev-fr"
    52  var nameProdFr = "prod-fr"
    53  var nameWhoKnowsFr = "whoknows-fr"
    54  var nameTestFr = "test-fr"
    55  
    56  var nameStagingUS = "staging-us"
    57  
    58  var nameDevGlobal = "dev-global"
    59  var nameTestGlobal = "test-global"
    60  
    61  var nameStaging = "staging"
    62  var nameDev = "dev"
    63  var nameProd = "prod"
    64  var nameWhoKnows = "whoknows"
    65  var nameTest = "test"
    66  var nameCanary = "canary"
    67  
    68  func makeEnv(envName string, groupName string, upstream *api.EnvironmentConfig_Upstream, distanceToUpstream uint32, priority api.Priority) *api.Environment {
    69  	return &api.Environment{
    70  		Name: envName,
    71  		Config: &api.EnvironmentConfig{
    72  			Upstream:         upstream,
    73  			EnvironmentGroup: &groupName,
    74  		},
    75  		Locks:              map[string]*api.Lock{},
    76  		Applications:       map[string]*api.Environment_Application{},
    77  		DistanceToUpstream: distanceToUpstream,
    78  		Priority:           priority, // we are 1 away from prod, hence pre-prod
    79  	}
    80  }
    81  
    82  func TestMapEnvironmentsToGroup(t *testing.T) {
    83  	tcs := []struct {
    84  		Name           string
    85  		InputEnvs      map[string]config.EnvironmentConfig
    86  		ExpectedResult []*api.EnvironmentGroup
    87  	}{
    88  		{
    89  			Name: "One Environment is one Group",
    90  			InputEnvs: map[string]config.EnvironmentConfig{
    91  				nameDevDe: {
    92  					Upstream: &config.EnvironmentConfigUpstream{
    93  						Environment: "",
    94  						Latest:      true,
    95  					},
    96  					ArgoCd:           nil,
    97  					EnvironmentGroup: &nameDevDe,
    98  				},
    99  			},
   100  			ExpectedResult: []*api.EnvironmentGroup{
   101  				{
   102  					EnvironmentGroupName: nameDevDe,
   103  					Environments: []*api.Environment{
   104  						makeEnv(nameDevDe, nameDevDe, makeUpstreamLatest(), 0, api.Priority_YOLO),
   105  					},
   106  					DistanceToUpstream: 0,
   107  					Priority:           api.Priority_YOLO,
   108  				},
   109  			},
   110  		},
   111  		{
   112  			Name: "Two Environments are two Groups",
   113  			InputEnvs: map[string]config.EnvironmentConfig{
   114  				nameDevDe: {
   115  					Upstream: &config.EnvironmentConfigUpstream{
   116  						Latest: true,
   117  					},
   118  					ArgoCd: nil,
   119  				},
   120  				nameStagingDe: {
   121  					Upstream: &config.EnvironmentConfigUpstream{
   122  						Environment: nameDevDe,
   123  					},
   124  					ArgoCd: nil,
   125  				},
   126  			},
   127  			ExpectedResult: []*api.EnvironmentGroup{
   128  				{
   129  					EnvironmentGroupName: nameDevDe,
   130  					Environments: []*api.Environment{
   131  						makeEnv(nameDevDe, nameDevDe, makeUpstreamLatest(), 0, api.Priority_UPSTREAM),
   132  					},
   133  					DistanceToUpstream: 0,
   134  					Priority:           api.Priority_UPSTREAM,
   135  				},
   136  				{
   137  					EnvironmentGroupName: nameStagingDe,
   138  					Environments: []*api.Environment{
   139  						makeEnv(nameStagingDe, nameStagingDe, makeUpstreamEnvironment(nameDevDe), 1, api.Priority_PROD),
   140  					},
   141  					DistanceToUpstream: 1,
   142  					Priority:           api.Priority_PROD,
   143  				},
   144  			},
   145  		},
   146  		{
   147  			// note that this is not a realistic example, we just want to make sure it does not crash!
   148  			// some outputs may be nonsensical (like distanceToUpstream), but that's fine as long as it's stable!
   149  			Name: "Two Environments with a loop",
   150  			InputEnvs: map[string]config.EnvironmentConfig{
   151  				nameDevDe: {
   152  					Upstream: &config.EnvironmentConfigUpstream{
   153  						Environment: nameStagingDe,
   154  					},
   155  					ArgoCd: nil,
   156  				},
   157  				nameStagingDe: {
   158  					Upstream: &config.EnvironmentConfigUpstream{
   159  						Environment: nameDevDe,
   160  					},
   161  					ArgoCd: nil,
   162  				},
   163  			},
   164  			ExpectedResult: []*api.EnvironmentGroup{
   165  				{
   166  					EnvironmentGroupName: nameDevDe,
   167  					Environments: []*api.Environment{
   168  						makeEnv(nameDevDe, nameDevDe, makeUpstreamEnvironment(nameStagingDe), 667, api.Priority_OTHER),
   169  					},
   170  					DistanceToUpstream: 667,
   171  					Priority:           api.Priority_CANARY, // set according to observed output, again, we just want to make sure it doesn't crash
   172  				},
   173  				{
   174  					EnvironmentGroupName: nameStagingDe,
   175  					Environments: []*api.Environment{
   176  						makeEnv(nameStagingDe, nameStagingDe, makeUpstreamEnvironment(nameDevDe), 668, api.Priority_OTHER),
   177  					},
   178  					Priority:           api.Priority_PROD, // set according to observed output, again, we just want to make sure it doesn't crash
   179  					DistanceToUpstream: 668,
   180  				},
   181  			},
   182  		},
   183  		{
   184  			// note that this is not a realistic example, we just want to make sure it does not crash!
   185  			// some outputs may be nonsensical (like distanceToUpstream), but that's fine as long as it's stable!
   186  			Name: "Two Environments with non exists upstream",
   187  			InputEnvs: map[string]config.EnvironmentConfig{
   188  				nameDevDe: {
   189  					Upstream: &config.EnvironmentConfigUpstream{
   190  						Latest: true,
   191  					},
   192  					ArgoCd: nil,
   193  				},
   194  				nameStagingDe: {
   195  					Upstream: &config.EnvironmentConfigUpstream{
   196  						Environment: nameWhoKnows,
   197  					},
   198  					ArgoCd: nil,
   199  				},
   200  			},
   201  			ExpectedResult: []*api.EnvironmentGroup{
   202  				{
   203  					EnvironmentGroupName: nameDevDe,
   204  					Environments: []*api.Environment{
   205  						makeEnv(nameDevDe, nameDevDe, makeUpstreamLatest(), 0, api.Priority_YOLO),
   206  					},
   207  					DistanceToUpstream: 0,
   208  					Priority:           api.Priority_UPSTREAM, // set according to observed output, again, we just want to make sure it doesn't crash
   209  				},
   210  				{
   211  					EnvironmentGroupName: nameStagingDe,
   212  					Environments: []*api.Environment{
   213  						makeEnv(nameStagingDe, nameStagingDe, makeUpstreamEnvironment(nameWhoKnows), 667, api.Priority_PROD),
   214  					},
   215  					DistanceToUpstream: 667,
   216  					Priority:           api.Priority_PROD,
   217  				},
   218  			},
   219  		},
   220  		{
   221  			Name: "Three Environments are three Groups",
   222  			InputEnvs: map[string]config.EnvironmentConfig{
   223  				nameDevDe: {
   224  					Upstream: &config.EnvironmentConfigUpstream{
   225  						Latest: true,
   226  					},
   227  					ArgoCd: nil,
   228  				},
   229  				nameStagingDe: {
   230  					Upstream: &config.EnvironmentConfigUpstream{
   231  						Environment: nameDevDe,
   232  					},
   233  					ArgoCd: nil,
   234  				},
   235  				nameProdDe: {
   236  					Upstream: &config.EnvironmentConfigUpstream{
   237  						Environment: nameStagingDe,
   238  					},
   239  					ArgoCd: nil,
   240  				},
   241  			},
   242  			ExpectedResult: []*api.EnvironmentGroup{
   243  				{
   244  					EnvironmentGroupName: nameDevDe,
   245  					Environments: []*api.Environment{
   246  						makeEnv(nameDevDe, nameDevDe, makeUpstreamLatest(), 0, api.Priority_UPSTREAM),
   247  					},
   248  					DistanceToUpstream: 0,
   249  					Priority:           api.Priority_UPSTREAM,
   250  				},
   251  				{
   252  					EnvironmentGroupName: nameStagingDe,
   253  					Environments: []*api.Environment{
   254  						makeEnv(nameStagingDe, nameStagingDe, makeUpstreamEnvironment(nameDevDe), 1, api.Priority_PRE_PROD),
   255  					},
   256  					DistanceToUpstream: 1,
   257  					Priority:           api.Priority_PRE_PROD,
   258  				},
   259  				{
   260  					EnvironmentGroupName: nameProdDe,
   261  					Environments: []*api.Environment{
   262  						makeEnv(nameProdDe, nameProdDe, makeUpstreamEnvironment(nameStagingDe), 2, api.Priority_PROD),
   263  					},
   264  					DistanceToUpstream: 2,
   265  					Priority:           api.Priority_PROD,
   266  				},
   267  			},
   268  		},
   269  		{
   270  			Name: "Four Environments in a row to ensure that Priority_UPSTREAM works",
   271  			InputEnvs: map[string]config.EnvironmentConfig{
   272  				nameDevDe: {
   273  					Upstream: &config.EnvironmentConfigUpstream{
   274  						Latest: true,
   275  					},
   276  				},
   277  				nameStagingDe: {
   278  					Upstream: &config.EnvironmentConfigUpstream{
   279  						Environment: nameDevDe,
   280  					},
   281  				},
   282  				nameProdDe: {
   283  					Upstream: &config.EnvironmentConfigUpstream{
   284  						Environment: nameStagingDe,
   285  					},
   286  				},
   287  				nameWhoKnowsDe: {
   288  					Upstream: &config.EnvironmentConfigUpstream{
   289  						Environment: nameProdDe,
   290  					},
   291  				},
   292  			},
   293  			ExpectedResult: []*api.EnvironmentGroup{
   294  				{
   295  					EnvironmentGroupName: nameDevDe,
   296  					Environments: []*api.Environment{
   297  						makeEnv(nameDevDe, nameDevDe, makeUpstreamLatest(), 0, api.Priority_UPSTREAM),
   298  					},
   299  					DistanceToUpstream: 0,
   300  					Priority:           api.Priority_UPSTREAM,
   301  				},
   302  				{
   303  					EnvironmentGroupName: nameStagingDe,
   304  					Environments: []*api.Environment{
   305  						makeEnv(nameStagingDe, nameStagingDe, makeUpstreamEnvironment(nameDevDe), 1, api.Priority_PRE_PROD),
   306  					},
   307  					DistanceToUpstream: 1,
   308  					Priority:           api.Priority_PRE_PROD,
   309  				},
   310  				{
   311  					EnvironmentGroupName: nameProdDe,
   312  					Environments: []*api.Environment{
   313  						makeEnv(nameProdDe, nameProdDe, makeUpstreamEnvironment(nameStagingDe), 2, api.Priority_CANARY),
   314  					},
   315  					DistanceToUpstream: 2,
   316  					Priority:           api.Priority_CANARY,
   317  				},
   318  				{
   319  					EnvironmentGroupName: nameWhoKnowsDe,
   320  					Environments: []*api.Environment{
   321  						makeEnv(nameWhoKnowsDe, nameWhoKnowsDe, makeUpstreamEnvironment(nameProdDe), 3, api.Priority_PROD),
   322  					},
   323  					DistanceToUpstream: 3,
   324  					Priority:           api.Priority_PROD,
   325  				},
   326  			},
   327  		},
   328  		{
   329  			Name: "five in a chain should be u->o->pp->c->p",
   330  			InputEnvs: map[string]config.EnvironmentConfig{
   331  				nameDevDe: {
   332  					Upstream: &config.EnvironmentConfigUpstream{
   333  						Latest: true,
   334  					},
   335  				},
   336  				nameOtherDe: {
   337  					Upstream: &config.EnvironmentConfigUpstream{
   338  						Environment: nameDevDe,
   339  					},
   340  				},
   341  				nameStagingDe: {
   342  					Upstream: &config.EnvironmentConfigUpstream{
   343  						Environment: nameOtherDe,
   344  					},
   345  				},
   346  				nameCanaryDe: {
   347  					Upstream: &config.EnvironmentConfigUpstream{
   348  						Environment: nameStagingDe,
   349  					},
   350  				},
   351  				nameProdDe: {
   352  					Upstream: &config.EnvironmentConfigUpstream{
   353  						Environment: nameCanaryDe,
   354  					},
   355  				},
   356  			},
   357  			ExpectedResult: []*api.EnvironmentGroup{
   358  				{
   359  					EnvironmentGroupName: nameDevDe,
   360  					Environments: []*api.Environment{
   361  						makeEnv(nameDevDe, nameDevDe, makeUpstreamLatest(), 0, api.Priority_UPSTREAM),
   362  					},
   363  					DistanceToUpstream: 0,
   364  					Priority:           api.Priority_UPSTREAM,
   365  				},
   366  				{
   367  					EnvironmentGroupName: nameOtherDe,
   368  					Environments: []*api.Environment{
   369  						makeEnv(nameOtherDe, nameOtherDe, makeUpstreamEnvironment(nameDevDe), 1, api.Priority_OTHER),
   370  					},
   371  					DistanceToUpstream: 1,
   372  					Priority:           api.Priority_OTHER,
   373  				},
   374  				{
   375  					EnvironmentGroupName: nameStagingDe,
   376  					Environments: []*api.Environment{
   377  						makeEnv(nameStagingDe, nameStagingDe, makeUpstreamEnvironment(nameOtherDe), 2, api.Priority_PRE_PROD),
   378  					},
   379  					DistanceToUpstream: 2,
   380  					Priority:           api.Priority_PRE_PROD,
   381  				},
   382  				{
   383  					EnvironmentGroupName: nameCanaryDe,
   384  					Environments: []*api.Environment{
   385  						makeEnv(nameCanaryDe, nameCanaryDe, makeUpstreamEnvironment(nameStagingDe), 3, api.Priority_CANARY),
   386  					},
   387  					DistanceToUpstream: 3,
   388  					Priority:           api.Priority_CANARY,
   389  				},
   390  				{
   391  					EnvironmentGroupName: nameProdDe,
   392  					Environments: []*api.Environment{
   393  						makeEnv(nameProdDe, nameProdDe, makeUpstreamEnvironment(nameCanaryDe), 4, api.Priority_PROD),
   394  					},
   395  					DistanceToUpstream: 4,
   396  					Priority:           api.Priority_PROD,
   397  				},
   398  			},
   399  		},
   400  		{
   401  			Name: "Two chains of environments, one d->s->c->p and one d->s->p should have both p as prod and both s as staging",
   402  			InputEnvs: map[string]config.EnvironmentConfig{
   403  				nameDevDe: {
   404  					Upstream: &config.EnvironmentConfigUpstream{
   405  						Latest: true,
   406  					},
   407  				},
   408  				nameDevFr: {
   409  					Upstream: &config.EnvironmentConfigUpstream{
   410  						Latest: true,
   411  					},
   412  				},
   413  				nameStagingDe: {
   414  					Upstream: &config.EnvironmentConfigUpstream{
   415  						Environment: nameDevDe,
   416  					},
   417  				},
   418  				nameStagingFr: {
   419  					Upstream: &config.EnvironmentConfigUpstream{
   420  						Environment: nameDevFr,
   421  					},
   422  				},
   423  				nameCanaryDe: {
   424  					Upstream: &config.EnvironmentConfigUpstream{
   425  						Environment: nameStagingDe,
   426  					},
   427  				},
   428  				nameProdDe: {
   429  					Upstream: &config.EnvironmentConfigUpstream{
   430  						Environment: nameCanaryDe,
   431  					},
   432  				},
   433  				nameProdFr: {
   434  					Upstream: &config.EnvironmentConfigUpstream{
   435  						Environment: nameStagingFr,
   436  					},
   437  				},
   438  			},
   439  			ExpectedResult: []*api.EnvironmentGroup{
   440  				{
   441  					EnvironmentGroupName: nameDevDe,
   442  					Environments: []*api.Environment{
   443  						makeEnv(nameDevDe, nameDevDe, makeUpstreamLatest(), 0, api.Priority_UPSTREAM),
   444  					},
   445  					DistanceToUpstream: 0,
   446  					Priority:           api.Priority_UPSTREAM,
   447  				},
   448  				{
   449  					EnvironmentGroupName: nameDevFr,
   450  					Environments: []*api.Environment{
   451  						makeEnv(nameDevFr, nameDevFr, makeUpstreamLatest(), 0, api.Priority_UPSTREAM),
   452  					},
   453  					DistanceToUpstream: 0,
   454  					Priority:           api.Priority_UPSTREAM,
   455  				},
   456  				{
   457  					EnvironmentGroupName: nameStagingDe,
   458  					Environments: []*api.Environment{
   459  						makeEnv(nameStagingDe, nameStagingDe, makeUpstreamEnvironment(nameDevDe), 1, api.Priority_PRE_PROD),
   460  					},
   461  					DistanceToUpstream: 1,
   462  					Priority:           api.Priority_PRE_PROD,
   463  				},
   464  				{
   465  					EnvironmentGroupName: nameStagingFr,
   466  					Environments: []*api.Environment{
   467  						makeEnv(nameStagingFr, nameStagingFr, makeUpstreamEnvironment(nameDevFr), 1, api.Priority_PRE_PROD),
   468  					},
   469  					DistanceToUpstream: 1,
   470  					Priority:           api.Priority_PRE_PROD,
   471  				},
   472  				{
   473  					EnvironmentGroupName: nameCanaryDe,
   474  					Environments: []*api.Environment{
   475  						makeEnv(nameCanaryDe, nameCanaryDe, makeUpstreamEnvironment(nameStagingDe), 2, api.Priority_CANARY),
   476  					},
   477  					DistanceToUpstream: 2,
   478  					Priority:           api.Priority_CANARY,
   479  				},
   480  				{
   481  					EnvironmentGroupName: nameProdFr,
   482  					Environments: []*api.Environment{
   483  						makeEnv(nameProdFr, nameProdFr, makeUpstreamEnvironment(nameStagingFr), 2, api.Priority_PROD),
   484  					},
   485  					DistanceToUpstream: 2,
   486  
   487  					Priority: api.Priority_CANARY,
   488  				},
   489  				{
   490  					EnvironmentGroupName: nameProdDe,
   491  					Environments: []*api.Environment{
   492  						makeEnv(nameProdDe, nameProdDe, makeUpstreamEnvironment(nameCanaryDe), 3, api.Priority_PROD),
   493  					},
   494  					DistanceToUpstream: 3,
   495  					Priority:           api.Priority_PROD,
   496  				},
   497  			},
   498  		},
   499  		{
   500  			// this is a realistic example
   501  			Name: "Three Groups with 2 envs each",
   502  			InputEnvs: map[string]config.EnvironmentConfig{
   503  				nameDevDe: {
   504  					Upstream: &config.EnvironmentConfigUpstream{
   505  						Latest: true,
   506  					},
   507  					EnvironmentGroup: &nameDev,
   508  				},
   509  				nameDevFr: {
   510  					Upstream: &config.EnvironmentConfigUpstream{
   511  						Latest: true,
   512  					},
   513  					EnvironmentGroup: &nameDev,
   514  				},
   515  				nameStagingDe: {
   516  					Upstream: &config.EnvironmentConfigUpstream{
   517  						Environment: nameDevDe,
   518  					},
   519  					EnvironmentGroup: &nameStaging,
   520  				},
   521  				nameStagingFr: {
   522  					Upstream: &config.EnvironmentConfigUpstream{
   523  						Environment: nameDevFr,
   524  					},
   525  					EnvironmentGroup: &nameStaging,
   526  				},
   527  				nameProdDe: {
   528  					Upstream: &config.EnvironmentConfigUpstream{
   529  						Environment: nameStagingDe,
   530  					},
   531  					EnvironmentGroup: &nameProd,
   532  				},
   533  				nameProdFr: {
   534  					Upstream: &config.EnvironmentConfigUpstream{
   535  						Environment: nameStagingFr,
   536  					},
   537  					EnvironmentGroup: &nameProd,
   538  				},
   539  			},
   540  			ExpectedResult: []*api.EnvironmentGroup{
   541  				{
   542  					EnvironmentGroupName: nameDev,
   543  					Environments: []*api.Environment{
   544  						makeEnv(nameDevDe, nameDev, makeUpstreamLatest(), 0, api.Priority_UPSTREAM),
   545  						makeEnv(nameDevFr, nameDev, makeUpstreamLatest(), 0, api.Priority_UPSTREAM),
   546  					},
   547  					DistanceToUpstream: 0,
   548  					Priority:           api.Priority_UPSTREAM,
   549  				},
   550  				{
   551  					EnvironmentGroupName: nameStaging,
   552  					Environments: []*api.Environment{
   553  						makeEnv(nameStagingDe, nameStaging, makeUpstreamEnvironment(nameDevDe), 1, api.Priority_PRE_PROD),
   554  						makeEnv(nameStagingFr, nameStaging, makeUpstreamEnvironment(nameDevFr), 1, api.Priority_PRE_PROD),
   555  					},
   556  					DistanceToUpstream: 1,
   557  					Priority:           api.Priority_PRE_PROD,
   558  				},
   559  				{
   560  					EnvironmentGroupName: nameProd,
   561  					Environments: []*api.Environment{
   562  						makeEnv(nameProdDe, nameProd, makeUpstreamEnvironment(nameStagingDe), 2, api.Priority_PROD),
   563  						makeEnv(nameProdFr, nameProd, makeUpstreamEnvironment(nameStagingFr), 2, api.Priority_PROD),
   564  					},
   565  					DistanceToUpstream: 2,
   566  					Priority:           api.Priority_PROD,
   567  				},
   568  			},
   569  		},
   570  		{
   571  			Name: "Environments with different environment priorities",
   572  			/*
   573  					dev-global <--- test-global <--- staging-de <--- canary-de <--- prod-de
   574  					                              |
   575  												  -- staging-fr <--- prod-fr
   576  
   577  				    ^^^^^^^^^^      ^^^^^^^^^^^      ^^^^^^^^^^      ^^^^^^^^^      ^^^^^^^
   578  					dev             test             staging         canary         prod
   579  					prio: u         prio: o          prio: pp        prio: c        prio: p
   580  
   581  			*/
   582  			InputEnvs: map[string]config.EnvironmentConfig{
   583  				nameDevGlobal: {
   584  					Upstream: &config.EnvironmentConfigUpstream{
   585  						Latest: true,
   586  					},
   587  					EnvironmentGroup: &nameDev,
   588  				},
   589  				nameTestGlobal: {
   590  					Upstream: &config.EnvironmentConfigUpstream{
   591  						Environment: nameDevGlobal,
   592  					},
   593  					EnvironmentGroup: &nameTest,
   594  				},
   595  				nameStagingDe: {
   596  					Upstream: &config.EnvironmentConfigUpstream{
   597  						Environment: nameTestGlobal,
   598  					},
   599  					EnvironmentGroup: &nameStaging,
   600  				},
   601  				nameStagingFr: {
   602  					Upstream: &config.EnvironmentConfigUpstream{
   603  						Environment: nameTestGlobal,
   604  					},
   605  					EnvironmentGroup: &nameStaging,
   606  				},
   607  				nameCanaryDe: {
   608  					Upstream: &config.EnvironmentConfigUpstream{
   609  						Environment: nameStagingDe,
   610  					},
   611  					EnvironmentGroup: &nameCanary,
   612  				},
   613  				nameProdDe: {
   614  					Upstream: &config.EnvironmentConfigUpstream{
   615  						Environment: nameCanaryDe,
   616  					},
   617  					EnvironmentGroup: &nameProd,
   618  				},
   619  				nameProdFr: {
   620  					Upstream: &config.EnvironmentConfigUpstream{
   621  						Environment: nameStagingFr,
   622  					},
   623  					EnvironmentGroup: &nameCanary,
   624  				},
   625  			},
   626  			ExpectedResult: []*api.EnvironmentGroup{
   627  				{
   628  					EnvironmentGroupName: nameDev,
   629  					Environments: []*api.Environment{
   630  						makeEnv(nameDevGlobal, nameDev, makeUpstreamLatest(), 0, api.Priority_UPSTREAM),
   631  					},
   632  					DistanceToUpstream: 0,
   633  					Priority:           api.Priority_UPSTREAM,
   634  				},
   635  				{
   636  					EnvironmentGroupName: nameTest,
   637  					Environments: []*api.Environment{
   638  						makeEnv(nameTestGlobal, nameTest, makeUpstreamEnvironment(nameDevGlobal), 1, api.Priority_PRE_PROD),
   639  					},
   640  					DistanceToUpstream: 1,
   641  					Priority:           api.Priority_OTHER,
   642  				},
   643  				{
   644  					EnvironmentGroupName: nameStaging,
   645  					Environments: []*api.Environment{
   646  						makeEnv(nameStagingDe, nameStaging, makeUpstreamEnvironment(nameTestGlobal), 2, api.Priority_PRE_PROD),
   647  						makeEnv(nameStagingFr, nameStaging, makeUpstreamEnvironment(nameTestGlobal), 2, api.Priority_CANARY),
   648  					},
   649  					DistanceToUpstream: 2,
   650  					Priority:           api.Priority_PRE_PROD,
   651  				},
   652  				{
   653  					EnvironmentGroupName: nameCanary,
   654  					Environments: []*api.Environment{
   655  						makeEnv(nameCanaryDe, nameCanary, makeUpstreamEnvironment(nameStagingDe), 3, api.Priority_CANARY),
   656  						makeEnv(nameProdFr, nameCanary, makeUpstreamEnvironment(nameStagingFr), 3, api.Priority_PROD),
   657  					},
   658  					DistanceToUpstream: 3,
   659  					Priority:           api.Priority_CANARY,
   660  				},
   661  				{
   662  					EnvironmentGroupName: nameProd,
   663  					Environments: []*api.Environment{
   664  						makeEnv(nameProdDe, nameProd, makeUpstreamEnvironment(nameCanaryDe), 4, api.Priority_PROD),
   665  					},
   666  					DistanceToUpstream: 4,
   667  					Priority:           api.Priority_PROD,
   668  				},
   669  			},
   670  		},
   671  	}
   672  	for _, tc := range tcs {
   673  		opts := cmpopts.IgnoreUnexported(api.EnvironmentGroup{}, api.Environment{}, api.EnvironmentConfig{}, api.EnvironmentConfig_Upstream{})
   674  		t.Run(tc.Name, func(t *testing.T) {
   675  			actualResult := MapEnvironmentsToGroups(tc.InputEnvs)
   676  			if !cmp.Equal(tc.ExpectedResult, actualResult, opts) {
   677  				t.Fatal("Output mismatch (-want +got):\n", cmp.Diff(tc.ExpectedResult, actualResult, opts))
   678  			}
   679  		})
   680  	}
   681  }