Add testifylint to lint checks (#4535)
go-require lint is ignored for now Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/4535 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Co-authored-by: TheFox0x7 <thefox0x7@gmail.com> Co-committed-by: TheFox0x7 <thefox0x7@gmail.com>
This commit is contained in:
parent
94933470cd
commit
4de909747b
504 changed files with 5028 additions and 4680 deletions
|
@ -13,6 +13,7 @@ import (
|
|||
base "code.gitea.io/gitea/modules/migration"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCodebaseDownloadRepo(t *testing.T) {
|
||||
|
@ -41,7 +42,7 @@ func TestCodebaseDownloadRepo(t *testing.T) {
|
|||
t.Fatalf("Error creating Codebase downloader: %v", err)
|
||||
}
|
||||
repo, err := downloader.GetRepoInfo()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertRepositoryEqual(t, &base.Repository{
|
||||
Name: "test",
|
||||
Owner: "",
|
||||
|
@ -51,7 +52,7 @@ func TestCodebaseDownloadRepo(t *testing.T) {
|
|||
}, repo)
|
||||
|
||||
milestones, err := downloader.GetMilestones()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertMilestonesEqual(t, []*base.Milestone{
|
||||
{
|
||||
Title: "Milestone1",
|
||||
|
@ -66,11 +67,11 @@ func TestCodebaseDownloadRepo(t *testing.T) {
|
|||
}, milestones)
|
||||
|
||||
labels, err := downloader.GetLabels()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, labels, 4)
|
||||
|
||||
issues, isEnd, err := downloader.GetIssues(1, 2)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, isEnd)
|
||||
assertIssuesEqual(t, []*base.Issue{
|
||||
{
|
||||
|
@ -107,7 +108,7 @@ func TestCodebaseDownloadRepo(t *testing.T) {
|
|||
}, issues)
|
||||
|
||||
comments, _, err := downloader.GetComments(issues[0])
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertCommentsEqual(t, []*base.Comment{
|
||||
{
|
||||
IssueIndex: 2,
|
||||
|
@ -120,7 +121,7 @@ func TestCodebaseDownloadRepo(t *testing.T) {
|
|||
}, comments)
|
||||
|
||||
prs, _, err := downloader.GetPullRequests(1, 1)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertPullRequestsEqual(t, []*base.PullRequest{
|
||||
{
|
||||
Number: 3,
|
||||
|
@ -145,6 +146,6 @@ func TestCodebaseDownloadRepo(t *testing.T) {
|
|||
}, prs)
|
||||
|
||||
rvs, err := downloader.GetReviews(prs[0])
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, rvs)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
base "code.gitea.io/gitea/modules/migration"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGiteaDownloadRepo(t *testing.T) {
|
||||
|
@ -32,12 +33,10 @@ func TestGiteaDownloadRepo(t *testing.T) {
|
|||
if downloader == nil {
|
||||
t.Fatal("NewGitlabDownloader is nil")
|
||||
}
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fatal("NewGitlabDownloader error occur")
|
||||
}
|
||||
require.NoError(t, err, "NewGitlabDownloader error occur")
|
||||
|
||||
repo, err := downloader.GetRepoInfo()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertRepositoryEqual(t, &base.Repository{
|
||||
Name: "test_repo",
|
||||
Owner: "gitea",
|
||||
|
@ -49,12 +48,12 @@ func TestGiteaDownloadRepo(t *testing.T) {
|
|||
}, repo)
|
||||
|
||||
topics, err := downloader.GetTopics()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
sort.Strings(topics)
|
||||
assert.EqualValues(t, []string{"ci", "gitea", "migration", "test"}, topics)
|
||||
|
||||
labels, err := downloader.GetLabels()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertLabelsEqual(t, []*base.Label{
|
||||
{
|
||||
Name: "Bug",
|
||||
|
@ -84,7 +83,7 @@ func TestGiteaDownloadRepo(t *testing.T) {
|
|||
}, labels)
|
||||
|
||||
milestones, err := downloader.GetMilestones()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertMilestonesEqual(t, []*base.Milestone{
|
||||
{
|
||||
Title: "V2 Finalize",
|
||||
|
@ -104,7 +103,7 @@ func TestGiteaDownloadRepo(t *testing.T) {
|
|||
}, milestones)
|
||||
|
||||
releases, err := downloader.GetReleases()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertReleasesEqual(t, []*base.Release{
|
||||
{
|
||||
Name: "Second Release",
|
||||
|
@ -135,13 +134,13 @@ func TestGiteaDownloadRepo(t *testing.T) {
|
|||
}, releases)
|
||||
|
||||
issues, isEnd, err := downloader.GetIssues(1, 50)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, isEnd)
|
||||
assert.Len(t, issues, 7)
|
||||
assert.EqualValues(t, "open", issues[0].State)
|
||||
|
||||
issues, isEnd, err = downloader.GetIssues(3, 2)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, isEnd)
|
||||
|
||||
assertIssuesEqual(t, []*base.Issue{
|
||||
|
@ -198,7 +197,7 @@ func TestGiteaDownloadRepo(t *testing.T) {
|
|||
}, issues)
|
||||
|
||||
comments, _, err := downloader.GetComments(&base.Issue{Number: 4, ForeignIndex: 4})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertCommentsEqual(t, []*base.Comment{
|
||||
{
|
||||
IssueIndex: 4,
|
||||
|
@ -221,11 +220,11 @@ func TestGiteaDownloadRepo(t *testing.T) {
|
|||
}, comments)
|
||||
|
||||
prs, isEnd, err := downloader.GetPullRequests(1, 50)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, isEnd)
|
||||
assert.Len(t, prs, 6)
|
||||
prs, isEnd, err = downloader.GetPullRequests(1, 3)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, isEnd)
|
||||
assert.Len(t, prs, 3)
|
||||
assertPullRequestEqual(t, &base.PullRequest{
|
||||
|
@ -263,7 +262,7 @@ func TestGiteaDownloadRepo(t *testing.T) {
|
|||
}, prs[1])
|
||||
|
||||
reviews, err := downloader.GetReviews(&base.Issue{Number: 7, ForeignIndex: 7})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertReviewsEqual(t, []*base.Review{
|
||||
{
|
||||
ID: 1770,
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGiteaUploadRepo(t *testing.T) {
|
||||
|
@ -60,7 +61,7 @@ func TestGiteaUploadRepo(t *testing.T) {
|
|||
Private: true,
|
||||
Mirror: false,
|
||||
}, nil)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, Name: repoName})
|
||||
assert.True(t, repo.HasWiki())
|
||||
|
@ -70,18 +71,18 @@ func TestGiteaUploadRepo(t *testing.T) {
|
|||
RepoID: repo.ID,
|
||||
IsClosed: optional.Some(false),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, milestones, 1)
|
||||
|
||||
milestones, err = db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{
|
||||
RepoID: repo.ID,
|
||||
IsClosed: optional.Some(true),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, milestones)
|
||||
|
||||
labels, err := issues_model.GetLabelsByRepoID(ctx, repo.ID, "", db.ListOptions{})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, labels, 12)
|
||||
|
||||
releases, err := db.Find[repo_model.Release](db.DefaultContext, repo_model.FindReleasesOptions{
|
||||
|
@ -92,7 +93,7 @@ func TestGiteaUploadRepo(t *testing.T) {
|
|||
IncludeTags: true,
|
||||
RepoID: repo.ID,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, releases, 8)
|
||||
|
||||
releases, err = db.Find[repo_model.Release](db.DefaultContext, repo_model.FindReleasesOptions{
|
||||
|
@ -103,7 +104,7 @@ func TestGiteaUploadRepo(t *testing.T) {
|
|||
IncludeTags: false,
|
||||
RepoID: repo.ID,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, releases, 1)
|
||||
|
||||
issues, err := issues_model.Issues(db.DefaultContext, &issues_model.IssuesOptions{
|
||||
|
@ -111,18 +112,18 @@ func TestGiteaUploadRepo(t *testing.T) {
|
|||
IsPull: optional.Some(false),
|
||||
SortType: "oldest",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, issues, 15)
|
||||
assert.NoError(t, issues[0].LoadDiscussComments(db.DefaultContext))
|
||||
require.NoError(t, issues[0].LoadDiscussComments(db.DefaultContext))
|
||||
assert.Empty(t, issues[0].Comments)
|
||||
|
||||
pulls, _, err := issues_model.PullRequests(db.DefaultContext, repo.ID, &issues_model.PullRequestsOptions{
|
||||
SortType: "oldest",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, pulls, 30)
|
||||
assert.NoError(t, pulls[0].LoadIssue(db.DefaultContext))
|
||||
assert.NoError(t, pulls[0].Issue.LoadDiscussComments(db.DefaultContext))
|
||||
require.NoError(t, pulls[0].LoadIssue(db.DefaultContext))
|
||||
require.NoError(t, pulls[0].Issue.LoadDiscussComments(db.DefaultContext))
|
||||
assert.Len(t, pulls[0].Issue.Comments, 2)
|
||||
}
|
||||
|
||||
|
@ -150,7 +151,7 @@ func TestGiteaUploadRemapLocalUser(t *testing.T) {
|
|||
target := repo_model.Release{}
|
||||
uploader.userMap = make(map[int64]int64)
|
||||
err := uploader.remapUser(&source, &target)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, user_model.GhostUserID, target.GetUserID())
|
||||
|
||||
//
|
||||
|
@ -161,7 +162,7 @@ func TestGiteaUploadRemapLocalUser(t *testing.T) {
|
|||
target = repo_model.Release{}
|
||||
uploader.userMap = make(map[int64]int64)
|
||||
err = uploader.remapUser(&source, &target)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, user_model.GhostUserID, target.GetUserID())
|
||||
|
||||
//
|
||||
|
@ -172,7 +173,7 @@ func TestGiteaUploadRemapLocalUser(t *testing.T) {
|
|||
target = repo_model.Release{}
|
||||
uploader.userMap = make(map[int64]int64)
|
||||
err = uploader.remapUser(&source, &target)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, user.ID, target.GetUserID())
|
||||
}
|
||||
|
||||
|
@ -200,7 +201,7 @@ func TestGiteaUploadRemapExternalUser(t *testing.T) {
|
|||
uploader.userMap = make(map[int64]int64)
|
||||
target := repo_model.Release{}
|
||||
err := uploader.remapUser(&source, &target)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, user_model.GhostUserID, target.GetUserID())
|
||||
|
||||
//
|
||||
|
@ -214,7 +215,7 @@ func TestGiteaUploadRemapExternalUser(t *testing.T) {
|
|||
Provider: structs.GiteaService.Name(),
|
||||
}
|
||||
err = user_model.LinkExternalToUser(db.DefaultContext, linkedUser, externalLoginUser)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
//
|
||||
// When a user is linked to the external ID, it becomes the author of
|
||||
|
@ -223,7 +224,7 @@ func TestGiteaUploadRemapExternalUser(t *testing.T) {
|
|||
uploader.userMap = make(map[int64]int64)
|
||||
target = repo_model.Release{}
|
||||
err = uploader.remapUser(&source, &target)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, linkedUser.ID, target.GetUserID())
|
||||
}
|
||||
|
||||
|
@ -235,44 +236,44 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) {
|
|||
//
|
||||
fromRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||
baseRef := "master"
|
||||
assert.NoError(t, git.InitRepository(git.DefaultContext, fromRepo.RepoPath(), false, fromRepo.ObjectFormatName))
|
||||
require.NoError(t, git.InitRepository(git.DefaultContext, fromRepo.RepoPath(), false, fromRepo.ObjectFormatName))
|
||||
err := git.NewCommand(git.DefaultContext, "symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseRef).Run(&git.RunOpts{Dir: fromRepo.RepoPath()})
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", fromRepo.RepoPath())), 0o644))
|
||||
assert.NoError(t, git.AddChanges(fromRepo.RepoPath(), true))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", fromRepo.RepoPath())), 0o644))
|
||||
require.NoError(t, git.AddChanges(fromRepo.RepoPath(), true))
|
||||
signature := git.Signature{
|
||||
Email: "test@example.com",
|
||||
Name: "test",
|
||||
When: time.Now(),
|
||||
}
|
||||
assert.NoError(t, git.CommitChanges(fromRepo.RepoPath(), git.CommitChangesOptions{
|
||||
require.NoError(t, git.CommitChanges(fromRepo.RepoPath(), git.CommitChangesOptions{
|
||||
Committer: &signature,
|
||||
Author: &signature,
|
||||
Message: "Initial Commit",
|
||||
}))
|
||||
fromGitRepo, err := gitrepo.OpenRepository(git.DefaultContext, fromRepo)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
defer fromGitRepo.Close()
|
||||
baseSHA, err := fromGitRepo.GetBranchCommitID(baseRef)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
//
|
||||
// fromRepo branch1
|
||||
//
|
||||
headRef := "branch1"
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "checkout", "-b").AddDynamicArguments(headRef).RunStdString(&git.RunOpts{Dir: fromRepo.RepoPath()})
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte("SOMETHING"), 0o644))
|
||||
assert.NoError(t, git.AddChanges(fromRepo.RepoPath(), true))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte("SOMETHING"), 0o644))
|
||||
require.NoError(t, git.AddChanges(fromRepo.RepoPath(), true))
|
||||
signature.When = time.Now()
|
||||
assert.NoError(t, git.CommitChanges(fromRepo.RepoPath(), git.CommitChangesOptions{
|
||||
require.NoError(t, git.CommitChanges(fromRepo.RepoPath(), git.CommitChangesOptions{
|
||||
Committer: &signature,
|
||||
Author: &signature,
|
||||
Message: "Pull request",
|
||||
}))
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
headSHA, err := fromGitRepo.GetBranchCommitID(headRef)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
fromRepoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: fromRepo.OwnerID})
|
||||
|
||||
|
@ -281,28 +282,28 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) {
|
|||
//
|
||||
forkHeadRef := "branch2"
|
||||
forkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 8})
|
||||
assert.NoError(t, git.CloneWithArgs(git.DefaultContext, nil, fromRepo.RepoPath(), forkRepo.RepoPath(), git.CloneRepoOptions{
|
||||
require.NoError(t, git.CloneWithArgs(git.DefaultContext, nil, fromRepo.RepoPath(), forkRepo.RepoPath(), git.CloneRepoOptions{
|
||||
Branch: headRef,
|
||||
}))
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "checkout", "-b").AddDynamicArguments(forkHeadRef).RunStdString(&git.RunOpts{Dir: forkRepo.RepoPath()})
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, os.WriteFile(filepath.Join(forkRepo.RepoPath(), "README.md"), []byte(fmt.Sprintf("# branch2 %s", forkRepo.RepoPath())), 0o644))
|
||||
assert.NoError(t, git.AddChanges(forkRepo.RepoPath(), true))
|
||||
assert.NoError(t, git.CommitChanges(forkRepo.RepoPath(), git.CommitChangesOptions{
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.WriteFile(filepath.Join(forkRepo.RepoPath(), "README.md"), []byte(fmt.Sprintf("# branch2 %s", forkRepo.RepoPath())), 0o644))
|
||||
require.NoError(t, git.AddChanges(forkRepo.RepoPath(), true))
|
||||
require.NoError(t, git.CommitChanges(forkRepo.RepoPath(), git.CommitChangesOptions{
|
||||
Committer: &signature,
|
||||
Author: &signature,
|
||||
Message: "branch2 commit",
|
||||
}))
|
||||
forkGitRepo, err := gitrepo.OpenRepository(git.DefaultContext, forkRepo)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
defer forkGitRepo.Close()
|
||||
forkHeadSHA, err := forkGitRepo.GetBranchCommitID(forkHeadRef)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
toRepoName := "migrated"
|
||||
uploader := NewGiteaLocalUploader(context.Background(), fromRepoOwner, fromRepoOwner.Name, toRepoName)
|
||||
uploader.gitServiceType = structs.GiteaService
|
||||
assert.NoError(t, uploader.CreateRepo(&base.Repository{
|
||||
require.NoError(t, uploader.CreateRepo(&base.Repository{
|
||||
Description: "description",
|
||||
OriginalURL: fromRepo.RepoPath(),
|
||||
CloneURL: fromRepo.RepoPath(),
|
||||
|
@ -503,7 +504,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) {
|
|||
testCase.pr.EnsuredSafe = true
|
||||
|
||||
head, err := uploader.updateGitForPullRequest(&testCase.pr)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, testCase.head, head)
|
||||
|
||||
log.Info(stopMark)
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
base "code.gitea.io/gitea/modules/migration"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGitHubDownloadRepo(t *testing.T) {
|
||||
|
@ -23,10 +24,10 @@ func TestGitHubDownloadRepo(t *testing.T) {
|
|||
}
|
||||
downloader := NewGithubDownloaderV3(context.Background(), "https://github.com", "", "", token, "go-gitea", "test_repo")
|
||||
err := downloader.RefreshRate()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
repo, err := downloader.GetRepoInfo()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertRepositoryEqual(t, &base.Repository{
|
||||
Name: "test_repo",
|
||||
Owner: "go-gitea",
|
||||
|
@ -37,11 +38,11 @@ func TestGitHubDownloadRepo(t *testing.T) {
|
|||
}, repo)
|
||||
|
||||
topics, err := downloader.GetTopics()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, topics, "gitea")
|
||||
|
||||
milestones, err := downloader.GetMilestones()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertMilestonesEqual(t, []*base.Milestone{
|
||||
{
|
||||
Title: "1.0.0",
|
||||
|
@ -64,7 +65,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
|
|||
}, milestones)
|
||||
|
||||
labels, err := downloader.GetLabels()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertLabelsEqual(t, []*base.Label{
|
||||
{
|
||||
Name: "bug",
|
||||
|
@ -114,7 +115,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
|
|||
}, labels)
|
||||
|
||||
releases, err := downloader.GetReleases()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertReleasesEqual(t, []*base.Release{
|
||||
{
|
||||
TagName: "v0.9.99",
|
||||
|
@ -130,7 +131,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
|
|||
|
||||
// downloader.GetIssues()
|
||||
issues, isEnd, err := downloader.GetIssues(1, 2)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, isEnd)
|
||||
assertIssuesEqual(t, []*base.Issue{
|
||||
{
|
||||
|
@ -219,7 +220,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
|
|||
|
||||
// downloader.GetComments()
|
||||
comments, _, err := downloader.GetComments(&base.Issue{Number: 2, ForeignIndex: 2})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertCommentsEqual(t, []*base.Comment{
|
||||
{
|
||||
IssueIndex: 2,
|
||||
|
@ -249,7 +250,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
|
|||
|
||||
// downloader.GetPullRequests()
|
||||
prs, _, err := downloader.GetPullRequests(1, 2)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertPullRequestsEqual(t, []*base.PullRequest{
|
||||
{
|
||||
Number: 3,
|
||||
|
@ -339,7 +340,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
|
|||
}, prs)
|
||||
|
||||
reviews, err := downloader.GetReviews(&base.PullRequest{Number: 3, ForeignIndex: 3})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertReviewsEqual(t, []*base.Review{
|
||||
{
|
||||
ID: 315859956,
|
||||
|
@ -371,7 +372,7 @@ func TestGitHubDownloadRepo(t *testing.T) {
|
|||
}, reviews)
|
||||
|
||||
reviews, err = downloader.GetReviews(&base.PullRequest{Number: 4, ForeignIndex: 4})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertReviewsEqual(t, []*base.Review{
|
||||
{
|
||||
ID: 338338740,
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
base "code.gitea.io/gitea/modules/migration"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/xanzy/go-gitlab"
|
||||
)
|
||||
|
||||
|
@ -35,7 +36,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
|
|||
t.Fatalf("NewGitlabDownloader is nil: %v", err)
|
||||
}
|
||||
repo, err := downloader.GetRepoInfo()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
// Repo Owner is blank in Gitlab Group repos
|
||||
assertRepositoryEqual(t, &base.Repository{
|
||||
Name: "test_repo",
|
||||
|
@ -47,12 +48,12 @@ func TestGitlabDownloadRepo(t *testing.T) {
|
|||
}, repo)
|
||||
|
||||
topics, err := downloader.GetTopics()
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, len(topics) == 2)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, topics, 2)
|
||||
assert.EqualValues(t, []string{"migration", "test"}, topics)
|
||||
|
||||
milestones, err := downloader.GetMilestones()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertMilestonesEqual(t, []*base.Milestone{
|
||||
{
|
||||
Title: "1.1.0",
|
||||
|
@ -70,7 +71,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
|
|||
}, milestones)
|
||||
|
||||
labels, err := downloader.GetLabels()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertLabelsEqual(t, []*base.Label{
|
||||
{
|
||||
Name: "bug",
|
||||
|
@ -111,7 +112,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
|
|||
}, labels)
|
||||
|
||||
releases, err := downloader.GetReleases()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertReleasesEqual(t, []*base.Release{
|
||||
{
|
||||
TagName: "v0.9.99",
|
||||
|
@ -125,7 +126,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
|
|||
}, releases)
|
||||
|
||||
issues, isEnd, err := downloader.GetIssues(1, 2)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, isEnd)
|
||||
|
||||
assertIssuesEqual(t, []*base.Issue{
|
||||
|
@ -217,7 +218,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
|
|||
ForeignIndex: 2,
|
||||
Context: gitlabIssueContext{IsMergeRequest: false},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertCommentsEqual(t, []*base.Comment{
|
||||
{
|
||||
IssueIndex: 2,
|
||||
|
@ -254,7 +255,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
|
|||
}, comments)
|
||||
|
||||
prs, _, err := downloader.GetPullRequests(1, 1)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertPullRequestsEqual(t, []*base.PullRequest{
|
||||
{
|
||||
Number: 4,
|
||||
|
@ -303,7 +304,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
|
|||
}, prs)
|
||||
|
||||
rvs, err := downloader.GetReviews(&base.PullRequest{Number: 1, ForeignIndex: 1})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertReviewsEqual(t, []*base.Review{
|
||||
{
|
||||
IssueIndex: 1,
|
||||
|
@ -322,7 +323,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
|
|||
}, rvs)
|
||||
|
||||
rvs, err = downloader.GetReviews(&base.PullRequest{Number: 2, ForeignIndex: 2})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertReviewsEqual(t, []*base.Review{
|
||||
{
|
||||
IssueIndex: 2,
|
||||
|
@ -348,7 +349,7 @@ func TestGitlabSkippedIssueNumber(t *testing.T) {
|
|||
t.Fatalf("NewGitlabDownloader is nil: %v", err)
|
||||
}
|
||||
repo, err := downloader.GetRepoInfo()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertRepositoryEqual(t, &base.Repository{
|
||||
Name: "archbuild",
|
||||
Owner: "troyengel",
|
||||
|
@ -359,20 +360,20 @@ func TestGitlabSkippedIssueNumber(t *testing.T) {
|
|||
}, repo)
|
||||
|
||||
issues, isEnd, err := downloader.GetIssues(1, 10)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, isEnd)
|
||||
|
||||
// the only issue in this repository has number 2
|
||||
assert.EqualValues(t, 1, len(issues))
|
||||
assert.Len(t, issues, 1)
|
||||
assert.EqualValues(t, 2, issues[0].Number)
|
||||
assert.EqualValues(t, "vpn unlimited errors", issues[0].Title)
|
||||
|
||||
prs, _, err := downloader.GetPullRequests(1, 10)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
// the only merge request in this repository has number 1,
|
||||
// but we offset it by the maximum issue number so it becomes
|
||||
// pull request 3 in Forgejo
|
||||
assert.EqualValues(t, 1, len(prs))
|
||||
assert.Len(t, prs, 1)
|
||||
assert.EqualValues(t, 3, prs[0].Number)
|
||||
assert.EqualValues(t, "Review", prs[0].Title)
|
||||
}
|
||||
|
@ -507,7 +508,7 @@ func TestGitlabGetReviews(t *testing.T) {
|
|||
|
||||
id := int64(testCase.prID)
|
||||
rvs, err := downloader.GetReviews(&base.Issue{Number: id, ForeignIndex: id})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertReviewsEqual(t, []*base.Review{&review}, rvs)
|
||||
}
|
||||
}
|
||||
|
@ -541,7 +542,7 @@ func TestAwardsToReactions(t *testing.T) {
|
|||
]
|
||||
`
|
||||
var awards []*gitlab.AwardEmoji
|
||||
assert.NoError(t, json.Unmarshal([]byte(testResponse), &awards))
|
||||
require.NoError(t, json.Unmarshal([]byte(testResponse), &awards))
|
||||
|
||||
reactions := downloader.awardsToReactions(awards)
|
||||
assert.EqualValues(t, []*base.Reaction{
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
base "code.gitea.io/gitea/modules/migration"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGogsDownloadRepo(t *testing.T) {
|
||||
|
@ -31,7 +32,7 @@ func TestGogsDownloadRepo(t *testing.T) {
|
|||
|
||||
downloader := NewGogsDownloader(context.Background(), "https://try.gogs.io", "", "", gogsPersonalAccessToken, "lunnytest", "TESTREPO")
|
||||
repo, err := downloader.GetRepoInfo()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
assertRepositoryEqual(t, &base.Repository{
|
||||
Name: "TESTREPO",
|
||||
|
@ -43,7 +44,7 @@ func TestGogsDownloadRepo(t *testing.T) {
|
|||
}, repo)
|
||||
|
||||
milestones, err := downloader.GetMilestones()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertMilestonesEqual(t, []*base.Milestone{
|
||||
{
|
||||
Title: "1.0",
|
||||
|
@ -52,7 +53,7 @@ func TestGogsDownloadRepo(t *testing.T) {
|
|||
}, milestones)
|
||||
|
||||
labels, err := downloader.GetLabels()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertLabelsEqual(t, []*base.Label{
|
||||
{
|
||||
Name: "bug",
|
||||
|
@ -86,7 +87,7 @@ func TestGogsDownloadRepo(t *testing.T) {
|
|||
|
||||
// downloader.GetIssues()
|
||||
issues, isEnd, err := downloader.GetIssues(1, 8)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, isEnd)
|
||||
assertIssuesEqual(t, []*base.Issue{
|
||||
{
|
||||
|
@ -111,7 +112,7 @@ func TestGogsDownloadRepo(t *testing.T) {
|
|||
|
||||
// downloader.GetComments()
|
||||
comments, _, err := downloader.GetComments(&base.Issue{Number: 1, ForeignIndex: 1})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertCommentsEqual(t, []*base.Comment{
|
||||
{
|
||||
IssueIndex: 1,
|
||||
|
@ -135,7 +136,7 @@ func TestGogsDownloadRepo(t *testing.T) {
|
|||
|
||||
// downloader.GetPullRequests()
|
||||
_, _, err = downloader.GetPullRequests(1, 3)
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestGogsDownloaderFactory_New(t *testing.T) {
|
||||
|
|
|
@ -12,65 +12,65 @@ import (
|
|||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMigrateWhiteBlocklist(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user1"})
|
||||
nonAdminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
|
||||
|
||||
setting.Migrations.AllowedDomains = "github.com"
|
||||
setting.Migrations.AllowLocalNetworks = false
|
||||
assert.NoError(t, Init())
|
||||
require.NoError(t, Init())
|
||||
|
||||
err := IsMigrateURLAllowed("https://gitlab.com/gitlab/gitlab.git", nonAdminUser)
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
|
||||
err = IsMigrateURLAllowed("https://github.com/go-gitea/gitea.git", nonAdminUser)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = IsMigrateURLAllowed("https://gITHUb.com/go-gitea/gitea.git", nonAdminUser)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
setting.Migrations.AllowedDomains = ""
|
||||
setting.Migrations.BlockedDomains = "github.com"
|
||||
assert.NoError(t, Init())
|
||||
require.NoError(t, Init())
|
||||
|
||||
err = IsMigrateURLAllowed("https://gitlab.com/gitlab/gitlab.git", nonAdminUser)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = IsMigrateURLAllowed("https://github.com/go-gitea/gitea.git", nonAdminUser)
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
|
||||
err = IsMigrateURLAllowed("https://10.0.0.1/go-gitea/gitea.git", nonAdminUser)
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
|
||||
setting.Migrations.AllowLocalNetworks = true
|
||||
assert.NoError(t, Init())
|
||||
require.NoError(t, Init())
|
||||
err = IsMigrateURLAllowed("https://10.0.0.1/go-gitea/gitea.git", nonAdminUser)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
old := setting.ImportLocalPaths
|
||||
setting.ImportLocalPaths = false
|
||||
|
||||
err = IsMigrateURLAllowed("/home/foo/bar/goo", adminUser)
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
|
||||
setting.ImportLocalPaths = true
|
||||
abs, err := filepath.Abs(".")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = IsMigrateURLAllowed(abs, adminUser)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = IsMigrateURLAllowed(abs, nonAdminUser)
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
|
||||
nonAdminUser.AllowImportLocal = true
|
||||
err = IsMigrateURLAllowed(abs, nonAdminUser)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
setting.ImportLocalPaths = old
|
||||
}
|
||||
|
@ -80,35 +80,35 @@ func TestAllowBlockList(t *testing.T) {
|
|||
setting.Migrations.AllowedDomains = allow
|
||||
setting.Migrations.BlockedDomains = block
|
||||
setting.Migrations.AllowLocalNetworks = local
|
||||
assert.NoError(t, Init())
|
||||
require.NoError(t, Init())
|
||||
}
|
||||
|
||||
// default, allow all external, block none, no local networks
|
||||
init("", "", false)
|
||||
assert.NoError(t, checkByAllowBlockList("domain.com", []net.IP{net.ParseIP("1.2.3.4")}))
|
||||
assert.Error(t, checkByAllowBlockList("domain.com", []net.IP{net.ParseIP("127.0.0.1")}))
|
||||
require.NoError(t, checkByAllowBlockList("domain.com", []net.IP{net.ParseIP("1.2.3.4")}))
|
||||
require.Error(t, checkByAllowBlockList("domain.com", []net.IP{net.ParseIP("127.0.0.1")}))
|
||||
|
||||
// allow all including local networks (it could lead to SSRF in production)
|
||||
init("", "", true)
|
||||
assert.NoError(t, checkByAllowBlockList("domain.com", []net.IP{net.ParseIP("1.2.3.4")}))
|
||||
assert.NoError(t, checkByAllowBlockList("domain.com", []net.IP{net.ParseIP("127.0.0.1")}))
|
||||
require.NoError(t, checkByAllowBlockList("domain.com", []net.IP{net.ParseIP("1.2.3.4")}))
|
||||
require.NoError(t, checkByAllowBlockList("domain.com", []net.IP{net.ParseIP("127.0.0.1")}))
|
||||
|
||||
// allow wildcard, block some subdomains. if the domain name is allowed, then the local network check is skipped
|
||||
init("*.domain.com", "blocked.domain.com", false)
|
||||
assert.NoError(t, checkByAllowBlockList("sub.domain.com", []net.IP{net.ParseIP("1.2.3.4")}))
|
||||
assert.NoError(t, checkByAllowBlockList("sub.domain.com", []net.IP{net.ParseIP("127.0.0.1")}))
|
||||
assert.Error(t, checkByAllowBlockList("blocked.domain.com", []net.IP{net.ParseIP("1.2.3.4")}))
|
||||
assert.Error(t, checkByAllowBlockList("sub.other.com", []net.IP{net.ParseIP("1.2.3.4")}))
|
||||
require.NoError(t, checkByAllowBlockList("sub.domain.com", []net.IP{net.ParseIP("1.2.3.4")}))
|
||||
require.NoError(t, checkByAllowBlockList("sub.domain.com", []net.IP{net.ParseIP("127.0.0.1")}))
|
||||
require.Error(t, checkByAllowBlockList("blocked.domain.com", []net.IP{net.ParseIP("1.2.3.4")}))
|
||||
require.Error(t, checkByAllowBlockList("sub.other.com", []net.IP{net.ParseIP("1.2.3.4")}))
|
||||
|
||||
// allow wildcard (it could lead to SSRF in production)
|
||||
init("*", "", false)
|
||||
assert.NoError(t, checkByAllowBlockList("domain.com", []net.IP{net.ParseIP("1.2.3.4")}))
|
||||
assert.NoError(t, checkByAllowBlockList("domain.com", []net.IP{net.ParseIP("127.0.0.1")}))
|
||||
require.NoError(t, checkByAllowBlockList("domain.com", []net.IP{net.ParseIP("1.2.3.4")}))
|
||||
require.NoError(t, checkByAllowBlockList("domain.com", []net.IP{net.ParseIP("127.0.0.1")}))
|
||||
|
||||
// local network can still be blocked
|
||||
init("*", "127.0.0.*", false)
|
||||
assert.NoError(t, checkByAllowBlockList("domain.com", []net.IP{net.ParseIP("1.2.3.4")}))
|
||||
assert.Error(t, checkByAllowBlockList("domain.com", []net.IP{net.ParseIP("127.0.0.1")}))
|
||||
require.NoError(t, checkByAllowBlockList("domain.com", []net.IP{net.ParseIP("1.2.3.4")}))
|
||||
require.Error(t, checkByAllowBlockList("domain.com", []net.IP{net.ParseIP("127.0.0.1")}))
|
||||
|
||||
// reset
|
||||
init("", "", false)
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
base "code.gitea.io/gitea/modules/migration"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOneDevDownloadRepo(t *testing.T) {
|
||||
|
@ -27,7 +28,7 @@ func TestOneDevDownloadRepo(t *testing.T) {
|
|||
t.Fatalf("NewOneDevDownloader is nil: %v", err)
|
||||
}
|
||||
repo, err := downloader.GetRepoInfo()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertRepositoryEqual(t, &base.Repository{
|
||||
Name: "go-gitea-test_repo",
|
||||
Owner: "",
|
||||
|
@ -37,7 +38,7 @@ func TestOneDevDownloadRepo(t *testing.T) {
|
|||
}, repo)
|
||||
|
||||
milestones, err := downloader.GetMilestones()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
deadline := time.Unix(1620086400, 0)
|
||||
assertMilestonesEqual(t, []*base.Milestone{
|
||||
{
|
||||
|
@ -52,11 +53,11 @@ func TestOneDevDownloadRepo(t *testing.T) {
|
|||
}, milestones)
|
||||
|
||||
labels, err := downloader.GetLabels()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, labels, 6)
|
||||
|
||||
issues, isEnd, err := downloader.GetIssues(1, 2)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, isEnd)
|
||||
assertIssuesEqual(t, []*base.Issue{
|
||||
{
|
||||
|
@ -99,7 +100,7 @@ func TestOneDevDownloadRepo(t *testing.T) {
|
|||
ForeignIndex: 398,
|
||||
Context: onedevIssueContext{IsPullRequest: false},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertCommentsEqual(t, []*base.Comment{
|
||||
{
|
||||
IssueIndex: 4,
|
||||
|
@ -111,7 +112,7 @@ func TestOneDevDownloadRepo(t *testing.T) {
|
|||
}, comments)
|
||||
|
||||
prs, _, err := downloader.GetPullRequests(1, 1)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertPullRequestsEqual(t, []*base.PullRequest{
|
||||
{
|
||||
Number: 5,
|
||||
|
@ -137,7 +138,7 @@ func TestOneDevDownloadRepo(t *testing.T) {
|
|||
}, prs)
|
||||
|
||||
rvs, err := downloader.GetReviews(&base.PullRequest{Number: 5, ForeignIndex: 186})
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
assertReviewsEqual(t, []*base.Review{
|
||||
{
|
||||
IssueIndex: 5,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue