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.7KB

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