Skip to content

Commit bc1f765

Browse files
Loïc Dacharysinguliere
Loïc Dachary
authored andcommitted
more repo dump/restore tests, including pull requests
The tests were refactored so that all YAML files content are checked, unless an exception is set (for instance for the Updated field which is automatically updated by the database and cannot be expected to be identical over a dump/restore/dump round. This approach helps catch more errors where fields are added in the migration files because they do not need to be added to the tests to be verified. It also helps as a reminder of what is left to be implemented, such as the the Assignees field in issues. A helper is added to keep the tests DRY and facilitate their maintenance. Signed-off-by: Loïc Dachary <[email protected]>
1 parent 8bd89ca commit bc1f765

File tree

1 file changed

+235
-36
lines changed

1 file changed

+235
-36
lines changed

integrations/dump_restore_test.go

+235-36
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ package integrations
66

77
import (
88
"context"
9+
"errors"
10+
"fmt"
911
"net/url"
1012
"os"
1113
"path/filepath"
14+
"reflect"
1215
"strings"
1316
"testing"
1417

@@ -58,6 +61,7 @@ func TestDumpRestore(t *testing.T) {
5861
opts := migrations.MigrateOptions{
5962
GitServiceType: structs.GiteaService,
6063
Issues: true,
64+
PullRequests: true,
6165
Labels: true,
6266
Milestones: true,
6367
Comments: true,
@@ -80,57 +84,252 @@ func TestDumpRestore(t *testing.T) {
8084
// Phase 2: restore from the filesystem to the Gitea instance in restoredrepo
8185
//
8286

83-
newreponame := "restoredrepo"
84-
err = migrations.RestoreRepository(ctx, d, repo.OwnerName, newreponame, []string{"labels", "milestones", "issues", "comments"}, false)
87+
newreponame := "restored"
88+
err = migrations.RestoreRepository(ctx, d, repo.OwnerName, newreponame, []string{
89+
"labels", "issues", "comments", "milestones", "pull_requests",
90+
}, false)
8591
assert.NoError(t, err)
8692

8793
newrepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: newreponame}).(*repo_model.Repository)
8894

8995
//
90-
// Phase 3: dump restoredrepo from the Gitea instance to the filesystem
96+
// Phase 3: dump restored from the Gitea instance to the filesystem
9197
//
9298
opts.RepoName = newreponame
9399
opts.CloneAddr = newrepo.CloneLink().HTTPS
94100
err = migrations.DumpRepository(ctx, basePath, repoOwner.Name, opts)
95101
assert.NoError(t, err)
96102

97103
//
98-
// Verify the dump of restoredrepo is the same as the dump of repo1
104+
// Verify the dump of restored is the same as the dump of repo1
99105
//
100-
newd := filepath.Join(basePath, newrepo.OwnerName, newrepo.Name)
101-
for _, filename := range []string{"repo.yml", "label.yml", "milestone.yml"} {
102-
beforeBytes, err := os.ReadFile(filepath.Join(d, filename))
103-
assert.NoError(t, err)
104-
before := strings.ReplaceAll(string(beforeBytes), reponame, newreponame)
105-
after, err := os.ReadFile(filepath.Join(newd, filename))
106-
assert.NoError(t, err)
107-
assert.EqualValues(t, before, string(after))
106+
comparator := &compareDump{
107+
t: t,
108+
basePath: basePath,
108109
}
110+
comparator.assertEquals(repo, newrepo)
111+
})
112+
}
109113

110-
beforeBytes, err := os.ReadFile(filepath.Join(d, "issue.yml"))
111-
assert.NoError(t, err)
112-
before := make([]*base.Issue, 0, 10)
113-
assert.NoError(t, yaml.Unmarshal(beforeBytes, &before))
114-
afterBytes, err := os.ReadFile(filepath.Join(newd, "issue.yml"))
115-
assert.NoError(t, err)
116-
after := make([]*base.Issue, 0, 10)
117-
assert.NoError(t, yaml.Unmarshal(afterBytes, &after))
118-
119-
assert.EqualValues(t, len(before), len(after))
120-
if len(before) == len(after) {
121-
for i := 0; i < len(before); i++ {
122-
assert.EqualValues(t, before[i].Number, after[i].Number)
123-
assert.EqualValues(t, before[i].Title, after[i].Title)
124-
assert.EqualValues(t, before[i].Content, after[i].Content)
125-
assert.EqualValues(t, before[i].Ref, after[i].Ref)
126-
assert.EqualValues(t, before[i].Milestone, after[i].Milestone)
127-
assert.EqualValues(t, before[i].State, after[i].State)
128-
assert.EqualValues(t, before[i].IsLocked, after[i].IsLocked)
129-
assert.EqualValues(t, before[i].Created, after[i].Created)
130-
assert.EqualValues(t, before[i].Updated, after[i].Updated)
131-
assert.EqualValues(t, before[i].Labels, after[i].Labels)
132-
assert.EqualValues(t, before[i].Reactions, after[i].Reactions)
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 interface{}
125+
after interface{}
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+
for _, filename := range []string{"repo.yml", "label.yml"} {
144+
beforeBytes, err := os.ReadFile(filepath.Join(c.dirBefore, filename))
145+
assert.NoError(c.t, err)
146+
before := c.replaceRepoName(string(beforeBytes))
147+
after, err := os.ReadFile(filepath.Join(c.dirAfter, filename))
148+
assert.NoError(c.t, err)
149+
assert.EqualValues(c.t, before, string(after))
150+
}
151+
152+
//
153+
// base.Repository
154+
//
155+
_ = c.assertEqual("repo.yml", base.Repository{}, compareFields{
156+
"Name": {
157+
before: c.repoBefore.Name,
158+
after: c.repoAfter.Name,
159+
},
160+
"CloneURL": {transform: c.replaceRepoName},
161+
"OriginalURL": {transform: c.replaceRepoName},
162+
})
163+
164+
//
165+
// base.Label
166+
//
167+
labels, ok := c.assertEqual("label.yml", []base.Label{}, compareFields{}).([]*base.Label)
168+
assert.True(c.t, ok)
169+
assert.GreaterOrEqual(c.t, len(labels), 1)
170+
171+
//
172+
// base.Milestone
173+
//
174+
milestones, ok := c.assertEqual("milestone.yml", []base.Milestone{}, compareFields{
175+
"Updated": {ignore: true}, // the database updates that field independently
176+
}).([]*base.Milestone)
177+
assert.True(c.t, ok)
178+
assert.GreaterOrEqual(c.t, len(milestones), 1)
179+
180+
//
181+
// base.Issue and the associated comments
182+
//
183+
issues, ok := c.assertEqual("issue.yml", []base.Issue{}, compareFields{
184+
"Assignees": {ignore: true}, // not implemented yet
185+
}).([]*base.Issue)
186+
assert.True(c.t, ok)
187+
assert.GreaterOrEqual(c.t, len(issues), 1)
188+
for _, issue := range issues {
189+
filename := filepath.Join("comments", fmt.Sprintf("%d.yml", issue.Number))
190+
comments, ok := c.assertEqual(filename, []base.Comment{}, compareFields{}).([]*base.Comment)
191+
assert.True(c.t, ok)
192+
for _, comment := range comments {
193+
assert.EqualValues(c.t, issue.Number, comment.IssueIndex)
194+
}
195+
}
196+
197+
//
198+
// base.PullRequest and the associated comments
199+
//
200+
comparePullRequestBranch := &compareFields{
201+
"RepoName": {
202+
before: c.repoBefore.Name,
203+
after: c.repoAfter.Name,
204+
},
205+
"CloneURL": {transform: c.replaceRepoName},
206+
}
207+
prs, ok := c.assertEqual("pull_request.yml", []base.PullRequest{}, compareFields{
208+
"Assignees": {ignore: true}, // not implemented yet
209+
"Head": {nested: comparePullRequestBranch},
210+
"Base": {nested: comparePullRequestBranch},
211+
"Labels": {ignore: true}, // because org labels are not handled propery
212+
}).([]*base.PullRequest)
213+
assert.True(c.t, ok)
214+
assert.GreaterOrEqual(c.t, len(prs), 1)
215+
for _, pr := range prs {
216+
filename := filepath.Join("comments", fmt.Sprintf("%d.yml", pr.Number))
217+
comments, ok := c.assertEqual(filename, []base.Comment{}, compareFields{}).([]*base.Comment)
218+
assert.True(c.t, ok)
219+
for _, comment := range comments {
220+
assert.EqualValues(c.t, pr.Number, comment.IssueIndex)
221+
}
222+
}
223+
}
224+
225+
func (c *compareDump) assertLoadYAMLFiles(beforeFilename, afterFilename string, before, after interface{}) {
226+
_, beforeErr := os.Stat(beforeFilename)
227+
_, afterErr := os.Stat(afterFilename)
228+
assert.EqualValues(c.t, errors.Is(beforeErr, os.ErrNotExist), errors.Is(afterErr, os.ErrNotExist))
229+
if errors.Is(beforeErr, os.ErrNotExist) {
230+
return
231+
}
232+
233+
beforeBytes, err := os.ReadFile(beforeFilename)
234+
assert.NoError(c.t, err)
235+
assert.NoError(c.t, yaml.Unmarshal(beforeBytes, before))
236+
afterBytes, err := os.ReadFile(afterFilename)
237+
assert.NoError(c.t, err)
238+
assert.NoError(c.t, yaml.Unmarshal(afterBytes, after))
239+
}
240+
241+
func (c *compareDump) assertLoadFiles(beforeFilename, afterFilename string, t reflect.Type) (before, after reflect.Value) {
242+
var beforePtr, afterPtr reflect.Value
243+
if t.Kind() == reflect.Slice {
244+
//
245+
// Given []Something{} create afterPtr, beforePtr []*Something{}
246+
//
247+
sliceType := reflect.SliceOf(reflect.PtrTo(t.Elem()))
248+
beforeSlice := reflect.MakeSlice(sliceType, 0, 10)
249+
beforePtr = reflect.New(beforeSlice.Type())
250+
beforePtr.Elem().Set(beforeSlice)
251+
afterSlice := reflect.MakeSlice(sliceType, 0, 10)
252+
afterPtr = reflect.New(afterSlice.Type())
253+
afterPtr.Elem().Set(afterSlice)
254+
} else {
255+
//
256+
// Given Something{} create afterPtr, beforePtr *Something{}
257+
//
258+
beforePtr = reflect.New(t)
259+
afterPtr = reflect.New(t)
260+
}
261+
c.assertLoadYAMLFiles(beforeFilename, afterFilename, beforePtr.Interface(), afterPtr.Interface())
262+
return beforePtr.Elem(), afterPtr.Elem()
263+
}
264+
265+
func (c *compareDump) assertEqual(filename string, kind interface{}, fields compareFields) (i interface{}) {
266+
beforeFilename := filepath.Join(c.dirBefore, filename)
267+
afterFilename := filepath.Join(c.dirAfter, filename)
268+
269+
typeOf := reflect.TypeOf(kind)
270+
before, after := c.assertLoadFiles(beforeFilename, afterFilename, typeOf)
271+
if typeOf.Kind() == reflect.Slice {
272+
i = c.assertEqualSlices(before, after, fields)
273+
} else {
274+
i = c.assertEqualValues(before, after, fields)
275+
}
276+
return i
277+
}
278+
279+
func (c *compareDump) assertEqualSlices(before, after reflect.Value, fields compareFields) interface{} {
280+
assert.EqualValues(c.t, before.Len(), after.Len())
281+
if before.Len() == after.Len() {
282+
for i := 0; i < before.Len(); i++ {
283+
_ = c.assertEqualValues(
284+
reflect.Indirect(before.Index(i).Elem()),
285+
reflect.Indirect(after.Index(i).Elem()),
286+
fields)
287+
}
288+
}
289+
return after.Interface()
290+
}
291+
292+
func (c *compareDump) assertEqualValues(before, after reflect.Value, fields compareFields) interface{} {
293+
for _, field := range reflect.VisibleFields(before.Type()) {
294+
bf := before.FieldByName(field.Name)
295+
bi := bf.Interface()
296+
af := after.FieldByName(field.Name)
297+
ai := af.Interface()
298+
if compare, ok := fields[field.Name]; ok {
299+
if compare.ignore == true {
300+
//
301+
// Ignore
302+
//
303+
continue
304+
}
305+
if compare.transform != nil {
306+
//
307+
// Transform these strings before comparing them
308+
//
309+
bs, ok := bi.(string)
310+
assert.True(c.t, ok)
311+
as, ok := ai.(string)
312+
assert.True(c.t, ok)
313+
assert.EqualValues(c.t, compare.transform(bs), compare.transform(as))
314+
continue
315+
}
316+
if compare.before != nil && compare.after != nil {
317+
//
318+
// The fields are expected to have different values
319+
//
320+
assert.EqualValues(c.t, compare.before, bi)
321+
assert.EqualValues(c.t, compare.after, ai)
322+
continue
323+
}
324+
if compare.nested != nil {
325+
//
326+
// The fields are a struct, recurse
327+
//
328+
c.assertEqualValues(bf, af, *compare.nested)
329+
continue
133330
}
134331
}
135-
})
332+
assert.EqualValues(c.t, bi, ai)
333+
}
334+
return after.Interface()
136335
}

0 commit comments

Comments
 (0)