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