1package gitlab
2
3import (
4 "context"
5 "fmt"
6 "math/rand"
7 "os"
8 "strconv"
9 "testing"
10 "time"
11
12 "github.com/xanzy/go-gitlab"
13
14 "github.com/stretchr/testify/require"
15
16 "github.com/MichaelMure/git-bug/bridge/core"
17 "github.com/MichaelMure/git-bug/bug"
18 "github.com/MichaelMure/git-bug/cache"
19 "github.com/MichaelMure/git-bug/repository"
20 "github.com/MichaelMure/git-bug/util/interrupt"
21)
22
23const (
24 testRepoBaseName = "git-bug-test-gitlab-exporter"
25)
26
27type testCase struct {
28 name string
29 bug *cache.BugCache
30 numOp int // number of original operations
31 numOpExp int // number of operations after export
32 numOpImp int // number of operations after import
33}
34
35func testCases(t *testing.T, repo *cache.RepoCache, identity *cache.IdentityCache) []*testCase {
36 // simple bug
37 simpleBug, _, err := repo.NewBug("simple bug", "new bug")
38 require.NoError(t, err)
39
40 // bug with comments
41 bugWithComments, _, err := repo.NewBug("bug with comments", "new bug")
42 require.NoError(t, err)
43
44 _, err = bugWithComments.AddComment("new comment")
45 require.NoError(t, err)
46
47 // bug with label changes
48 bugLabelChange, _, err := repo.NewBug("bug label change", "new bug")
49 require.NoError(t, err)
50
51 _, _, err = bugLabelChange.ChangeLabels([]string{"bug"}, nil)
52 require.NoError(t, err)
53
54 _, _, err = bugLabelChange.ChangeLabels([]string{"core"}, nil)
55 require.NoError(t, err)
56
57 _, _, err = bugLabelChange.ChangeLabels(nil, []string{"bug"})
58 require.NoError(t, err)
59
60 // bug with comments editions
61 bugWithCommentEditions, createOp, err := repo.NewBug("bug with comments editions", "new bug")
62 require.NoError(t, err)
63
64 _, err = bugWithCommentEditions.EditComment(createOp.Id(), "first comment edited")
65 require.NoError(t, err)
66
67 commentOp, err := bugWithCommentEditions.AddComment("first comment")
68 require.NoError(t, err)
69
70 _, err = bugWithCommentEditions.EditComment(commentOp.Id(), "first comment edited")
71 require.NoError(t, err)
72
73 // bug status changed
74 bugStatusChanged, _, err := repo.NewBug("bug status changed", "new bug")
75 require.NoError(t, err)
76
77 _, err = bugStatusChanged.Close()
78 require.NoError(t, err)
79
80 _, err = bugStatusChanged.Open()
81 require.NoError(t, err)
82
83 // bug title changed
84 bugTitleEdited, _, err := repo.NewBug("bug title edited", "new bug")
85 require.NoError(t, err)
86
87 _, err = bugTitleEdited.SetTitle("bug title edited again")
88 require.NoError(t, err)
89
90 return []*testCase{
91 &testCase{
92 name: "simple bug",
93 bug: simpleBug,
94 numOp: 1,
95 numOpExp: 2,
96 numOpImp: 1,
97 },
98 &testCase{
99 name: "bug with comments",
100 bug: bugWithComments,
101 numOp: 2,
102 numOpExp: 4,
103 numOpImp: 2,
104 },
105 &testCase{
106 name: "bug label change",
107 bug: bugLabelChange,
108 numOp: 4,
109 numOpExp: 8,
110 numOpImp: 4,
111 },
112 &testCase{
113 name: "bug with comment editions",
114 bug: bugWithCommentEditions,
115 numOp: 4,
116 numOpExp: 8,
117 numOpImp: 2,
118 },
119 &testCase{
120 name: "bug changed status",
121 bug: bugStatusChanged,
122 numOp: 3,
123 numOpExp: 6,
124 numOpImp: 3,
125 },
126 &testCase{
127 name: "bug title edited",
128 bug: bugTitleEdited,
129 numOp: 2,
130 numOpExp: 4,
131 numOpImp: 2,
132 },
133 }
134}
135
136func TestPushPull(t *testing.T) {
137 // token must have 'repo' and 'delete_repo' scopes
138 token := os.Getenv("GITLAB_API_TOKEN")
139 if token == "" {
140 t.Skip("Env var GITLAB_API_TOKEN missing")
141 }
142
143 // create repo backend
144 repo := repository.CreateTestRepo(false)
145 defer repository.CleanupTestRepos(t, repo)
146
147 backend, err := cache.NewRepoCache(repo)
148 require.NoError(t, err)
149
150 // set author identity
151 author, err := backend.NewIdentity("test identity", "test@test.org")
152 require.NoError(t, err)
153
154 err = backend.SetUserIdentity(author)
155 require.NoError(t, err)
156
157 defer backend.Close()
158 interrupt.RegisterCleaner(backend.Close)
159
160 tests := testCases(t, backend, author)
161
162 // generate project name
163 projectName := generateRepoName()
164
165 // create target Gitlab repository
166 projectID, err := createRepository(context.TODO(), projectName, token)
167 require.NoError(t, err)
168
169 fmt.Println("created repository", projectName)
170
171 // Make sure to remove the Gitlab repository when the test end
172 defer func(t *testing.T) {
173 if err := deleteRepository(context.TODO(), projectID, token); err != nil {
174 t.Fatal(err)
175 }
176 fmt.Println("deleted repository:", projectName)
177 }(t)
178
179 interrupt.RegisterCleaner(func() error {
180 return deleteRepository(context.TODO(), projectID, token)
181 })
182
183 // initialize exporter
184 exporter := &gitlabExporter{}
185 err = exporter.Init(core.Configuration{
186 keyProjectID: strconv.Itoa(projectID),
187 keyToken: token,
188 })
189 require.NoError(t, err)
190
191 ctx := context.Background()
192 start := time.Now()
193
194 // export all bugs
195 exportEvents, err := exporter.ExportAll(ctx, backend, time.Time{})
196 require.NoError(t, err)
197
198 for result := range exportEvents {
199 require.NoError(t, result.Err)
200 }
201 require.NoError(t, err)
202
203 fmt.Printf("test repository exported in %f seconds\n", time.Since(start).Seconds())
204
205 repoTwo := repository.CreateTestRepo(false)
206 defer repository.CleanupTestRepos(t, repoTwo)
207
208 // create a second backend
209 backendTwo, err := cache.NewRepoCache(repoTwo)
210 require.NoError(t, err)
211
212 importer := &gitlabImporter{}
213 err = importer.Init(core.Configuration{
214 keyProjectID: strconv.Itoa(projectID),
215 keyToken: token,
216 })
217 require.NoError(t, err)
218
219 // import all exported bugs to the second backend
220 importEvents, err := importer.ImportAll(ctx, backendTwo, time.Time{})
221 require.NoError(t, err)
222
223 for result := range importEvents {
224 require.NoError(t, result.Err)
225 }
226
227 require.Len(t, backendTwo.AllBugsIds(), len(tests))
228
229 for _, tt := range tests {
230 t.Run(tt.name, func(t *testing.T) {
231 // for each operation a SetMetadataOperation will be added
232 // so number of operations should double
233 require.Len(t, tt.bug.Snapshot().Operations, tt.numOpExp)
234
235 // verify operation have correct metadata
236 for _, op := range tt.bug.Snapshot().Operations {
237 // Check if the originals operations (*not* SetMetadata) are tagged properly
238 if _, ok := op.(*bug.SetMetadataOperation); !ok {
239 _, haveIDMetadata := op.GetMetadata(metaKeyGitlabId)
240 require.True(t, haveIDMetadata)
241
242 _, haveURLMetada := op.GetMetadata(metaKeyGitlabUrl)
243 require.True(t, haveURLMetada)
244 }
245 }
246
247 // get bug gitlab ID
248 bugGitlabID, ok := tt.bug.Snapshot().GetCreateMetadata(metaKeyGitlabId)
249 require.True(t, ok)
250
251 // retrieve bug from backendTwo
252 importedBug, err := backendTwo.ResolveBugCreateMetadata(metaKeyGitlabId, bugGitlabID)
253 require.NoError(t, err)
254
255 // verify bug have same number of original operations
256 require.Len(t, importedBug.Snapshot().Operations, tt.numOpImp)
257
258 // verify bugs are taged with origin=gitlab
259 issueOrigin, ok := importedBug.Snapshot().GetCreateMetadata(core.MetaKeyOrigin)
260 require.True(t, ok)
261 require.Equal(t, issueOrigin, target)
262
263 //TODO: maybe more tests to ensure bug final state
264 })
265 }
266}
267
268func generateRepoName() string {
269 rand.Seed(time.Now().UnixNano())
270 var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
271 b := make([]rune, 8)
272 for i := range b {
273 b[i] = letterRunes[rand.Intn(len(letterRunes))]
274 }
275 return fmt.Sprintf("%s-%s", testRepoBaseName, string(b))
276}
277
278// create repository need a token with scope 'repo'
279func createRepository(ctx context.Context, name, token string) (int, error) {
280 client := buildClient(token)
281 project, _, err := client.Projects.CreateProject(
282 &gitlab.CreateProjectOptions{
283 Name: gitlab.String(name),
284 },
285 gitlab.WithContext(ctx),
286 )
287 if err != nil {
288 return 0, err
289 }
290
291 return project.ID, nil
292}
293
294// delete repository need a token with scope 'delete_repo'
295func deleteRepository(ctx context.Context, project int, token string) error {
296 client := buildClient(token)
297 _, err := client.Projects.DeleteProject(project, gitlab.WithContext(ctx))
298 return err
299}