1package github
2
3import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "math/rand"
8 "net/http"
9 "os"
10 "testing"
11 "time"
12
13 "github.com/stretchr/testify/require"
14
15 "github.com/MichaelMure/git-bug/bridge/core"
16 "github.com/MichaelMure/git-bug/cache"
17 "github.com/MichaelMure/git-bug/repository"
18 "github.com/MichaelMure/git-bug/util/interrupt"
19)
20
21const (
22 testRepoBaseName = "git-bug-test-github-exporter"
23)
24
25type testCase struct {
26 name string
27 bug *cache.BugCache
28 numOrOp int // number of original operations
29}
30
31func testCases(repo *cache.RepoCache, identity *cache.IdentityCache) ([]*testCase, error) {
32 // simple bug
33 simpleBug, err := repo.NewBug("simple bug", "new bug")
34 if err != nil {
35 return nil, err
36 }
37
38 // bug with comments
39 bugWithComments, err := repo.NewBug("bug with comments", "new bug")
40 if err != nil {
41 return nil, err
42 }
43
44 _, err = bugWithComments.AddComment("new comment")
45 if err != nil {
46 return nil, err
47 }
48
49 // bug with label changes
50 bugLabelChange, err := repo.NewBug("bug label change", "new bug")
51 if err != nil {
52 return nil, err
53 }
54
55 _, _, err = bugLabelChange.ChangeLabels([]string{"bug"}, nil)
56 if err != nil {
57 return nil, err
58 }
59
60 _, _, err = bugLabelChange.ChangeLabels([]string{"core"}, nil)
61 if err != nil {
62 return nil, err
63 }
64
65 _, _, err = bugLabelChange.ChangeLabels(nil, []string{"bug"})
66 if err != nil {
67 return nil, err
68 }
69
70 // bug with comments editions
71 bugWithCommentEditions, err := repo.NewBug("bug with comments editions", "new bug")
72 if err != nil {
73 return nil, err
74 }
75
76 createOpHash, err := bugWithCommentEditions.Snapshot().Operations[0].Hash()
77 if err != nil {
78 return nil, err
79 }
80
81 _, err = bugWithCommentEditions.EditComment(createOpHash, "first comment edited")
82 if err != nil {
83 return nil, err
84 }
85
86 commentOp, err := bugWithCommentEditions.AddComment("first comment")
87 if err != nil {
88 return nil, err
89 }
90
91 commentOpHash, err := commentOp.Hash()
92 if err != nil {
93 return nil, err
94 }
95
96 _, err = bugWithCommentEditions.EditComment(commentOpHash, "first comment edited")
97 if err != nil {
98 return nil, err
99 }
100
101 // bug status changed
102 bugStatusChanged, err := repo.NewBug("bug status changed", "new bug")
103 if err != nil {
104 return nil, err
105 }
106
107 _, err = bugStatusChanged.Close()
108 if err != nil {
109 return nil, err
110 }
111
112 _, err = bugStatusChanged.Open()
113 if err != nil {
114 return nil, err
115 }
116
117 // bug title changed
118 bugTitleEdited, err := repo.NewBug("bug title edited", "new bug")
119 if err != nil {
120 return nil, err
121 }
122
123 _, err = bugTitleEdited.SetTitle("bug title edited again")
124 if err != nil {
125 return nil, err
126 }
127
128 return []*testCase{
129 &testCase{
130 name: "simple bug",
131 bug: simpleBug,
132 numOrOp: 1,
133 },
134 &testCase{
135 name: "bug with comments",
136 bug: bugWithComments,
137 numOrOp: 2,
138 },
139 &testCase{
140 name: "bug label change",
141 bug: bugLabelChange,
142 numOrOp: 4,
143 },
144 &testCase{
145 name: "bug with comment editions",
146 bug: bugWithCommentEditions,
147 numOrOp: 4,
148 },
149 &testCase{
150 name: "bug changed status",
151 bug: bugStatusChanged,
152 numOrOp: 3,
153 },
154 &testCase{
155 name: "bug title edited",
156 bug: bugTitleEdited,
157 numOrOp: 2,
158 },
159 }, nil
160
161}
162
163func TestExporter(t *testing.T) {
164 user := os.Getenv("TEST_USER")
165 token := os.Getenv("BOT_TOKEN")
166 if token == "" {
167 t.Skip("Env var GITHUB_TOKEN_PRIVATE missing")
168 }
169
170 repo := repository.CreateTestRepo(false)
171 defer repository.CleanupTestRepos(t, repo)
172
173 backend, err := cache.NewRepoCache(repo)
174 require.NoError(t, err)
175
176 author, err := backend.NewIdentity("test identity", "test@test.org")
177 if err != nil {
178 t.Fatal(err)
179 }
180
181 err = backend.SetUserIdentity(author)
182 if err != nil {
183 t.Fatal(err)
184 }
185
186 defer backend.Close()
187 interrupt.RegisterCleaner(backend.Close)
188
189 tests, err := testCases(backend, author)
190 if err != nil {
191 t.Fatal(err)
192 }
193
194 // generate project name
195 projectName := generateRepoName()
196
197 // create repository
198 if err := createRepository(projectName, token); err != nil {
199 t.Fatal(err)
200 }
201 fmt.Println("created repository", projectName)
202
203 // delete repository before ending tests
204 defer func(t *testing.T) {
205 if err := deleteRepository(projectName, user, token); err != nil {
206 t.Fatal(err)
207 }
208 fmt.Println("deleted repository:", projectName)
209 }(t)
210
211 // initialize exporter
212 exporter := &githubExporter{}
213 err = exporter.Init(core.Configuration{
214 keyOwner: user,
215 keyProject: projectName,
216 keyToken: token,
217 })
218 require.NoError(t, err)
219
220 start := time.Now()
221
222 // export all bugs
223 err = exporter.ExportAll(backend, time.Time{})
224 require.NoError(t, err)
225
226 fmt.Printf("test repository exported in %f seconds\n", time.Since(start).Seconds())
227
228 repoTwo := repository.CreateTestRepo(false)
229 defer repository.CleanupTestRepos(t, repoTwo)
230
231 // create a second backend
232 backendTwo, err := cache.NewRepoCache(repoTwo)
233 require.NoError(t, err)
234
235 importer := &githubImporter{}
236 err = importer.Init(core.Configuration{
237 keyOwner: user,
238 keyProject: projectName,
239 keyToken: token,
240 })
241 require.NoError(t, err)
242
243 // import all exported bugs to the second backend
244 err = importer.ImportAll(backendTwo, time.Time{})
245 require.NoError(t, err)
246
247 require.Len(t, backendTwo.AllBugsIds(), len(tests))
248
249 for _, tt := range tests {
250 t.Run(tt.name, func(t *testing.T) {
251 // for each operation a SetMetadataOperation will be added
252 // so number of operations should double
253 require.Len(t, tt.bug.Snapshot().Operations, tt.numOrOp*2)
254
255 bugGithubID, ok := tt.bug.Snapshot().Operations[0].GetMetadata(keyGithubId)
256 require.True(t, ok)
257
258 importedBug, err := backendTwo.ResolveBugCreateMetadata(keyGithubId, bugGithubID)
259 require.NoError(t, err)
260
261 require.Len(t, importedBug.Snapshot().Operations, tt.numOrOp)
262
263 for _, _ = range importedBug.Snapshot().Operations {
264 // test operations or last bug state ?
265 }
266 })
267 }
268}
269
270func generateRepoName() string {
271 rand.Seed(time.Now().UnixNano())
272 var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
273 b := make([]rune, 8)
274 for i := range b {
275 b[i] = letterRunes[rand.Intn(len(letterRunes))]
276 }
277 return fmt.Sprintf("%s-%s", testRepoBaseName, string(b))
278}
279
280func createRepository(project, token string) error {
281 url := fmt.Sprintf("%s/user/repos", githubV3Url)
282
283 params := struct {
284 Name string `json:"name"`
285 Description string `json:"description"`
286 Private bool `json:"private"`
287 HasIssues bool `json:"has_issues"`
288 }{
289 Name: project,
290 Description: "git-bug exporter temporary test repository",
291 Private: true,
292 HasIssues: true,
293 }
294
295 data, err := json.Marshal(params)
296 if err != nil {
297 return err
298 }
299
300 req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
301 if err != nil {
302 return err
303 }
304
305 // need the token for private repositories
306 req.Header.Set("Authorization", fmt.Sprintf("token %s", token))
307
308 client := &http.Client{
309 Timeout: defaultTimeout,
310 }
311
312 resp, err := client.Do(req)
313 if err != nil {
314 return err
315 }
316
317 return resp.Body.Close()
318}
319
320func deleteRepository(project, owner, token string) error {
321 url := fmt.Sprintf("%s/repos/%s/%s", githubV3Url, owner, project)
322
323 req, err := http.NewRequest("DELETE", url, nil)
324 if err != nil {
325 return err
326 }
327
328 // need the token for private repositories
329 req.Header.Set("Authorization", fmt.Sprintf("token %s", token))
330
331 client := &http.Client{
332 Timeout: defaultTimeout,
333 }
334
335 resp, err := client.Do(req)
336 if err != nil {
337 return err
338 }
339
340 defer resp.Body.Close()
341
342 if resp.StatusCode != http.StatusNoContent {
343 return fmt.Errorf("error deleting repository")
344 }
345
346 return nil
347}