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