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