export_test.go

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