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