Create Proper Migration Tests (#15116)
	
		
	
				
					
				
			* Create Proper Migration tests Unfortunately our testing regime has so far meant that migrations do not get proper testing. This PR begins the process of creating migration tests for this. * Add test for v176 * fix mssql drop db Signed-off-by: Andrew Thornton <art27@cantab.net>tokarchuk/v1.17
							parent
							
								
									750ac52db2
								
							
						
					
					
						commit
						39ef6f83d5
					
				@ -0,0 +1,29 @@ | 
				
			|||||||
 | 
					# Issue_Label 1 should not be deleted | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 1 | 
				
			||||||
 | 
					  issue_id: 1 | 
				
			||||||
 | 
					  label_id: 1 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Issue_label 2 should be deleted | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 2 | 
				
			||||||
 | 
					  issue_id: 5 | 
				
			||||||
 | 
					  label_id: 99 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Issue_Label 3 should not be deleted | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 3 | 
				
			||||||
 | 
					  issue_id: 2 | 
				
			||||||
 | 
					  label_id: 1 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Issue_Label 4 should not be deleted | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 4 | 
				
			||||||
 | 
					  issue_id: 2 | 
				
			||||||
 | 
					  label_id: 4 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 5 | 
				
			||||||
 | 
					  issue_id: 2 | 
				
			||||||
 | 
					  label_id: 87 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,43 @@ | 
				
			|||||||
 | 
					- | 
				
			||||||
 | 
					  id: 1 | 
				
			||||||
 | 
					  repo_id: 1 | 
				
			||||||
 | 
					  org_id: 0 | 
				
			||||||
 | 
					  name: label1 | 
				
			||||||
 | 
					  color: '#abcdef' | 
				
			||||||
 | 
					  num_issues: 2 | 
				
			||||||
 | 
					  num_closed_issues: 0 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 2 | 
				
			||||||
 | 
					  repo_id: 1 | 
				
			||||||
 | 
					  org_id: 0 | 
				
			||||||
 | 
					  name: label2 | 
				
			||||||
 | 
					  color: '#000000' | 
				
			||||||
 | 
					  num_issues: 1 | 
				
			||||||
 | 
					  num_closed_issues: 1 | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 3 | 
				
			||||||
 | 
					  repo_id: 0 | 
				
			||||||
 | 
					  org_id:  3 | 
				
			||||||
 | 
					  name: orglabel3 | 
				
			||||||
 | 
					  color: '#abcdef' | 
				
			||||||
 | 
					  num_issues: 0 | 
				
			||||||
 | 
					  num_closed_issues: 0 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 4 | 
				
			||||||
 | 
					  repo_id: 0 | 
				
			||||||
 | 
					  org_id: 3 | 
				
			||||||
 | 
					  name: orglabel4 | 
				
			||||||
 | 
					  color: '#000000' | 
				
			||||||
 | 
					  num_issues: 1 | 
				
			||||||
 | 
					  num_closed_issues: 0 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 5 | 
				
			||||||
 | 
					  repo_id: 10 | 
				
			||||||
 | 
					  org_id: 0 | 
				
			||||||
 | 
					  name: pull-test-label | 
				
			||||||
 | 
					  color: '#000000' | 
				
			||||||
 | 
					  num_issues: 0 | 
				
			||||||
 | 
					  num_closed_issues: 0 | 
				
			||||||
@ -0,0 +1,52 @@ | 
				
			|||||||
 | 
					# type Comment struct { | 
				
			||||||
 | 
					#   ID      int64 `xorm:"pk autoincr"` | 
				
			||||||
 | 
					#   Type    int   `xorm:"INDEX"` | 
				
			||||||
 | 
					#   IssueID int64 `xorm:"INDEX"` | 
				
			||||||
 | 
					#   LabelID int64 | 
				
			||||||
 | 
					# } | 
				
			||||||
 | 
					# | 
				
			||||||
 | 
					# we are only interested in type 7 | 
				
			||||||
 | 
					# | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 1 # Should remain | 
				
			||||||
 | 
					  type: 6 | 
				
			||||||
 | 
					  issue_id: 1 | 
				
			||||||
 | 
					  label_id: 0 | 
				
			||||||
 | 
					  should_remain: true | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 2 # Should remain | 
				
			||||||
 | 
					  type: 7 | 
				
			||||||
 | 
					  issue_id: 1 # repo_id: 1 | 
				
			||||||
 | 
					  label_id: 1 # repo_id: 1 | 
				
			||||||
 | 
					  should_remain: true | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 3 # Should remain | 
				
			||||||
 | 
					  type: 7 | 
				
			||||||
 | 
					  issue_id: 2 # repo_id: 2 owner_id: 1 | 
				
			||||||
 | 
					  label_id: 2 # org_id: 1 | 
				
			||||||
 | 
					  should_remain: true | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 4 # Should be DELETED | 
				
			||||||
 | 
					  type: 7 | 
				
			||||||
 | 
					  issue_id: 1 # repo_id: 1 | 
				
			||||||
 | 
					  label_id: 3 # repo_id: 2 | 
				
			||||||
 | 
					  should_remain: false | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 5 # Should remain | 
				
			||||||
 | 
					  type: 7 | 
				
			||||||
 | 
					  issue_id: 3 # repo_id: 1 | 
				
			||||||
 | 
					  label_id: 1 # repo_id: 1 | 
				
			||||||
 | 
					  should_remain: true | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 6 # Should be DELETED | 
				
			||||||
 | 
					  type: 7 | 
				
			||||||
 | 
					  issue_id: 3 # repo_id: 1 owner_id: 2 | 
				
			||||||
 | 
					  label_id: 2 # org_id: 1 | 
				
			||||||
 | 
					  should_remain: false | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 7 # Should be DELETED | 
				
			||||||
 | 
					  type: 7 | 
				
			||||||
 | 
					  issue_id: 3 # repo_id: 1 owner_id: 2 | 
				
			||||||
 | 
					  label_id: 5 # repo_id: 3 | 
				
			||||||
 | 
					  should_remain: false | 
				
			||||||
@ -0,0 +1,21 @@ | 
				
			|||||||
 | 
					# type Issue struct { | 
				
			||||||
 | 
					#   ID     int64 `xorm:"pk autoincr"` | 
				
			||||||
 | 
					#   RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"` | 
				
			||||||
 | 
					#   Index  int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository. | 
				
			||||||
 | 
					# } | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 1 | 
				
			||||||
 | 
					  repo_id: 1 | 
				
			||||||
 | 
					  index: 1 | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 2 | 
				
			||||||
 | 
					  repo_id: 2 | 
				
			||||||
 | 
					  index: 1 | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 3 | 
				
			||||||
 | 
					  repo_id: 1 | 
				
			||||||
 | 
					  index: 2 | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 4 | 
				
			||||||
 | 
					  repo_id: 3 | 
				
			||||||
 | 
					  index: 1 | 
				
			||||||
@ -0,0 +1,35 @@ | 
				
			|||||||
 | 
					# type IssueLabel struct { | 
				
			||||||
 | 
					#   ID      int64 `xorm:"pk autoincr"` | 
				
			||||||
 | 
					#   IssueID int64 `xorm:"UNIQUE(s)"` | 
				
			||||||
 | 
					#   LabelID int64 `xorm:"UNIQUE(s)"` | 
				
			||||||
 | 
					# } | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 1 # Should remain - matches comment 2 | 
				
			||||||
 | 
					  issue_id: 1 | 
				
			||||||
 | 
					  label_id: 1 | 
				
			||||||
 | 
					  should_remain: true | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 2 # Should remain | 
				
			||||||
 | 
					  issue_id: 2 | 
				
			||||||
 | 
					  label_id: 2 | 
				
			||||||
 | 
					  should_remain: true | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 3 # Should be deleted | 
				
			||||||
 | 
					  issue_id: 1 | 
				
			||||||
 | 
					  label_id: 3 | 
				
			||||||
 | 
					  should_remain: false | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 4 # Should remain | 
				
			||||||
 | 
					  issue_id: 3 | 
				
			||||||
 | 
					  label_id: 1 | 
				
			||||||
 | 
					  should_remain: true | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 5 # Should be deleted | 
				
			||||||
 | 
					  issue_id: 3 | 
				
			||||||
 | 
					  label_id: 2 | 
				
			||||||
 | 
					  should_remain: false | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 6 # Should be deleted | 
				
			||||||
 | 
					  issue_id: 3 | 
				
			||||||
 | 
					  label_id: 5 | 
				
			||||||
 | 
					  should_remain: false | 
				
			||||||
@ -0,0 +1,26 @@ | 
				
			|||||||
 | 
					# type Label struct { | 
				
			||||||
 | 
					#   ID     int64 `xorm:"pk autoincr"` | 
				
			||||||
 | 
					#   RepoID int64 `xorm:"INDEX"` | 
				
			||||||
 | 
					#   OrgID  int64 `xorm:"INDEX"` | 
				
			||||||
 | 
					# } | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 1 | 
				
			||||||
 | 
					  repo_id: 1 | 
				
			||||||
 | 
					  org_id: 0 | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 2 | 
				
			||||||
 | 
					  repo_id: 0 | 
				
			||||||
 | 
					  org_id: 1 | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 3 | 
				
			||||||
 | 
					  repo_id: 2 | 
				
			||||||
 | 
					  org_id: 0 | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 4 | 
				
			||||||
 | 
					  repo_id: 1 | 
				
			||||||
 | 
					  org_id: 0 | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 5 | 
				
			||||||
 | 
					  repo_id: 3 | 
				
			||||||
 | 
					  org_id: 0 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,17 @@ | 
				
			|||||||
 | 
					# type Repository struct { | 
				
			||||||
 | 
					#   ID        int64  `xorm:"pk autoincr"` | 
				
			||||||
 | 
					#   OwnerID   int64  `xorm:"UNIQUE(s) index"` | 
				
			||||||
 | 
					#   LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` | 
				
			||||||
 | 
					# } | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 1 | 
				
			||||||
 | 
					  owner_id: 2 | 
				
			||||||
 | 
					  lower_name: "repo1" | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 2 | 
				
			||||||
 | 
					  owner_id: 1 | 
				
			||||||
 | 
					  lower_name: "repo2" | 
				
			||||||
 | 
					- | 
				
			||||||
 | 
					  id: 3 | 
				
			||||||
 | 
					  owner_id: 2 | 
				
			||||||
 | 
					  lower_name: "repo3" | 
				
			||||||
@ -0,0 +1,338 @@ | 
				
			|||||||
 | 
					// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package migrations | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ( | 
				
			||||||
 | 
						"database/sql" | 
				
			||||||
 | 
						"fmt" | 
				
			||||||
 | 
						"os" | 
				
			||||||
 | 
						"path" | 
				
			||||||
 | 
						"path/filepath" | 
				
			||||||
 | 
						"runtime" | 
				
			||||||
 | 
						"testing" | 
				
			||||||
 | 
						"time" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models" | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/base" | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting" | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/timeutil" | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util" | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert" | 
				
			||||||
 | 
						"github.com/unknwon/com" | 
				
			||||||
 | 
						"xorm.io/xorm" | 
				
			||||||
 | 
						"xorm.io/xorm/names" | 
				
			||||||
 | 
					) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestMain(m *testing.M) { | 
				
			||||||
 | 
						giteaRoot := base.SetupGiteaRoot() | 
				
			||||||
 | 
						if giteaRoot == "" { | 
				
			||||||
 | 
							fmt.Println("Environment variable $GITEA_ROOT not set") | 
				
			||||||
 | 
							os.Exit(1) | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						giteaBinary := "gitea" | 
				
			||||||
 | 
						if runtime.GOOS == "windows" { | 
				
			||||||
 | 
							giteaBinary += ".exe" | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						setting.AppPath = path.Join(giteaRoot, giteaBinary) | 
				
			||||||
 | 
						if _, err := os.Stat(setting.AppPath); err != nil { | 
				
			||||||
 | 
							fmt.Printf("Could not find gitea binary at %s\n", setting.AppPath) | 
				
			||||||
 | 
							os.Exit(1) | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						giteaConf := os.Getenv("GITEA_CONF") | 
				
			||||||
 | 
						if giteaConf == "" { | 
				
			||||||
 | 
							giteaConf = path.Join(filepath.Dir(setting.AppPath), "integrations/sqlite.ini") | 
				
			||||||
 | 
							fmt.Printf("Environment variable $GITEA_CONF not set - defaulting to %s\n", giteaConf) | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !path.IsAbs(giteaConf) { | 
				
			||||||
 | 
							setting.CustomConf = path.Join(giteaRoot, giteaConf) | 
				
			||||||
 | 
						} else { | 
				
			||||||
 | 
							setting.CustomConf = giteaConf | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						setting.SetCustomPathAndConf("", "", "") | 
				
			||||||
 | 
						setting.NewContext() | 
				
			||||||
 | 
						setting.CheckLFSVersion() | 
				
			||||||
 | 
						setting.InitDBConfig() | 
				
			||||||
 | 
						setting.NewLogServices(true) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						exitStatus := m.Run() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := removeAllWithRetry(setting.RepoRootPath); err != nil { | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err) | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						if err := removeAllWithRetry(setting.AppDataPath); err != nil { | 
				
			||||||
 | 
							fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err) | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						os.Exit(exitStatus) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func removeAllWithRetry(dir string) error { | 
				
			||||||
 | 
						var err error | 
				
			||||||
 | 
						for i := 0; i < 20; i++ { | 
				
			||||||
 | 
							err = os.RemoveAll(dir) | 
				
			||||||
 | 
							if err == nil { | 
				
			||||||
 | 
								break | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							time.Sleep(100 * time.Millisecond) | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						return err | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetEngine sets the xorm.Engine
 | 
				
			||||||
 | 
					func SetEngine() (*xorm.Engine, error) { | 
				
			||||||
 | 
						x, err := models.GetNewEngine() | 
				
			||||||
 | 
						if err != nil { | 
				
			||||||
 | 
							return x, fmt.Errorf("Failed to connect to database: %v", err) | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						x.SetMapper(names.GonicMapper{}) | 
				
			||||||
 | 
						// WARNING: for serv command, MUST remove the output to os.stdout,
 | 
				
			||||||
 | 
						// so use log file to instead print to stdout.
 | 
				
			||||||
 | 
						x.SetLogger(models.NewXORMLogger(setting.Database.LogSQL)) | 
				
			||||||
 | 
						x.ShowSQL(setting.Database.LogSQL) | 
				
			||||||
 | 
						x.SetMaxOpenConns(setting.Database.MaxOpenConns) | 
				
			||||||
 | 
						x.SetMaxIdleConns(setting.Database.MaxIdleConns) | 
				
			||||||
 | 
						x.SetConnMaxLifetime(setting.Database.ConnMaxLifetime) | 
				
			||||||
 | 
						return x, nil | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func deleteDB() error { | 
				
			||||||
 | 
						switch { | 
				
			||||||
 | 
						case setting.Database.UseSQLite3: | 
				
			||||||
 | 
							if err := util.Remove(setting.Database.Path); err != nil { | 
				
			||||||
 | 
								return err | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							return os.MkdirAll(path.Dir(setting.Database.Path), os.ModePerm) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case setting.Database.UseMySQL: | 
				
			||||||
 | 
							db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/", | 
				
			||||||
 | 
								setting.Database.User, setting.Database.Passwd, setting.Database.Host)) | 
				
			||||||
 | 
							if err != nil { | 
				
			||||||
 | 
								return err | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							defer db.Close() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)); err != nil { | 
				
			||||||
 | 
								return err | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name)); err != nil { | 
				
			||||||
 | 
								return err | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							return nil | 
				
			||||||
 | 
						case setting.Database.UsePostgreSQL: | 
				
			||||||
 | 
							db, err := sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/?sslmode=%s", | 
				
			||||||
 | 
								setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.SSLMode)) | 
				
			||||||
 | 
							if err != nil { | 
				
			||||||
 | 
								return err | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							defer db.Close() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)); err != nil { | 
				
			||||||
 | 
								return err | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)); err != nil { | 
				
			||||||
 | 
								return err | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							db.Close() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check if we need to setup a specific schema
 | 
				
			||||||
 | 
							if len(setting.Database.Schema) != 0 { | 
				
			||||||
 | 
								db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", | 
				
			||||||
 | 
									setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode)) | 
				
			||||||
 | 
								if err != nil { | 
				
			||||||
 | 
									return err | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
								defer db.Close() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema)) | 
				
			||||||
 | 
								if err != nil { | 
				
			||||||
 | 
									return err | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
								defer schrows.Close() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if !schrows.Next() { | 
				
			||||||
 | 
									// Create and setup a DB schema
 | 
				
			||||||
 | 
									_, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s", setting.Database.Schema)) | 
				
			||||||
 | 
									if err != nil { | 
				
			||||||
 | 
										return err | 
				
			||||||
 | 
									} | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Make the user's default search path the created schema; this will affect new connections
 | 
				
			||||||
 | 
								_, err = db.Exec(fmt.Sprintf(`ALTER USER "%s" SET search_path = %s`, setting.Database.User, setting.Database.Schema)) | 
				
			||||||
 | 
								if err != nil { | 
				
			||||||
 | 
									return err | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
								return nil | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
						case setting.Database.UseMSSQL: | 
				
			||||||
 | 
							host, port := setting.ParseMSSQLHostPort(setting.Database.Host) | 
				
			||||||
 | 
							db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", | 
				
			||||||
 | 
								host, port, "master", setting.Database.User, setting.Database.Passwd)) | 
				
			||||||
 | 
							if err != nil { | 
				
			||||||
 | 
								return err | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							defer db.Close() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS [%s]", setting.Database.Name)); err != nil { | 
				
			||||||
 | 
								return err | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE [%s]", setting.Database.Name)); err != nil { | 
				
			||||||
 | 
								return err | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// prepareTestEnv prepares the test environment and reset the database. The skip parameter should usually be 0.
 | 
				
			||||||
 | 
					// Provide models to be sync'd with the database - in particular any models you expect fixtures to be loaded from.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// fixtures in `models/migrations/fixtures/<TestName>` will be loaded automatically
 | 
				
			||||||
 | 
					func prepareTestEnv(t *testing.T, skip int, syncModels ...interface{}) (*xorm.Engine, func()) { | 
				
			||||||
 | 
						t.Helper() | 
				
			||||||
 | 
						ourSkip := 2 | 
				
			||||||
 | 
						ourSkip += skip | 
				
			||||||
 | 
						deferFn := PrintCurrentTest(t, ourSkip) | 
				
			||||||
 | 
						assert.NoError(t, os.RemoveAll(setting.RepoRootPath)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.NoError(t, com.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), | 
				
			||||||
 | 
							setting.RepoRootPath)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := deleteDB(); err != nil { | 
				
			||||||
 | 
							t.Errorf("unable to reset database: %v", err) | 
				
			||||||
 | 
							return nil, deferFn | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						x, err := SetEngine() | 
				
			||||||
 | 
						assert.NoError(t, err) | 
				
			||||||
 | 
						if x != nil { | 
				
			||||||
 | 
							oldDefer := deferFn | 
				
			||||||
 | 
							deferFn = func() { | 
				
			||||||
 | 
								oldDefer() | 
				
			||||||
 | 
								if err := x.Close(); err != nil { | 
				
			||||||
 | 
									t.Errorf("error during close: %v", err) | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						if err != nil { | 
				
			||||||
 | 
							return x, deferFn | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(syncModels) > 0 { | 
				
			||||||
 | 
							if err := x.Sync2(syncModels...); err != nil { | 
				
			||||||
 | 
								t.Errorf("error during sync: %v", err) | 
				
			||||||
 | 
								return x, deferFn | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fixturesDir := filepath.Join(filepath.Dir(setting.AppPath), "models", "migrations", "fixtures", t.Name()) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err := os.Stat(fixturesDir); err == nil { | 
				
			||||||
 | 
							t.Logf("initializing fixtures from: %s", fixturesDir) | 
				
			||||||
 | 
							if err := models.InitFixtures(fixturesDir, x); err != nil { | 
				
			||||||
 | 
								t.Errorf("error whilst initializing fixtures from %s: %v", fixturesDir, err) | 
				
			||||||
 | 
								return x, deferFn | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							if err := models.LoadFixtures(x); err != nil { | 
				
			||||||
 | 
								t.Errorf("error whilst loading fixtures from %s: %v", fixturesDir, err) | 
				
			||||||
 | 
								return x, deferFn | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
						} else if !os.IsNotExist(err) { | 
				
			||||||
 | 
							t.Errorf("unexpected error whilst checking for existence of fixtures: %v", err) | 
				
			||||||
 | 
						} else { | 
				
			||||||
 | 
							t.Logf("no fixtures found in: %s", fixturesDir) | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return x, deferFn | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_dropTableColumns(t *testing.T) { | 
				
			||||||
 | 
						x, deferable := prepareTestEnv(t, 0) | 
				
			||||||
 | 
						if x == nil || t.Failed() { | 
				
			||||||
 | 
							defer deferable() | 
				
			||||||
 | 
							return | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						defer deferable() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type DropTest struct { | 
				
			||||||
 | 
							ID            int64 `xorm:"pk autoincr"` | 
				
			||||||
 | 
							FirstColumn   string | 
				
			||||||
 | 
							ToDropColumn  string `xorm:"unique"` | 
				
			||||||
 | 
							AnotherColumn int64 | 
				
			||||||
 | 
							CreatedUnix   timeutil.TimeStamp `xorm:"INDEX created"` | 
				
			||||||
 | 
							UpdatedUnix   timeutil.TimeStamp `xorm:"INDEX updated"` | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						columns := []string{ | 
				
			||||||
 | 
							"first_column", | 
				
			||||||
 | 
							"to_drop_column", | 
				
			||||||
 | 
							"another_column", | 
				
			||||||
 | 
							"created_unix", | 
				
			||||||
 | 
							"updated_unix", | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i := range columns { | 
				
			||||||
 | 
							x.SetMapper(names.GonicMapper{}) | 
				
			||||||
 | 
							if err := x.Sync2(new(DropTest)); err != nil { | 
				
			||||||
 | 
								t.Errorf("unable to create DropTest table: %v", err) | 
				
			||||||
 | 
								return | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							sess := x.NewSession() | 
				
			||||||
 | 
							if err := sess.Begin(); err != nil { | 
				
			||||||
 | 
								sess.Close() | 
				
			||||||
 | 
								t.Errorf("unable to begin transaction: %v", err) | 
				
			||||||
 | 
								return | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							if err := dropTableColumns(sess, "drop_test", columns[i:]...); err != nil { | 
				
			||||||
 | 
								sess.Close() | 
				
			||||||
 | 
								t.Errorf("Unable to drop columns[%d:]: %s from drop_test: %v", i, columns[i:], err) | 
				
			||||||
 | 
								return | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							if err := sess.Commit(); err != nil { | 
				
			||||||
 | 
								sess.Close() | 
				
			||||||
 | 
								t.Errorf("unable to commit transaction: %v", err) | 
				
			||||||
 | 
								return | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							sess.Close() | 
				
			||||||
 | 
							if err := x.DropTables(new(DropTest)); err != nil { | 
				
			||||||
 | 
								t.Errorf("unable to drop table: %v", err) | 
				
			||||||
 | 
								return | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							for j := range columns[i+1:] { | 
				
			||||||
 | 
								x.SetMapper(names.GonicMapper{}) | 
				
			||||||
 | 
								if err := x.Sync2(new(DropTest)); err != nil { | 
				
			||||||
 | 
									t.Errorf("unable to create DropTest table: %v", err) | 
				
			||||||
 | 
									return | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
								dropcols := append([]string{columns[i]}, columns[j+i+1:]...) | 
				
			||||||
 | 
								sess := x.NewSession() | 
				
			||||||
 | 
								if err := sess.Begin(); err != nil { | 
				
			||||||
 | 
									sess.Close() | 
				
			||||||
 | 
									t.Errorf("unable to begin transaction: %v", err) | 
				
			||||||
 | 
									return | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
								if err := dropTableColumns(sess, "drop_test", dropcols...); err != nil { | 
				
			||||||
 | 
									sess.Close() | 
				
			||||||
 | 
									t.Errorf("Unable to drop columns: %s from drop_test: %v", dropcols, err) | 
				
			||||||
 | 
									return | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
								if err := sess.Commit(); err != nil { | 
				
			||||||
 | 
									sess.Close() | 
				
			||||||
 | 
									t.Errorf("unable to commit transaction: %v", err) | 
				
			||||||
 | 
									return | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
								sess.Close() | 
				
			||||||
 | 
								if err := x.DropTables(new(DropTest)); err != nil { | 
				
			||||||
 | 
									t.Errorf("unable to drop table: %v", err) | 
				
			||||||
 | 
									return | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,188 @@ | 
				
			|||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package migrations | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ( | 
				
			||||||
 | 
						"context" | 
				
			||||||
 | 
						"fmt" | 
				
			||||||
 | 
						"os" | 
				
			||||||
 | 
						"runtime" | 
				
			||||||
 | 
						"strings" | 
				
			||||||
 | 
						"sync" | 
				
			||||||
 | 
						"testing" | 
				
			||||||
 | 
						"time" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/log" | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/queue" | 
				
			||||||
 | 
						jsoniter "github.com/json-iterator/go" | 
				
			||||||
 | 
					) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var ( | 
				
			||||||
 | 
						prefix    string | 
				
			||||||
 | 
						slowTest  = 10 * time.Second | 
				
			||||||
 | 
						slowFlush = 5 * time.Second | 
				
			||||||
 | 
					) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestLogger is a logger which will write to the testing log
 | 
				
			||||||
 | 
					type TestLogger struct { | 
				
			||||||
 | 
						log.WriterLogger | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var writerCloser = &testLoggerWriterCloser{} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type testLoggerWriterCloser struct { | 
				
			||||||
 | 
						sync.RWMutex | 
				
			||||||
 | 
						t []*testing.TB | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (w *testLoggerWriterCloser) setT(t *testing.TB) { | 
				
			||||||
 | 
						w.Lock() | 
				
			||||||
 | 
						w.t = append(w.t, t) | 
				
			||||||
 | 
						w.Unlock() | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (w *testLoggerWriterCloser) Write(p []byte) (int, error) { | 
				
			||||||
 | 
						w.RLock() | 
				
			||||||
 | 
						var t *testing.TB | 
				
			||||||
 | 
						if len(w.t) > 0 { | 
				
			||||||
 | 
							t = w.t[len(w.t)-1] | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						w.RUnlock() | 
				
			||||||
 | 
						if t != nil && *t != nil { | 
				
			||||||
 | 
							if len(p) > 0 && p[len(p)-1] == '\n' { | 
				
			||||||
 | 
								p = p[:len(p)-1] | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							defer func() { | 
				
			||||||
 | 
								err := recover() | 
				
			||||||
 | 
								if err == nil { | 
				
			||||||
 | 
									return | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
								var errString string | 
				
			||||||
 | 
								errErr, ok := err.(error) | 
				
			||||||
 | 
								if ok { | 
				
			||||||
 | 
									errString = errErr.Error() | 
				
			||||||
 | 
								} else { | 
				
			||||||
 | 
									errString, ok = err.(string) | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
								if !ok { | 
				
			||||||
 | 
									panic(err) | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
								if !strings.HasPrefix(errString, "Log in goroutine after ") { | 
				
			||||||
 | 
									panic(err) | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
							}() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							(*t).Log(string(p)) | 
				
			||||||
 | 
							return len(p), nil | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						return len(p), nil | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (w *testLoggerWriterCloser) Close() error { | 
				
			||||||
 | 
						w.Lock() | 
				
			||||||
 | 
						if len(w.t) > 0 { | 
				
			||||||
 | 
							w.t = w.t[:len(w.t)-1] | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						w.Unlock() | 
				
			||||||
 | 
						return nil | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PrintCurrentTest prints the current test to os.Stdout
 | 
				
			||||||
 | 
					func PrintCurrentTest(t testing.TB, skip ...int) func() { | 
				
			||||||
 | 
						start := time.Now() | 
				
			||||||
 | 
						actualSkip := 1 | 
				
			||||||
 | 
						if len(skip) > 0 { | 
				
			||||||
 | 
							actualSkip = skip[0] | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						_, filename, line, _ := runtime.Caller(actualSkip) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if log.CanColorStdout { | 
				
			||||||
 | 
							fmt.Fprintf(os.Stdout, "=== %s (%s:%d)\n", fmt.Formatter(log.NewColoredValue(t.Name())), strings.TrimPrefix(filename, prefix), line) | 
				
			||||||
 | 
						} else { | 
				
			||||||
 | 
							fmt.Fprintf(os.Stdout, "=== %s (%s:%d)\n", t.Name(), strings.TrimPrefix(filename, prefix), line) | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						writerCloser.setT(&t) | 
				
			||||||
 | 
						return func() { | 
				
			||||||
 | 
							took := time.Since(start) | 
				
			||||||
 | 
							if took > slowTest { | 
				
			||||||
 | 
								if log.CanColorStdout { | 
				
			||||||
 | 
									fmt.Fprintf(os.Stdout, "+++ %s is a slow test (took %v)\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgYellow)), fmt.Formatter(log.NewColoredValue(took, log.Bold, log.FgYellow))) | 
				
			||||||
 | 
								} else { | 
				
			||||||
 | 
									fmt.Fprintf(os.Stdout, "+++ %s is a slow tets (took %v)\n", t.Name(), took) | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							timer := time.AfterFunc(slowFlush, func() { | 
				
			||||||
 | 
								if log.CanColorStdout { | 
				
			||||||
 | 
									fmt.Fprintf(os.Stdout, "+++ %s ... still flushing after %v ...\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgRed)), slowFlush) | 
				
			||||||
 | 
								} else { | 
				
			||||||
 | 
									fmt.Fprintf(os.Stdout, "+++ %s ... still flushing after %v ...\n", t.Name(), slowFlush) | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
							}) | 
				
			||||||
 | 
							if err := queue.GetManager().FlushAll(context.Background(), -1); err != nil { | 
				
			||||||
 | 
								t.Errorf("Flushing queues failed with error %v", err) | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							timer.Stop() | 
				
			||||||
 | 
							flushTook := time.Since(start) - took | 
				
			||||||
 | 
							if flushTook > slowFlush { | 
				
			||||||
 | 
								if log.CanColorStdout { | 
				
			||||||
 | 
									fmt.Fprintf(os.Stdout, "+++ %s had a slow clean-up flush (took %v)\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgRed)), fmt.Formatter(log.NewColoredValue(flushTook, log.Bold, log.FgRed))) | 
				
			||||||
 | 
								} else { | 
				
			||||||
 | 
									fmt.Fprintf(os.Stdout, "+++ %s had a slow clean-up flush (took %v)\n", t.Name(), flushTook) | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
							_ = writerCloser.Close() | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Printf takes a format and args and prints the string to os.Stdout
 | 
				
			||||||
 | 
					func Printf(format string, args ...interface{}) { | 
				
			||||||
 | 
						if log.CanColorStdout { | 
				
			||||||
 | 
							for i := 0; i < len(args); i++ { | 
				
			||||||
 | 
								args[i] = log.NewColoredValue(args[i]) | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						fmt.Fprintf(os.Stdout, "\t"+format, args...) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewTestLogger creates a TestLogger as a log.LoggerProvider
 | 
				
			||||||
 | 
					func NewTestLogger() log.LoggerProvider { | 
				
			||||||
 | 
						logger := &TestLogger{} | 
				
			||||||
 | 
						logger.Colorize = log.CanColorStdout | 
				
			||||||
 | 
						logger.Level = log.TRACE | 
				
			||||||
 | 
						return logger | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Init inits connection writer with json config.
 | 
				
			||||||
 | 
					// json config only need key "level".
 | 
				
			||||||
 | 
					func (log *TestLogger) Init(config string) error { | 
				
			||||||
 | 
						json := jsoniter.ConfigCompatibleWithStandardLibrary | 
				
			||||||
 | 
						err := json.Unmarshal([]byte(config), log) | 
				
			||||||
 | 
						if err != nil { | 
				
			||||||
 | 
							return err | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						log.NewWriterLogger(writerCloser) | 
				
			||||||
 | 
						return nil | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Flush when log should be flushed
 | 
				
			||||||
 | 
					func (log *TestLogger) Flush() { | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//ReleaseReopen does nothing
 | 
				
			||||||
 | 
					func (log *TestLogger) ReleaseReopen() error { | 
				
			||||||
 | 
						return nil | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetName returns the default name for this implementation
 | 
				
			||||||
 | 
					func (log *TestLogger) GetName() string { | 
				
			||||||
 | 
						return "test" | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() { | 
				
			||||||
 | 
						log.Register("test", NewTestLogger) | 
				
			||||||
 | 
						_, filename, _, _ := runtime.Caller(0) | 
				
			||||||
 | 
						prefix = strings.TrimSuffix(filename, "integrations/testlogger.go") | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,128 @@ | 
				
			|||||||
 | 
					// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package migrations | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ( | 
				
			||||||
 | 
						"testing" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert" | 
				
			||||||
 | 
					) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_removeInvalidLabels(t *testing.T) { | 
				
			||||||
 | 
						// Models used by the migration
 | 
				
			||||||
 | 
						type Comment struct { | 
				
			||||||
 | 
							ID           int64 `xorm:"pk autoincr"` | 
				
			||||||
 | 
							Type         int   `xorm:"INDEX"` | 
				
			||||||
 | 
							IssueID      int64 `xorm:"INDEX"` | 
				
			||||||
 | 
							LabelID      int64 | 
				
			||||||
 | 
							ShouldRemain bool // <- Flag for testing the migration
 | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type Issue struct { | 
				
			||||||
 | 
							ID     int64 `xorm:"pk autoincr"` | 
				
			||||||
 | 
							RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"` | 
				
			||||||
 | 
							Index  int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
 | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type Repository struct { | 
				
			||||||
 | 
							ID        int64  `xorm:"pk autoincr"` | 
				
			||||||
 | 
							OwnerID   int64  `xorm:"UNIQUE(s) index"` | 
				
			||||||
 | 
							LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type Label struct { | 
				
			||||||
 | 
							ID     int64 `xorm:"pk autoincr"` | 
				
			||||||
 | 
							RepoID int64 `xorm:"INDEX"` | 
				
			||||||
 | 
							OrgID  int64 `xorm:"INDEX"` | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type IssueLabel struct { | 
				
			||||||
 | 
							ID           int64 `xorm:"pk autoincr"` | 
				
			||||||
 | 
							IssueID      int64 `xorm:"UNIQUE(s)"` | 
				
			||||||
 | 
							LabelID      int64 `xorm:"UNIQUE(s)"` | 
				
			||||||
 | 
							ShouldRemain bool  // <- Flag for testing the migration
 | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// load and prepare the test database
 | 
				
			||||||
 | 
						x, deferable := prepareTestEnv(t, 0, new(Comment), new(Issue), new(Repository), new(IssueLabel), new(Label)) | 
				
			||||||
 | 
						if x == nil || t.Failed() { | 
				
			||||||
 | 
							defer deferable() | 
				
			||||||
 | 
							return | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						defer deferable() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var issueLabels []*IssueLabel | 
				
			||||||
 | 
						ilPreMigration := map[int64]*IssueLabel{} | 
				
			||||||
 | 
						ilPostMigration := map[int64]*IssueLabel{} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var comments []*Comment | 
				
			||||||
 | 
						comPreMigration := map[int64]*Comment{} | 
				
			||||||
 | 
						comPostMigration := map[int64]*Comment{} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get pre migration values
 | 
				
			||||||
 | 
						if err := x.Find(&issueLabels); err != nil { | 
				
			||||||
 | 
							t.Errorf("Unable to find issueLabels: %v", err) | 
				
			||||||
 | 
							return | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						for _, issueLabel := range issueLabels { | 
				
			||||||
 | 
							ilPreMigration[issueLabel.ID] = issueLabel | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						if err := x.Find(&comments); err != nil { | 
				
			||||||
 | 
							t.Errorf("Unable to find comments: %v", err) | 
				
			||||||
 | 
							return | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						for _, comment := range comments { | 
				
			||||||
 | 
							comPreMigration[comment.ID] = comment | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Run the migration
 | 
				
			||||||
 | 
						if err := removeInvalidLabels(x); err != nil { | 
				
			||||||
 | 
							t.Errorf("unable to RemoveInvalidLabels: %v", err) | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get the post migration values
 | 
				
			||||||
 | 
						issueLabels = issueLabels[:0] | 
				
			||||||
 | 
						if err := x.Find(&issueLabels); err != nil { | 
				
			||||||
 | 
							t.Errorf("Unable to find issueLabels: %v", err) | 
				
			||||||
 | 
							return | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						for _, issueLabel := range issueLabels { | 
				
			||||||
 | 
							ilPostMigration[issueLabel.ID] = issueLabel | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						comments = comments[:0] | 
				
			||||||
 | 
						if err := x.Find(&comments); err != nil { | 
				
			||||||
 | 
							t.Errorf("Unable to find comments: %v", err) | 
				
			||||||
 | 
							return | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						for _, comment := range comments { | 
				
			||||||
 | 
							comPostMigration[comment.ID] = comment | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Finally test results of the migration
 | 
				
			||||||
 | 
						for id, comment := range comPreMigration { | 
				
			||||||
 | 
							post, ok := comPostMigration[id] | 
				
			||||||
 | 
							if ok { | 
				
			||||||
 | 
								if !comment.ShouldRemain { | 
				
			||||||
 | 
									t.Errorf("Comment[%d] remained but should have been deleted", id) | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
								assert.Equal(t, comment, post) | 
				
			||||||
 | 
							} else if comment.ShouldRemain { | 
				
			||||||
 | 
								t.Errorf("Comment[%d] was deleted but should have remained", id) | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for id, il := range ilPreMigration { | 
				
			||||||
 | 
							post, ok := ilPostMigration[id] | 
				
			||||||
 | 
							if ok { | 
				
			||||||
 | 
								if !il.ShouldRemain { | 
				
			||||||
 | 
									t.Errorf("IssueLabel[%d] remained but should have been deleted", id) | 
				
			||||||
 | 
								} | 
				
			||||||
 | 
								assert.Equal(t, il, post) | 
				
			||||||
 | 
							} else if il.ShouldRemain { | 
				
			||||||
 | 
								t.Errorf("IssueLabel[%d] was deleted but should have remained", id) | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,88 @@ | 
				
			|||||||
 | 
					// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package migrations | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ( | 
				
			||||||
 | 
						"testing" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/timeutil" | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert" | 
				
			||||||
 | 
					) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_deleteOrphanedIssueLabels(t *testing.T) { | 
				
			||||||
 | 
						// Create the models used in the migration
 | 
				
			||||||
 | 
						type IssueLabel struct { | 
				
			||||||
 | 
							ID      int64 `xorm:"pk autoincr"` | 
				
			||||||
 | 
							IssueID int64 `xorm:"UNIQUE(s)"` | 
				
			||||||
 | 
							LabelID int64 `xorm:"UNIQUE(s)"` | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type Label struct { | 
				
			||||||
 | 
							ID              int64 `xorm:"pk autoincr"` | 
				
			||||||
 | 
							RepoID          int64 `xorm:"INDEX"` | 
				
			||||||
 | 
							OrgID           int64 `xorm:"INDEX"` | 
				
			||||||
 | 
							Name            string | 
				
			||||||
 | 
							Description     string | 
				
			||||||
 | 
							Color           string `xorm:"VARCHAR(7)"` | 
				
			||||||
 | 
							NumIssues       int | 
				
			||||||
 | 
							NumClosedIssues int | 
				
			||||||
 | 
							CreatedUnix     timeutil.TimeStamp `xorm:"INDEX created"` | 
				
			||||||
 | 
							UpdatedUnix     timeutil.TimeStamp `xorm:"INDEX updated"` | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Prepare and load the testing database
 | 
				
			||||||
 | 
						x, deferable := prepareTestEnv(t, 0, new(IssueLabel), new(Label)) | 
				
			||||||
 | 
						if x == nil || t.Failed() { | 
				
			||||||
 | 
							defer deferable() | 
				
			||||||
 | 
							return | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						defer deferable() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var issueLabels []*IssueLabel | 
				
			||||||
 | 
						preMigration := map[int64]*IssueLabel{} | 
				
			||||||
 | 
						postMigration := map[int64]*IssueLabel{} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Load issue labels that exist in the database pre-migration
 | 
				
			||||||
 | 
						if err := x.Find(&issueLabels); err != nil { | 
				
			||||||
 | 
							assert.NoError(t, err) | 
				
			||||||
 | 
							return | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						for _, issueLabel := range issueLabels { | 
				
			||||||
 | 
							preMigration[issueLabel.ID] = issueLabel | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Run the migration
 | 
				
			||||||
 | 
						if err := deleteOrphanedIssueLabels(x); err != nil { | 
				
			||||||
 | 
							assert.NoError(t, err) | 
				
			||||||
 | 
							return | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Load the remaining issue-labels
 | 
				
			||||||
 | 
						issueLabels = issueLabels[:0] | 
				
			||||||
 | 
						if err := x.Find(&issueLabels); err != nil { | 
				
			||||||
 | 
							assert.NoError(t, err) | 
				
			||||||
 | 
							return | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						for _, issueLabel := range issueLabels { | 
				
			||||||
 | 
							postMigration[issueLabel.ID] = issueLabel | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now test what is left
 | 
				
			||||||
 | 
						if _, ok := postMigration[2]; ok { | 
				
			||||||
 | 
							t.Errorf("Orphaned Label[2] survived the migration") | 
				
			||||||
 | 
							return | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, ok := postMigration[5]; ok { | 
				
			||||||
 | 
							t.Errorf("Orphaned Label[5] survived the migration") | 
				
			||||||
 | 
							return | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for id, post := range postMigration { | 
				
			||||||
 | 
							pre := preMigration[id] | 
				
			||||||
 | 
							assert.Equal(t, pre, post, "migration changed issueLabel %d", id) | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue