123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- // Copyright 2022 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 integrations
-
- import (
- "context"
- "errors"
- "fmt"
- "net/url"
- "os"
- "path/filepath"
- "reflect"
- "strings"
- "testing"
-
- repo_model "code.gitea.io/gitea/models/repo"
- "code.gitea.io/gitea/models/unittest"
- user_model "code.gitea.io/gitea/models/user"
- base "code.gitea.io/gitea/modules/migration"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/modules/util"
- "code.gitea.io/gitea/services/migrations"
-
- "github.com/stretchr/testify/assert"
- "gopkg.in/yaml.v2"
- )
-
- func TestDumpRestore(t *testing.T) {
- onGiteaRun(t, func(t *testing.T, u *url.URL) {
- AllowLocalNetworks := setting.Migrations.AllowLocalNetworks
- setting.Migrations.AllowLocalNetworks = true
- AppVer := setting.AppVer
- // Gitea SDK (go-sdk) need to parse the AppVer from server response, so we must set it to a valid version string.
- setting.AppVer = "1.16.0"
- defer func() {
- setting.Migrations.AllowLocalNetworks = AllowLocalNetworks
- setting.AppVer = AppVer
- }()
-
- assert.NoError(t, migrations.Init())
-
- reponame := "repo1"
-
- basePath, err := os.MkdirTemp("", reponame)
- assert.NoError(t, err)
- defer util.RemoveAll(basePath)
-
- repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
- repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
- session := loginUser(t, repoOwner.Name)
- token := getTokenForLoggedInUser(t, session)
-
- //
- // Phase 1: dump repo1 from the Gitea instance to the filesystem
- //
-
- ctx := context.Background()
- opts := migrations.MigrateOptions{
- GitServiceType: structs.GiteaService,
- Issues: true,
- PullRequests: true,
- Labels: true,
- Milestones: true,
- Comments: true,
- AuthToken: token,
- CloneAddr: repo.CloneLink().HTTPS,
- RepoName: reponame,
- }
- err = migrations.DumpRepository(ctx, basePath, repoOwner.Name, opts)
- assert.NoError(t, err)
-
- //
- // Verify desired side effects of the dump
- //
- d := filepath.Join(basePath, repo.OwnerName, repo.Name)
- for _, f := range []string{"repo.yml", "topic.yml", "label.yml", "milestone.yml", "issue.yml"} {
- assert.FileExists(t, filepath.Join(d, f))
- }
-
- //
- // Phase 2: restore from the filesystem to the Gitea instance in restoredrepo
- //
-
- newreponame := "restored"
- err = migrations.RestoreRepository(ctx, d, repo.OwnerName, newreponame, []string{
- "labels", "issues", "comments", "milestones", "pull_requests",
- }, false)
- assert.NoError(t, err)
-
- newrepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: newreponame}).(*repo_model.Repository)
-
- //
- // Phase 3: dump restored from the Gitea instance to the filesystem
- //
- opts.RepoName = newreponame
- opts.CloneAddr = newrepo.CloneLink().HTTPS
- err = migrations.DumpRepository(ctx, basePath, repoOwner.Name, opts)
- assert.NoError(t, err)
-
- //
- // Verify the dump of restored is the same as the dump of repo1
- //
- comparator := &compareDump{
- t: t,
- basePath: basePath,
- }
- comparator.assertEquals(repo, newrepo)
- })
- }
-
- type compareDump struct {
- t *testing.T
- basePath string
- repoBefore *repo_model.Repository
- dirBefore string
- repoAfter *repo_model.Repository
- dirAfter string
- }
-
- type compareField struct {
- before interface{}
- after interface{}
- ignore bool
- transform func(string) string
- nested *compareFields
- }
-
- type compareFields map[string]compareField
-
- func (c *compareDump) replaceRepoName(original string) string {
- return strings.ReplaceAll(original, c.repoBefore.Name, c.repoAfter.Name)
- }
-
- func (c *compareDump) assertEquals(repoBefore, repoAfter *repo_model.Repository) {
- c.repoBefore = repoBefore
- c.dirBefore = filepath.Join(c.basePath, repoBefore.OwnerName, repoBefore.Name)
- c.repoAfter = repoAfter
- c.dirAfter = filepath.Join(c.basePath, repoAfter.OwnerName, repoAfter.Name)
-
- //
- // base.Repository
- //
- _ = c.assertEqual("repo.yml", base.Repository{}, compareFields{
- "Name": {
- before: c.repoBefore.Name,
- after: c.repoAfter.Name,
- },
- "CloneURL": {transform: c.replaceRepoName},
- "OriginalURL": {transform: c.replaceRepoName},
- })
-
- //
- // base.Label
- //
- labels, ok := c.assertEqual("label.yml", []base.Label{}, compareFields{}).([]*base.Label)
- assert.True(c.t, ok)
- assert.GreaterOrEqual(c.t, len(labels), 1)
-
- //
- // base.Milestone
- //
- milestones, ok := c.assertEqual("milestone.yml", []base.Milestone{}, compareFields{
- "Updated": {ignore: true}, // the database updates that field independently
- }).([]*base.Milestone)
- assert.True(c.t, ok)
- assert.GreaterOrEqual(c.t, len(milestones), 1)
-
- //
- // base.Issue and the associated comments
- //
- issues, ok := c.assertEqual("issue.yml", []base.Issue{}, compareFields{
- "Assignees": {ignore: true}, // not implemented yet
- }).([]*base.Issue)
- assert.True(c.t, ok)
- assert.GreaterOrEqual(c.t, len(issues), 1)
- for _, issue := range issues {
- filename := filepath.Join("comments", fmt.Sprintf("%d.yml", issue.Number))
- comments, ok := c.assertEqual(filename, []base.Comment{}, compareFields{
- "Index": {ignore: true},
- }).([]*base.Comment)
- assert.True(c.t, ok)
- for _, comment := range comments {
- assert.EqualValues(c.t, issue.Number, comment.IssueIndex)
- }
- }
-
- //
- // base.PullRequest and the associated comments
- //
- comparePullRequestBranch := &compareFields{
- "RepoName": {
- before: c.repoBefore.Name,
- after: c.repoAfter.Name,
- },
- "CloneURL": {transform: c.replaceRepoName},
- }
- prs, ok := c.assertEqual("pull_request.yml", []base.PullRequest{}, compareFields{
- "Assignees": {ignore: true}, // not implemented yet
- "Head": {nested: comparePullRequestBranch},
- "Base": {nested: comparePullRequestBranch},
- "Labels": {ignore: true}, // because org labels are not handled properly
- }).([]*base.PullRequest)
- assert.True(c.t, ok)
- assert.GreaterOrEqual(c.t, len(prs), 1)
- for _, pr := range prs {
- filename := filepath.Join("comments", fmt.Sprintf("%d.yml", pr.Number))
- comments, ok := c.assertEqual(filename, []base.Comment{}, compareFields{}).([]*base.Comment)
- assert.True(c.t, ok)
- for _, comment := range comments {
- assert.EqualValues(c.t, pr.Number, comment.IssueIndex)
- }
- }
- }
-
- func (c *compareDump) assertLoadYAMLFiles(beforeFilename, afterFilename string, before, after interface{}) {
- _, beforeErr := os.Stat(beforeFilename)
- _, afterErr := os.Stat(afterFilename)
- assert.EqualValues(c.t, errors.Is(beforeErr, os.ErrNotExist), errors.Is(afterErr, os.ErrNotExist))
- if errors.Is(beforeErr, os.ErrNotExist) {
- return
- }
-
- beforeBytes, err := os.ReadFile(beforeFilename)
- assert.NoError(c.t, err)
- assert.NoError(c.t, yaml.Unmarshal(beforeBytes, before))
- afterBytes, err := os.ReadFile(afterFilename)
- assert.NoError(c.t, err)
- assert.NoError(c.t, yaml.Unmarshal(afterBytes, after))
- }
-
- func (c *compareDump) assertLoadFiles(beforeFilename, afterFilename string, t reflect.Type) (before, after reflect.Value) {
- var beforePtr, afterPtr reflect.Value
- if t.Kind() == reflect.Slice {
- //
- // Given []Something{} create afterPtr, beforePtr []*Something{}
- //
- sliceType := reflect.SliceOf(reflect.PtrTo(t.Elem()))
- beforeSlice := reflect.MakeSlice(sliceType, 0, 10)
- beforePtr = reflect.New(beforeSlice.Type())
- beforePtr.Elem().Set(beforeSlice)
- afterSlice := reflect.MakeSlice(sliceType, 0, 10)
- afterPtr = reflect.New(afterSlice.Type())
- afterPtr.Elem().Set(afterSlice)
- } else {
- //
- // Given Something{} create afterPtr, beforePtr *Something{}
- //
- beforePtr = reflect.New(t)
- afterPtr = reflect.New(t)
- }
- c.assertLoadYAMLFiles(beforeFilename, afterFilename, beforePtr.Interface(), afterPtr.Interface())
- return beforePtr.Elem(), afterPtr.Elem()
- }
-
- func (c *compareDump) assertEqual(filename string, kind interface{}, fields compareFields) (i interface{}) {
- beforeFilename := filepath.Join(c.dirBefore, filename)
- afterFilename := filepath.Join(c.dirAfter, filename)
-
- typeOf := reflect.TypeOf(kind)
- before, after := c.assertLoadFiles(beforeFilename, afterFilename, typeOf)
- if typeOf.Kind() == reflect.Slice {
- i = c.assertEqualSlices(before, after, fields)
- } else {
- i = c.assertEqualValues(before, after, fields)
- }
- return i
- }
-
- func (c *compareDump) assertEqualSlices(before, after reflect.Value, fields compareFields) interface{} {
- assert.EqualValues(c.t, before.Len(), after.Len())
- if before.Len() == after.Len() {
- for i := 0; i < before.Len(); i++ {
- _ = c.assertEqualValues(
- reflect.Indirect(before.Index(i).Elem()),
- reflect.Indirect(after.Index(i).Elem()),
- fields)
- }
- }
- return after.Interface()
- }
-
- func (c *compareDump) assertEqualValues(before, after reflect.Value, fields compareFields) interface{} {
- for _, field := range reflect.VisibleFields(before.Type()) {
- bf := before.FieldByName(field.Name)
- bi := bf.Interface()
- af := after.FieldByName(field.Name)
- ai := af.Interface()
- if compare, ok := fields[field.Name]; ok {
- if compare.ignore == true {
- //
- // Ignore
- //
- continue
- }
- if compare.transform != nil {
- //
- // Transform these strings before comparing them
- //
- bs, ok := bi.(string)
- assert.True(c.t, ok)
- as, ok := ai.(string)
- assert.True(c.t, ok)
- assert.EqualValues(c.t, compare.transform(bs), compare.transform(as))
- continue
- }
- if compare.before != nil && compare.after != nil {
- //
- // The fields are expected to have different values
- //
- assert.EqualValues(c.t, compare.before, bi)
- assert.EqualValues(c.t, compare.after, ai)
- continue
- }
- if compare.nested != nil {
- //
- // The fields are a struct, recurse
- //
- c.assertEqualValues(bf, af, *compare.nested)
- continue
- }
- }
- assert.EqualValues(c.t, bi, ai)
- }
- return after.Interface()
- }
|