code.gitea.io/gitea@v1.22.3/tests/integration/dump_restore_test.go (about) 1 // Copyright 2022 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package integration 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "net/url" 11 "os" 12 "path/filepath" 13 "reflect" 14 "strings" 15 "testing" 16 17 auth_model "code.gitea.io/gitea/models/auth" 18 repo_model "code.gitea.io/gitea/models/repo" 19 "code.gitea.io/gitea/models/unittest" 20 user_model "code.gitea.io/gitea/models/user" 21 base "code.gitea.io/gitea/modules/migration" 22 "code.gitea.io/gitea/modules/setting" 23 "code.gitea.io/gitea/modules/structs" 24 "code.gitea.io/gitea/modules/util" 25 "code.gitea.io/gitea/services/migrations" 26 27 "github.com/stretchr/testify/assert" 28 "gopkg.in/yaml.v3" 29 ) 30 31 func TestDumpRestore(t *testing.T) { 32 onGiteaRun(t, func(t *testing.T, u *url.URL) { 33 AllowLocalNetworks := setting.Migrations.AllowLocalNetworks 34 setting.Migrations.AllowLocalNetworks = true 35 AppVer := setting.AppVer 36 // Gitea SDK (go-sdk) need to parse the AppVer from server response, so we must set it to a valid version string. 37 setting.AppVer = "1.16.0" 38 defer func() { 39 setting.Migrations.AllowLocalNetworks = AllowLocalNetworks 40 setting.AppVer = AppVer 41 }() 42 43 assert.NoError(t, migrations.Init()) 44 45 reponame := "repo1" 46 47 basePath, err := os.MkdirTemp("", reponame) 48 assert.NoError(t, err) 49 defer util.RemoveAll(basePath) 50 51 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}) 52 repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) 53 session := loginUser(t, repoOwner.Name) 54 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeReadMisc) 55 56 // 57 // Phase 1: dump repo1 from the Gitea instance to the filesystem 58 // 59 60 ctx := context.Background() 61 opts := migrations.MigrateOptions{ 62 GitServiceType: structs.GiteaService, 63 Issues: true, 64 PullRequests: true, 65 Labels: true, 66 Milestones: true, 67 Comments: true, 68 AuthToken: token, 69 CloneAddr: repo.CloneLink().HTTPS, 70 RepoName: reponame, 71 } 72 err = migrations.DumpRepository(ctx, basePath, repoOwner.Name, opts) 73 assert.NoError(t, err) 74 75 // 76 // Verify desired side effects of the dump 77 // 78 d := filepath.Join(basePath, repo.OwnerName, repo.Name) 79 for _, f := range []string{"repo.yml", "topic.yml", "label.yml", "milestone.yml", "issue.yml"} { 80 assert.FileExists(t, filepath.Join(d, f)) 81 } 82 83 // 84 // Phase 2: restore from the filesystem to the Gitea instance in restoredrepo 85 // 86 87 newreponame := "restored" 88 err = migrations.RestoreRepository(ctx, d, repo.OwnerName, newreponame, []string{ 89 "labels", "issues", "comments", "milestones", "pull_requests", 90 }, false) 91 assert.NoError(t, err) 92 93 newrepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: newreponame}) 94 95 // 96 // Phase 3: dump restored from the Gitea instance to the filesystem 97 // 98 opts.RepoName = newreponame 99 opts.CloneAddr = newrepo.CloneLink().HTTPS 100 err = migrations.DumpRepository(ctx, basePath, repoOwner.Name, opts) 101 assert.NoError(t, err) 102 103 // 104 // Verify the dump of restored is the same as the dump of repo1 105 // 106 comparator := &compareDump{ 107 t: t, 108 basePath: basePath, 109 } 110 comparator.assertEquals(repo, newrepo) 111 }) 112 } 113 114 type compareDump struct { 115 t *testing.T 116 basePath string 117 repoBefore *repo_model.Repository 118 dirBefore string 119 repoAfter *repo_model.Repository 120 dirAfter string 121 } 122 123 type compareField struct { 124 before any 125 after any 126 ignore bool 127 transform func(string) string 128 nested *compareFields 129 } 130 131 type compareFields map[string]compareField 132 133 func (c *compareDump) replaceRepoName(original string) string { 134 return strings.ReplaceAll(original, c.repoBefore.Name, c.repoAfter.Name) 135 } 136 137 func (c *compareDump) assertEquals(repoBefore, repoAfter *repo_model.Repository) { 138 c.repoBefore = repoBefore 139 c.dirBefore = filepath.Join(c.basePath, repoBefore.OwnerName, repoBefore.Name) 140 c.repoAfter = repoAfter 141 c.dirAfter = filepath.Join(c.basePath, repoAfter.OwnerName, repoAfter.Name) 142 143 // 144 // base.Repository 145 // 146 _ = c.assertEqual("repo.yml", base.Repository{}, compareFields{ 147 "Name": { 148 before: c.repoBefore.Name, 149 after: c.repoAfter.Name, 150 }, 151 "CloneURL": {transform: c.replaceRepoName}, 152 "OriginalURL": {transform: c.replaceRepoName}, 153 }) 154 155 // 156 // base.Label 157 // 158 labels, ok := c.assertEqual("label.yml", []base.Label{}, compareFields{}).([]*base.Label) 159 assert.True(c.t, ok) 160 assert.GreaterOrEqual(c.t, len(labels), 1) 161 162 // 163 // base.Milestone 164 // 165 milestones, ok := c.assertEqual("milestone.yml", []base.Milestone{}, compareFields{ 166 "Updated": {ignore: true}, // the database updates that field independently 167 }).([]*base.Milestone) 168 assert.True(c.t, ok) 169 assert.GreaterOrEqual(c.t, len(milestones), 1) 170 171 // 172 // base.Issue and the associated comments 173 // 174 issues, ok := c.assertEqual("issue.yml", []base.Issue{}, compareFields{ 175 "Assignees": {ignore: true}, // not implemented yet 176 }).([]*base.Issue) 177 assert.True(c.t, ok) 178 assert.GreaterOrEqual(c.t, len(issues), 1) 179 for _, issue := range issues { 180 filename := filepath.Join("comments", fmt.Sprintf("%d.yml", issue.Number)) 181 comments, ok := c.assertEqual(filename, []base.Comment{}, compareFields{ 182 "Index": {ignore: true}, 183 }).([]*base.Comment) 184 assert.True(c.t, ok) 185 for _, comment := range comments { 186 assert.EqualValues(c.t, issue.Number, comment.IssueIndex) 187 } 188 } 189 190 // 191 // base.PullRequest and the associated comments 192 // 193 comparePullRequestBranch := &compareFields{ 194 "RepoName": { 195 before: c.repoBefore.Name, 196 after: c.repoAfter.Name, 197 }, 198 "CloneURL": {transform: c.replaceRepoName}, 199 } 200 prs, ok := c.assertEqual("pull_request.yml", []base.PullRequest{}, compareFields{ 201 "Assignees": {ignore: true}, // not implemented yet 202 "Head": {nested: comparePullRequestBranch}, 203 "Base": {nested: comparePullRequestBranch}, 204 "Labels": {ignore: true}, // because org labels are not handled properly 205 }).([]*base.PullRequest) 206 assert.True(c.t, ok) 207 assert.GreaterOrEqual(c.t, len(prs), 1) 208 for _, pr := range prs { 209 filename := filepath.Join("comments", fmt.Sprintf("%d.yml", pr.Number)) 210 comments, ok := c.assertEqual(filename, []base.Comment{}, compareFields{}).([]*base.Comment) 211 assert.True(c.t, ok) 212 for _, comment := range comments { 213 assert.EqualValues(c.t, pr.Number, comment.IssueIndex) 214 } 215 } 216 } 217 218 func (c *compareDump) assertLoadYAMLFiles(beforeFilename, afterFilename string, before, after any) { 219 _, beforeErr := os.Stat(beforeFilename) 220 _, afterErr := os.Stat(afterFilename) 221 assert.EqualValues(c.t, errors.Is(beforeErr, os.ErrNotExist), errors.Is(afterErr, os.ErrNotExist)) 222 if errors.Is(beforeErr, os.ErrNotExist) { 223 return 224 } 225 226 beforeBytes, err := os.ReadFile(beforeFilename) 227 assert.NoError(c.t, err) 228 assert.NoError(c.t, yaml.Unmarshal(beforeBytes, before)) 229 afterBytes, err := os.ReadFile(afterFilename) 230 assert.NoError(c.t, err) 231 assert.NoError(c.t, yaml.Unmarshal(afterBytes, after)) 232 } 233 234 func (c *compareDump) assertLoadFiles(beforeFilename, afterFilename string, t reflect.Type) (before, after reflect.Value) { 235 var beforePtr, afterPtr reflect.Value 236 if t.Kind() == reflect.Slice { 237 // 238 // Given []Something{} create afterPtr, beforePtr []*Something{} 239 // 240 sliceType := reflect.SliceOf(reflect.PtrTo(t.Elem())) 241 beforeSlice := reflect.MakeSlice(sliceType, 0, 10) 242 beforePtr = reflect.New(beforeSlice.Type()) 243 beforePtr.Elem().Set(beforeSlice) 244 afterSlice := reflect.MakeSlice(sliceType, 0, 10) 245 afterPtr = reflect.New(afterSlice.Type()) 246 afterPtr.Elem().Set(afterSlice) 247 } else { 248 // 249 // Given Something{} create afterPtr, beforePtr *Something{} 250 // 251 beforePtr = reflect.New(t) 252 afterPtr = reflect.New(t) 253 } 254 c.assertLoadYAMLFiles(beforeFilename, afterFilename, beforePtr.Interface(), afterPtr.Interface()) 255 return beforePtr.Elem(), afterPtr.Elem() 256 } 257 258 func (c *compareDump) assertEqual(filename string, kind any, fields compareFields) (i any) { 259 beforeFilename := filepath.Join(c.dirBefore, filename) 260 afterFilename := filepath.Join(c.dirAfter, filename) 261 262 typeOf := reflect.TypeOf(kind) 263 before, after := c.assertLoadFiles(beforeFilename, afterFilename, typeOf) 264 if typeOf.Kind() == reflect.Slice { 265 i = c.assertEqualSlices(before, after, fields) 266 } else { 267 i = c.assertEqualValues(before, after, fields) 268 } 269 return i 270 } 271 272 func (c *compareDump) assertEqualSlices(before, after reflect.Value, fields compareFields) any { 273 assert.EqualValues(c.t, before.Len(), after.Len()) 274 if before.Len() == after.Len() { 275 for i := 0; i < before.Len(); i++ { 276 _ = c.assertEqualValues( 277 reflect.Indirect(before.Index(i).Elem()), 278 reflect.Indirect(after.Index(i).Elem()), 279 fields) 280 } 281 } 282 return after.Interface() 283 } 284 285 func (c *compareDump) assertEqualValues(before, after reflect.Value, fields compareFields) any { 286 for _, field := range reflect.VisibleFields(before.Type()) { 287 bf := before.FieldByName(field.Name) 288 bi := bf.Interface() 289 af := after.FieldByName(field.Name) 290 ai := af.Interface() 291 if compare, ok := fields[field.Name]; ok { 292 if compare.ignore == true { 293 // 294 // Ignore 295 // 296 continue 297 } 298 if compare.transform != nil { 299 // 300 // Transform these strings before comparing them 301 // 302 bs, ok := bi.(string) 303 assert.True(c.t, ok) 304 as, ok := ai.(string) 305 assert.True(c.t, ok) 306 assert.EqualValues(c.t, compare.transform(bs), compare.transform(as)) 307 continue 308 } 309 if compare.before != nil && compare.after != nil { 310 // 311 // The fields are expected to have different values 312 // 313 assert.EqualValues(c.t, compare.before, bi) 314 assert.EqualValues(c.t, compare.after, ai) 315 continue 316 } 317 if compare.nested != nil { 318 // 319 // The fields are a struct, recurse 320 // 321 c.assertEqualValues(bf, af, *compare.nested) 322 continue 323 } 324 } 325 assert.EqualValues(c.t, bi, ai) 326 } 327 return after.Interface() 328 }