github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/cd-service/pkg/service/overview_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 service
    18  
    19  import (
    20  	"context"
    21  	"github.com/freiheit-com/kuberpult/services/cd-service/pkg/repository/testutil"
    22  	"sync"
    23  	"testing"
    24  
    25  	api "github.com/freiheit-com/kuberpult/pkg/api/v1"
    26  	"github.com/freiheit-com/kuberpult/pkg/auth"
    27  	"github.com/freiheit-com/kuberpult/services/cd-service/pkg/config"
    28  	"github.com/freiheit-com/kuberpult/services/cd-service/pkg/repository"
    29  	"github.com/google/go-cmp/cmp"
    30  	"google.golang.org/grpc"
    31  )
    32  
    33  type mockOverviewService_StreamOverviewServer struct {
    34  	grpc.ServerStream
    35  	Results chan *api.GetOverviewResponse
    36  	Ctx     context.Context
    37  }
    38  
    39  func (m *mockOverviewService_StreamOverviewServer) Send(msg *api.GetOverviewResponse) error {
    40  	m.Results <- msg
    41  	return nil
    42  }
    43  
    44  func (m *mockOverviewService_StreamOverviewServer) Context() context.Context {
    45  	return m.Ctx
    46  }
    47  
    48  func makeApps(apps ...*api.Environment_Application) map[string]*api.Environment_Application {
    49  	var result map[string]*api.Environment_Application = map[string]*api.Environment_Application{}
    50  	for i := 0; i < len(apps); i++ {
    51  		app := apps[i]
    52  		result[app.Name] = app
    53  	}
    54  	return result
    55  }
    56  
    57  func makeEnv(envName string, groupName string, upstream *api.EnvironmentConfig_Upstream, apps map[string]*api.Environment_Application) *api.Environment {
    58  	return &api.Environment{
    59  		Name: envName,
    60  		Config: &api.EnvironmentConfig{
    61  			Upstream:         upstream,
    62  			EnvironmentGroup: &groupName,
    63  		},
    64  		Locks:              map[string]*api.Lock{},
    65  		Applications:       apps,
    66  		DistanceToUpstream: 0,
    67  		Priority:           api.Priority_UPSTREAM, // we are 1 away from prod, hence pre-prod
    68  	}
    69  }
    70  
    71  func makeApp(appName string, version uint64) *api.Environment_Application {
    72  	return &api.Environment_Application{
    73  		Name:            appName,
    74  		Version:         version,
    75  		Locks:           nil,
    76  		QueuedVersion:   0,
    77  		UndeployVersion: false,
    78  		ArgoCd:          nil,
    79  	}
    80  }
    81  func makeEnvGroup(envGroupName string, environments []*api.Environment) *api.EnvironmentGroup {
    82  	return &api.EnvironmentGroup{
    83  		EnvironmentGroupName: envGroupName,
    84  		Environments:         environments,
    85  		DistanceToUpstream:   0,
    86  	}
    87  }
    88  
    89  func makeUpstreamLatest() *api.EnvironmentConfig_Upstream {
    90  	f := true
    91  	return &api.EnvironmentConfig_Upstream{
    92  		Latest: &f,
    93  	}
    94  }
    95  
    96  func makeUpstreamEnv(upstream string) *api.EnvironmentConfig_Upstream {
    97  	return &api.EnvironmentConfig_Upstream{
    98  		Environment: &upstream,
    99  	}
   100  }
   101  
   102  func TestCalculateWarnings(t *testing.T) {
   103  	var dev = "dev"
   104  	tcs := []struct {
   105  		Name             string
   106  		AppName          string
   107  		Groups           []*api.EnvironmentGroup
   108  		ExpectedWarnings []*api.Warning
   109  	}{
   110  		{
   111  			Name:    "no envs - no warning",
   112  			AppName: "foo",
   113  			Groups: []*api.EnvironmentGroup{
   114  				makeEnvGroup(dev, []*api.Environment{
   115  					makeEnv("dev-de", dev, makeUpstreamLatest(), nil),
   116  				})},
   117  			ExpectedWarnings: []*api.Warning{},
   118  		},
   119  		{
   120  			Name:    "app deployed in higher version on upstream should warn",
   121  			AppName: "foo",
   122  			Groups: []*api.EnvironmentGroup{
   123  				makeEnvGroup(dev, []*api.Environment{
   124  					makeEnv("prod", dev, makeUpstreamEnv("dev"),
   125  						makeApps(makeApp("foo", 2))),
   126  				}),
   127  				makeEnvGroup(dev, []*api.Environment{
   128  					makeEnv("dev", dev, makeUpstreamLatest(),
   129  						makeApps(makeApp("foo", 1))),
   130  				}),
   131  			},
   132  			ExpectedWarnings: []*api.Warning{
   133  				{
   134  					WarningType: &api.Warning_UnusualDeploymentOrder{
   135  						UnusualDeploymentOrder: &api.UnusualDeploymentOrder{
   136  							UpstreamVersion:     1,
   137  							UpstreamEnvironment: "dev",
   138  							ThisVersion:         2,
   139  							ThisEnvironment:     "prod",
   140  						},
   141  					},
   142  				},
   143  			},
   144  		},
   145  		{
   146  			Name:    "app deployed in same version on upstream should not warn",
   147  			AppName: "foo",
   148  			Groups: []*api.EnvironmentGroup{
   149  				makeEnvGroup(dev, []*api.Environment{
   150  					makeEnv("prod", dev, makeUpstreamEnv("dev"),
   151  						makeApps(makeApp("foo", 2))),
   152  				}),
   153  				makeEnvGroup(dev, []*api.Environment{
   154  					makeEnv("dev", dev, makeUpstreamLatest(),
   155  						makeApps(makeApp("foo", 2))),
   156  				}),
   157  			},
   158  			ExpectedWarnings: []*api.Warning{},
   159  		},
   160  		{
   161  			Name:    "app deployed in no version on upstream should warn",
   162  			AppName: "foo",
   163  			Groups: []*api.EnvironmentGroup{
   164  				makeEnvGroup(dev, []*api.Environment{
   165  					makeEnv("prod", dev, makeUpstreamEnv("dev"),
   166  						makeApps(makeApp("foo", 1))),
   167  				}),
   168  				makeEnvGroup(dev, []*api.Environment{
   169  					makeEnv("dev", dev, makeUpstreamLatest(),
   170  						makeApps()),
   171  				}),
   172  			},
   173  			ExpectedWarnings: []*api.Warning{
   174  				{
   175  					WarningType: &api.Warning_UpstreamNotDeployed{
   176  						UpstreamNotDeployed: &api.UpstreamNotDeployed{
   177  							UpstreamEnvironment: "dev",
   178  							ThisVersion:         1,
   179  							ThisEnvironment:     "prod",
   180  						},
   181  					},
   182  				},
   183  			},
   184  		},
   185  	}
   186  	for _, tc := range tcs {
   187  		tc := tc
   188  		t.Run(tc.Name, func(t *testing.T) {
   189  			actualWarnings := CalculateWarnings(testutil.MakeTestContext(), tc.AppName, tc.Groups)
   190  			if len(actualWarnings) != len(tc.ExpectedWarnings) {
   191  				t.Errorf("Different number of warnings. got: %s\nwant: %s", actualWarnings, tc.ExpectedWarnings)
   192  			}
   193  			for i := 0; i < len(actualWarnings); i++ {
   194  				actualWarning := actualWarnings[i]
   195  				expectedWarning := tc.ExpectedWarnings[i]
   196  				if diff := cmp.Diff(actualWarning.String(), expectedWarning.String()); diff != "" {
   197  					t.Errorf("Different warning at index [%d]:\ngot:  %s\nwant: %s", i, actualWarning, expectedWarning)
   198  				}
   199  			}
   200  		})
   201  	}
   202  
   203  }
   204  
   205  func TestOverviewService(t *testing.T) {
   206  	var dev = "dev"
   207  	tcs := []struct {
   208  		Name  string
   209  		Setup []repository.Transformer
   210  		Test  func(t *testing.T, svc *OverviewServiceServer)
   211  	}{
   212  		{
   213  			Name: "A simple overview works",
   214  			Setup: []repository.Transformer{
   215  				&repository.CreateEnvironment{
   216  					Environment: "development",
   217  					Config: config.EnvironmentConfig{
   218  						Upstream: &config.EnvironmentConfigUpstream{
   219  							Latest: true,
   220  						},
   221  						ArgoCd:           nil,
   222  						EnvironmentGroup: &dev,
   223  					},
   224  				},
   225  				&repository.CreateEnvironment{
   226  					Environment: "staging",
   227  					Config: config.EnvironmentConfig{
   228  						Upstream: &config.EnvironmentConfigUpstream{
   229  							Environment: "development",
   230  						},
   231  					},
   232  				},
   233  				&repository.CreateEnvironment{
   234  					Environment: "production",
   235  					Config: config.EnvironmentConfig{
   236  						Upstream: &config.EnvironmentConfigUpstream{
   237  							Environment: "staging",
   238  						},
   239  					},
   240  				},
   241  				&repository.CreateApplicationVersion{
   242  					Application: "test",
   243  					Manifests: map[string]string{
   244  						"development": "dev",
   245  					},
   246  					SourceAuthor:   "example <example@example.com>",
   247  					SourceCommitId: "deadbeef",
   248  					SourceMessage:  "changed something (#678)",
   249  					SourceRepoUrl:  "testing@testing.com/abc",
   250  				},
   251  				&repository.CreateApplicationVersion{
   252  					Application: "test-with-team",
   253  					Manifests: map[string]string{
   254  						"development": "dev",
   255  					},
   256  					Team: "test-team",
   257  				},
   258  				&repository.CreateApplicationVersion{
   259  					Application: "test-with-incorrect-pr-number",
   260  					Manifests: map[string]string{
   261  						"development": "dev",
   262  					},
   263  					SourceAuthor:   "example <example@example.com>",
   264  					SourceCommitId: "deadbeef",
   265  					SourceMessage:  "changed something (#678",
   266  					SourceRepoUrl:  "testing@testing.com/abc",
   267  				},
   268  				&repository.CreateApplicationVersion{
   269  					Application: "test-with-only-pr-number",
   270  					Manifests: map[string]string{
   271  						"development": "dev",
   272  					},
   273  					SourceAuthor:   "example <example@example.com>",
   274  					SourceCommitId: "deadbeef",
   275  					SourceMessage:  "(#678)",
   276  					SourceRepoUrl:  "testing@testing.com/abc",
   277  				},
   278  				&repository.DeployApplicationVersion{
   279  					Application: "test",
   280  					Environment: "development",
   281  					Version:     1,
   282  				},
   283  				&repository.DeployApplicationVersion{
   284  					Application: "test-with-team",
   285  					Environment: "development",
   286  					Version:     1,
   287  				},
   288  				&repository.CreateEnvironmentLock{
   289  					Environment: "development",
   290  					LockId:      "manual",
   291  					Message:     "please",
   292  				},
   293  				&repository.CreateEnvironmentApplicationLock{
   294  					Environment: "production",
   295  					Application: "test",
   296  					LockId:      "manual",
   297  					Message:     "no",
   298  				},
   299  			},
   300  			Test: func(t *testing.T, svc *OverviewServiceServer) {
   301  				var ctx = auth.WriteUserToContext(testutil.MakeTestContext(), auth.User{
   302  					Email: "test-email@example.com",
   303  					Name:  "overview tester",
   304  				})
   305  				resp, err := svc.GetOverview(ctx, &api.GetOverviewRequest{})
   306  				if err != nil {
   307  					t.Fatal(err)
   308  				}
   309  				if resp.GitRevision == "" {
   310  					t.Errorf("expected non-empty git revision but was empty")
   311  				}
   312  
   313  				const expectedEnvs = 3
   314  				if len(resp.EnvironmentGroups) != expectedEnvs {
   315  					t.Errorf("expected %d environmentGroups, got %q", expectedEnvs, resp.EnvironmentGroups)
   316  				}
   317  				testApp := resp.Applications["test"]
   318  				if testApp.SourceRepoUrl != "testing@testing.com/abc" {
   319  					t.Errorf("Expected \"testing@testing.com/abc\", but got %#q", resp.Applications["test"].SourceRepoUrl)
   320  				}
   321  				releases := testApp.Releases
   322  				if len(releases) != 1 {
   323  					t.Errorf("Expected one release, but got %#q", len(releases))
   324  				}
   325  				if releases[0].PrNumber != "678" {
   326  					t.Errorf("Release should have PR number \"678\", but got %q", releases[0].PrNumber)
   327  				}
   328  				testApp = resp.Applications["test-with-team"]
   329  				if testApp.SourceRepoUrl != "" {
   330  					t.Errorf("Expected \"\", but got %#q", resp.Applications["test"].SourceRepoUrl)
   331  				}
   332  				releases = testApp.Releases
   333  				if len(releases) != 1 {
   334  					t.Errorf("Expected one release, but got %#q", len(releases))
   335  				}
   336  				if releases[0].PrNumber != "" {
   337  					t.Errorf("Release should not have PR number")
   338  				}
   339  				testApp = resp.Applications["test-with-incorrect-pr-number"]
   340  				releases = testApp.Releases
   341  				if len(releases) != 1 {
   342  					t.Errorf("Expected one release, but got %#q", len(releases))
   343  				}
   344  				if releases[0].PrNumber != "" {
   345  					t.Errorf("Release should not have PR number since is an invalid PR number")
   346  				}
   347  				testApp = resp.Applications["test-with-only-pr-number"]
   348  				releases = testApp.Releases
   349  				if len(releases) != 1 {
   350  					t.Errorf("Expected one release, but got %#q", len(releases))
   351  				}
   352  				if releases[0].PrNumber == "" {
   353  					t.Errorf("Release should have PR number \"678\", but got %q", releases[0].PrNumber)
   354  				}
   355  
   356  				// Check Dev
   357  				// Note that EnvironmentGroups are sorted, so it's dev,staging,production (see MapEnvironmentsToGroups for details on sorting)
   358  				devGroup := resp.EnvironmentGroups[0]
   359  				if devGroup.EnvironmentGroupName != "dev" {
   360  					t.Errorf("dev environmentGroup has wrong name: %q", devGroup.EnvironmentGroupName)
   361  				}
   362  				dev := devGroup.Environments[0]
   363  				if dev.Name != "development" {
   364  					t.Errorf("development environment has wrong name: %q", dev.Name)
   365  				}
   366  				if dev.Config.Upstream == nil {
   367  					t.Errorf("development environment has wrong upstream: %#v", dev.Config.Upstream)
   368  				} else {
   369  					if !dev.Config.Upstream.GetLatest() {
   370  						t.Errorf("production environment has wrong upstream: %#v", dev.Config.Upstream)
   371  					}
   372  				}
   373  
   374  				if len(dev.Locks) != 1 {
   375  					t.Errorf("development environment has wrong locks: %#v", dev.Locks)
   376  				}
   377  				if lck, ok := dev.Locks["manual"]; !ok {
   378  					t.Errorf("development environment doesn't contain manual lock: %#v", dev.Locks)
   379  				} else {
   380  					if lck.Message != "please" {
   381  						t.Errorf("development environment manual lock has wrong message: %q", lck.Message)
   382  					}
   383  				}
   384  				if len(dev.Applications) != 4 {
   385  					t.Errorf("development environment has wrong applications: %#v", dev.Applications)
   386  				}
   387  				if app, ok := dev.Applications["test"]; !ok {
   388  					t.Errorf("development environment has wrong applications: %#v", dev.Applications)
   389  				} else {
   390  					if app.Version != 1 {
   391  						t.Errorf("test application has not version 1 but %d", app.Version)
   392  					}
   393  					if len(app.Locks) != 0 {
   394  						t.Errorf("test application has locks in development: %#v", app.Locks)
   395  					}
   396  				}
   397  
   398  				got := dev.Applications["test"].GetDeploymentMetaData().DeployAuthor
   399  				if got != "test tester" {
   400  					t.Errorf("development environment deployment did not create deploymentMetaData, got %s", got)
   401  				}
   402  
   403  				// Check staging
   404  				stageGroup := resp.EnvironmentGroups[1]
   405  				if stageGroup.EnvironmentGroupName != "staging" {
   406  					t.Errorf("staging environmentGroup has wrong name: %q", stageGroup.EnvironmentGroupName)
   407  				}
   408  				stage := stageGroup.Environments[0]
   409  				if stage.Name != "staging" {
   410  					t.Errorf("staging environment has wrong name: %q", stage.Name)
   411  				}
   412  				if stage.Config.Upstream == nil {
   413  					t.Errorf("staging environment has wrong upstream: %#v", stage.Config.Upstream)
   414  				} else {
   415  					if stage.Config.Upstream.GetEnvironment() != "development" {
   416  						t.Errorf("staging environment has wrong upstream: %#v", stage.Config.Upstream)
   417  					}
   418  					if stage.Config.Upstream.GetLatest() {
   419  						t.Errorf("staging environment has wrong upstream: %#v", stage.Config.Upstream)
   420  					}
   421  				}
   422  				if len(stage.Locks) != 0 {
   423  					t.Errorf("staging environment has wrong locks: %#v", stage.Locks)
   424  				}
   425  				if len(stage.Applications) != 0 {
   426  					t.Errorf("staging environment has wrong applications: %#v", stage.Applications)
   427  				}
   428  
   429  				// Check production
   430  				prodGroup := resp.EnvironmentGroups[2]
   431  				if prodGroup.EnvironmentGroupName != "production" {
   432  					t.Errorf("prod environmentGroup has wrong name: %q", prodGroup.EnvironmentGroupName)
   433  				}
   434  				prod := prodGroup.Environments[0]
   435  				if prod.Name != "production" {
   436  					t.Errorf("production environment has wrong name: %q", prod.Name)
   437  				}
   438  				if prod.Config.Upstream == nil {
   439  					t.Errorf("production environment has wrong upstream: %#v", prod.Config.Upstream)
   440  				} else {
   441  					if prod.Config.Upstream.GetEnvironment() != "staging" {
   442  						t.Errorf("production environment has wrong upstream: %#v", prod.Config.Upstream)
   443  					}
   444  					if prod.Config.Upstream.GetLatest() {
   445  						t.Errorf("production environment has wrong upstream: %#v", prod.Config.Upstream)
   446  					}
   447  				}
   448  				if len(prod.Locks) != 0 {
   449  					t.Errorf("production environment has wrong locks: %#v", prod.Locks)
   450  				}
   451  				if len(prod.Applications) != 1 {
   452  					t.Errorf("production environment has wrong applications: %#v", prod.Applications)
   453  				}
   454  				if app, ok := prod.Applications["test"]; !ok {
   455  					t.Errorf("production environment has wrong applications: %#v", prod.Applications)
   456  				} else {
   457  					if app.Version != 0 {
   458  						t.Errorf("test application has not version 0 but %d", app.Version)
   459  					}
   460  					if len(app.Locks) != 1 {
   461  						t.Errorf("test application has locks in production: %#v", app.Locks)
   462  					}
   463  				}
   464  
   465  				// Check applications
   466  				if len(resp.Applications) != 4 {
   467  					t.Errorf("expected two application, got %#v", resp.Applications)
   468  				}
   469  				if test, ok := resp.Applications["test"]; !ok {
   470  					t.Errorf("test application is missing in %#v", resp.Applications)
   471  				} else {
   472  					if test.Name != "test" {
   473  						t.Errorf("test applications name is not test but %q", test.Name)
   474  					}
   475  					if len(test.Releases) != 1 {
   476  						t.Errorf("expected one release, got %#v", test.Releases)
   477  					}
   478  					if test.Releases[0].Version != 1 {
   479  						t.Errorf("expected test release version to be 1, but got %d", test.Releases[0].Version)
   480  					}
   481  					if test.Releases[0].SourceAuthor != "example <example@example.com>" {
   482  						t.Errorf("expected test source author to be \"example <example@example.com>\", but got %q", test.Releases[0].SourceAuthor)
   483  					}
   484  					if test.Releases[0].SourceMessage != "changed something (#678)" {
   485  						t.Errorf("expected test source message to be \"changed something\", but got %q", test.Releases[0].SourceMessage)
   486  					}
   487  					if test.Releases[0].SourceCommitId != "deadbeef" {
   488  						t.Errorf("expected test source commit id to be \"deadbeef\", but got %q", test.Releases[0].SourceCommitId)
   489  					}
   490  				}
   491  				if testWithTeam, ok := resp.Applications["test-with-team"]; !ok {
   492  					t.Errorf("test-with-team application is missing in %#v", resp.Applications)
   493  				} else {
   494  					if testWithTeam.Team != "test-team" {
   495  						t.Errorf("application team is not test-team but %q", testWithTeam.Team)
   496  					}
   497  				}
   498  			},
   499  		},
   500  		{
   501  			Name: "A stream overview works",
   502  			Setup: []repository.Transformer{
   503  				&repository.CreateEnvironment{
   504  					Environment: "development",
   505  					Config:      config.EnvironmentConfig{},
   506  				},
   507  				&repository.CreateApplicationVersion{
   508  					Application: "test",
   509  					Manifests: map[string]string{
   510  						"development": "v1",
   511  					},
   512  				},
   513  				&repository.CreateApplicationVersion{
   514  					Application: "test",
   515  					Manifests: map[string]string{
   516  						"development": "v2",
   517  					},
   518  				},
   519  				&repository.DeployApplicationVersion{
   520  					Application: "test",
   521  					Environment: "development",
   522  					Version:     1,
   523  				},
   524  			},
   525  			Test: func(t *testing.T, svc *OverviewServiceServer) {
   526  				ctx, cancel := context.WithCancel(testutil.MakeTestContext())
   527  				ch := make(chan *api.GetOverviewResponse)
   528  				stream := mockOverviewService_StreamOverviewServer{
   529  					Results: ch,
   530  					Ctx:     ctx,
   531  				}
   532  				wg := sync.WaitGroup{}
   533  				wg.Add(1)
   534  				go func() {
   535  					defer wg.Done()
   536  					err := svc.StreamOverview(&api.GetOverviewRequest{}, &stream)
   537  					if err != nil {
   538  						t.Fatal(err)
   539  					}
   540  				}()
   541  
   542  				// Check that we get a first overview
   543  				overview1 := <-ch
   544  				if overview1 == nil {
   545  					t.Fatal("overview is nil")
   546  				}
   547  				v1 := overview1.GetEnvironmentGroups()[0].GetEnvironments()[0].GetApplications()["test"].Version
   548  
   549  				// Update a version and see that the version changed
   550  				err := svc.Repository.Apply(ctx, &repository.DeployApplicationVersion{
   551  					Application: "test",
   552  					Environment: "development",
   553  					Version:     2,
   554  				})
   555  				if err != nil {
   556  					t.Fatal(err)
   557  				}
   558  
   559  				// Check that the second overview is different
   560  				overview2 := <-ch
   561  				if overview2 == nil {
   562  					t.Fatal("overview is nil")
   563  				}
   564  				v2 := overview2.EnvironmentGroups[0].Environments[0].Applications["test"].Version
   565  				if v1 == v2 {
   566  					t.Fatalf("Versions are not different: %q vs %q", v1, v2)
   567  				}
   568  
   569  				if overview1.GitRevision == overview2.GitRevision {
   570  					t.Errorf("Git Revisions are not different: %q", overview1.GitRevision)
   571  				}
   572  
   573  				cancel()
   574  				wg.Wait()
   575  			},
   576  		},
   577  	}
   578  	for _, tc := range tcs {
   579  		tc := tc
   580  		t.Run(tc.Name, func(t *testing.T) {
   581  			shutdown := make(chan struct{}, 1)
   582  			repo, err := setupRepositoryTest(t)
   583  			if err != nil {
   584  				t.Fatal(err)
   585  			}
   586  			for _, tr := range tc.Setup {
   587  				if err := repo.Apply(testutil.MakeTestContext(), tr); err != nil {
   588  					t.Fatal(err)
   589  				}
   590  			}
   591  			svc := &OverviewServiceServer{
   592  				Repository: repo,
   593  				Shutdown:   shutdown,
   594  			}
   595  			tc.Test(t, svc)
   596  			close(shutdown)
   597  		})
   598  	}
   599  }
   600  
   601  func TestOverviewServiceFromCommit(t *testing.T) {
   602  	type step struct {
   603  		Transformer repository.Transformer
   604  	}
   605  	tcs := []struct {
   606  		Name  string
   607  		Steps []step
   608  	}{
   609  		{
   610  			Name: "A simple overview works",
   611  			Steps: []step{
   612  				{
   613  					Transformer: &repository.CreateEnvironment{
   614  						Environment: "development",
   615  						Config:      config.EnvironmentConfig{},
   616  					},
   617  				},
   618  				{
   619  					Transformer: &repository.CreateEnvironment{
   620  						Environment: "staging",
   621  						Config: config.EnvironmentConfig{
   622  							Upstream: &config.EnvironmentConfigUpstream{
   623  								Latest: true,
   624  							},
   625  						},
   626  					},
   627  				},
   628  				{
   629  					Transformer: &repository.CreateEnvironment{
   630  						Environment: "production",
   631  						Config: config.EnvironmentConfig{
   632  							Upstream: &config.EnvironmentConfigUpstream{
   633  								Environment: "staging",
   634  							},
   635  						},
   636  					},
   637  				},
   638  				{
   639  					Transformer: &repository.CreateApplicationVersion{
   640  						Application: "test",
   641  						Manifests: map[string]string{
   642  							"development": "dev",
   643  						},
   644  						SourceAuthor:   "example <example@example.com>",
   645  						SourceCommitId: "deadbeef",
   646  						SourceMessage:  "changed something (#678)",
   647  						SourceRepoUrl:  "testing@testing.com/abc",
   648  					},
   649  				},
   650  				{
   651  					Transformer: &repository.CreateApplicationVersion{
   652  						Application: "test-with-team",
   653  						Manifests: map[string]string{
   654  							"development": "dev",
   655  						},
   656  						Team: "test-team",
   657  					},
   658  				},
   659  				{
   660  					Transformer: &repository.CreateApplicationVersion{
   661  						Application: "test-with-incorrect-pr-number",
   662  						Manifests: map[string]string{
   663  							"development": "dev",
   664  						},
   665  						SourceAuthor:   "example <example@example.com>",
   666  						SourceCommitId: "deadbeef",
   667  						SourceMessage:  "changed something (#678",
   668  						SourceRepoUrl:  "testing@testing.com/abc",
   669  					},
   670  				},
   671  				{
   672  					Transformer: &repository.CreateApplicationVersion{
   673  						Application: "test-with-only-pr-number",
   674  						Manifests: map[string]string{
   675  							"development": "dev",
   676  						},
   677  						SourceAuthor:   "example <example@example.com>",
   678  						SourceCommitId: "deadbeef",
   679  						SourceMessage:  "(#678)",
   680  						SourceRepoUrl:  "testing@testing.com/abc",
   681  					},
   682  				},
   683  				{
   684  					Transformer: &repository.DeployApplicationVersion{
   685  						Application: "test",
   686  						Environment: "development",
   687  						Version:     1,
   688  					},
   689  				},
   690  				{
   691  					Transformer: &repository.DeployApplicationVersion{
   692  						Application: "test-with-team",
   693  						Environment: "development",
   694  						Version:     1,
   695  					},
   696  				},
   697  				{
   698  					Transformer: &repository.CreateEnvironmentLock{
   699  						Environment: "development",
   700  						LockId:      "manual",
   701  						Message:     "please",
   702  					},
   703  				},
   704  				{
   705  					Transformer: &repository.CreateEnvironmentApplicationLock{
   706  						Environment: "production",
   707  						Application: "test",
   708  						LockId:      "manual",
   709  						Message:     "no",
   710  					},
   711  				},
   712  			},
   713  		},
   714  	}
   715  	for _, tc := range tcs {
   716  		tc := tc
   717  		t.Run(tc.Name, func(t *testing.T) {
   718  			shutdown := make(chan struct{}, 1)
   719  			repo, err := setupRepositoryTest(t)
   720  			if err != nil {
   721  				t.Fatal(err)
   722  			}
   723  			svc := &OverviewServiceServer{
   724  				Repository: repo,
   725  				Shutdown:   shutdown,
   726  			}
   727  
   728  			ov, err := svc.GetOverview(testutil.MakeTestContext(), &api.GetOverviewRequest{})
   729  			if err != nil {
   730  				t.Errorf("expected no error, got %s", err)
   731  			}
   732  			if ov.GitRevision != "" {
   733  				t.Errorf("expected git revision to be empty, got %q", ov.GitRevision)
   734  			}
   735  			revisions := map[string]*api.GetOverviewResponse{}
   736  			for _, tr := range tc.Steps {
   737  				if err := repo.Apply(testutil.MakeTestContext(), tr.Transformer); err != nil {
   738  					t.Fatal(err)
   739  				}
   740  				ov, err = svc.GetOverview(testutil.MakeTestContext(), &api.GetOverviewRequest{})
   741  				if err != nil {
   742  					t.Errorf("expected no error, got %s", err)
   743  				}
   744  				if ov.GitRevision == "" {
   745  					t.Errorf("expected git revision to be non-empty")
   746  				}
   747  				if revisions[ov.GitRevision] != nil {
   748  					t.Errorf("git revision was observed twice: %q", ov.GitRevision)
   749  				}
   750  				revisions[ov.GitRevision] = ov
   751  			}
   752  			for rev := range revisions {
   753  				ov, err = svc.GetOverview(testutil.MakeTestContext(), &api.GetOverviewRequest{GitRevision: rev})
   754  				if err != nil {
   755  					t.Errorf("expected no error, got %s", err)
   756  				}
   757  				if ov.GitRevision != rev {
   758  					t.Errorf("expected git revision to be %q, but got %q", rev, ov.GitRevision)
   759  				}
   760  			}
   761  			close(shutdown)
   762  		})
   763  	}
   764  }
   765  
   766  func groupFromEnvs(environments []*api.Environment) []*api.EnvironmentGroup {
   767  	return []*api.EnvironmentGroup{
   768  		{
   769  			EnvironmentGroupName: "group1",
   770  			Environments:         environments,
   771  		},
   772  	}
   773  }
   774  
   775  func TestDeriveUndeploySummary(t *testing.T) {
   776  	var tcs = []struct {
   777  		Name           string
   778  		AppName        string
   779  		groups         []*api.EnvironmentGroup
   780  		ExpectedResult api.UndeploySummary
   781  	}{
   782  		{
   783  			Name:           "No Environments",
   784  			AppName:        "foo",
   785  			groups:         []*api.EnvironmentGroup{},
   786  			ExpectedResult: api.UndeploySummary_UNDEPLOY,
   787  		},
   788  		{
   789  			Name:    "one Environment but no Application",
   790  			AppName: "foo",
   791  			groups: groupFromEnvs([]*api.Environment{
   792  				{
   793  					Applications: map[string]*api.Environment_Application{
   794  						"bar": { // different app
   795  							UndeployVersion: true,
   796  							Version:         666,
   797  						},
   798  					},
   799  				},
   800  			}),
   801  			ExpectedResult: api.UndeploySummary_UNDEPLOY,
   802  		},
   803  		{
   804  			Name:    "One Env with undeploy",
   805  			AppName: "foo",
   806  			groups: groupFromEnvs([]*api.Environment{
   807  				{
   808  					Applications: map[string]*api.Environment_Application{
   809  						"foo": {
   810  							UndeployVersion: true,
   811  							Version:         666,
   812  						},
   813  					},
   814  				},
   815  			}),
   816  			ExpectedResult: api.UndeploySummary_UNDEPLOY,
   817  		},
   818  		{
   819  			Name:    "One Env with normal version",
   820  			AppName: "foo",
   821  			groups: groupFromEnvs([]*api.Environment{
   822  				{
   823  					Applications: map[string]*api.Environment_Application{
   824  						"foo": {
   825  							UndeployVersion: false,
   826  							Version:         666,
   827  						},
   828  					},
   829  				},
   830  			}),
   831  			ExpectedResult: api.UndeploySummary_NORMAL,
   832  		},
   833  		{
   834  			Name:    "Two Envs all undeploy",
   835  			AppName: "foo",
   836  			groups: groupFromEnvs([]*api.Environment{
   837  				{
   838  					Applications: map[string]*api.Environment_Application{
   839  						"foo": {
   840  							UndeployVersion: true,
   841  							Version:         666,
   842  						},
   843  					},
   844  				},
   845  				{
   846  					Applications: map[string]*api.Environment_Application{
   847  						"foo": {
   848  							UndeployVersion: true,
   849  							Version:         666,
   850  						},
   851  					},
   852  				},
   853  			}),
   854  			ExpectedResult: api.UndeploySummary_UNDEPLOY,
   855  		},
   856  		{
   857  			Name:    "Two Envs all normal",
   858  			AppName: "foo",
   859  			groups: groupFromEnvs([]*api.Environment{
   860  				{
   861  					Applications: map[string]*api.Environment_Application{
   862  						"foo": {
   863  							UndeployVersion: false,
   864  							Version:         666,
   865  						},
   866  					},
   867  				},
   868  				{
   869  					Applications: map[string]*api.Environment_Application{
   870  						"foo": {
   871  							UndeployVersion: false,
   872  							Version:         666,
   873  						},
   874  					},
   875  				},
   876  			}),
   877  			ExpectedResult: api.UndeploySummary_NORMAL,
   878  		},
   879  		{
   880  			Name:    "Two Envs all different",
   881  			AppName: "foo",
   882  			groups: groupFromEnvs([]*api.Environment{
   883  				{
   884  					Applications: map[string]*api.Environment_Application{
   885  						"foo": {
   886  							UndeployVersion: true,
   887  							Version:         666,
   888  						},
   889  					},
   890  				},
   891  				{
   892  					Applications: map[string]*api.Environment_Application{
   893  						"foo": {
   894  							UndeployVersion: false,
   895  							Version:         666,
   896  						},
   897  					},
   898  				},
   899  			}),
   900  			ExpectedResult: api.UndeploySummary_MIXED,
   901  		},
   902  	}
   903  	for _, tc := range tcs {
   904  		t.Run(tc.Name, func(t *testing.T) {
   905  			actualResult := deriveUndeploySummary(tc.AppName, tc.groups)
   906  			if !cmp.Equal(tc.ExpectedResult, actualResult) {
   907  				t.Fatal("Output mismatch (-want +got):\n", cmp.Diff(tc.ExpectedResult, actualResult))
   908  			}
   909  		})
   910  	}
   911  }