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