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