You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

dump_restore_test.go 9.6KB

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