export_test.go

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