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