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