gitea源码

dump_restore_test.go 9.6KB

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