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