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 start := time.Now()
178
179 // export all bugs
180 events, err := exporter.ExportAll(backend, time.Time{})
181 require.NoError(t, err)
182
183 for result := range events {
184 require.NoError(t, result.Err)
185 }
186 require.NoError(t, err)
187
188 fmt.Printf("test repository exported in %f seconds\n", time.Since(start).Seconds())
189
190 repoTwo := repository.CreateTestRepo(false)
191 defer repository.CleanupTestRepos(t, repoTwo)
192
193 // create a second backend
194 backendTwo, err := cache.NewRepoCache(repoTwo)
195 require.NoError(t, err)
196
197 importer := &gitlabImporter{}
198 err = importer.Init(core.Configuration{
199 keyProjectID: strconv.Itoa(projectID),
200 keyToken: token,
201 })
202 require.NoError(t, err)
203
204 // import all exported bugs to the second backend
205 err = importer.ImportAll(backendTwo, time.Time{})
206 require.NoError(t, err)
207
208 require.Len(t, backendTwo.AllBugsIds(), len(tests))
209
210 for _, tt := range tests {
211 t.Run(tt.name, func(t *testing.T) {
212 // for each operation a SetMetadataOperation will be added
213 // so number of operations should double
214 require.Len(t, tt.bug.Snapshot().Operations, tt.numOrOp*2)
215
216 // verify operation have correct metadata
217 for _, op := range tt.bug.Snapshot().Operations {
218 // Check if the originals operations (*not* SetMetadata) are tagged properly
219 if _, ok := op.(*bug.SetMetadataOperation); !ok {
220 _, haveIDMetadata := op.GetMetadata(keyGitlabId)
221 require.True(t, haveIDMetadata)
222
223 _, haveURLMetada := op.GetMetadata(keyGitlabUrl)
224 require.True(t, haveURLMetada)
225 }
226 }
227
228 // get bug gitlab ID
229 bugGitlabID, ok := tt.bug.Snapshot().GetCreateMetadata(keyGitlabId)
230 require.True(t, ok)
231
232 // retrieve bug from backendTwo
233 importedBug, err := backendTwo.ResolveBugCreateMetadata(keyGitlabId, bugGitlabID)
234 require.NoError(t, err)
235
236 // verify bug have same number of original operations
237 require.Len(t, importedBug.Snapshot().Operations, tt.numOrOp)
238
239 // verify bugs are taged with origin=gitlab
240 issueOrigin, ok := importedBug.Snapshot().GetCreateMetadata(core.KeyOrigin)
241 require.True(t, ok)
242 require.Equal(t, issueOrigin, target)
243
244 //TODO: maybe more tests to ensure bug final state
245 })
246 }
247}
248
249func generateRepoName() string {
250 rand.Seed(time.Now().UnixNano())
251 var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
252 b := make([]rune, 8)
253 for i := range b {
254 b[i] = letterRunes[rand.Intn(len(letterRunes))]
255 }
256 return fmt.Sprintf("%s-%s", testRepoBaseName, string(b))
257}
258
259// create repository need a token with scope 'repo'
260func createRepository(ctx context.Context, name, token string) (int, error) {
261 client := buildClient(token)
262 project, _, err := client.Projects.CreateProject(
263 &gitlab.CreateProjectOptions{
264 Name: gitlab.String(name),
265 },
266 gitlab.WithContext(ctx),
267 )
268
269 return project.ID, err
270}
271
272// delete repository need a token with scope 'delete_repo'
273func deleteRepository(ctx context.Context, project int, token string) error {
274 client := buildClient(token)
275 _, err := client.Projects.DeleteProject(project, gitlab.WithContext(ctx))
276 return err
277}