diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index 899209874f7cc..57c66dddbba55 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -1767,6 +1767,12 @@ LEVEL = Info
;;
;; convert \r\n to \n for Sendmail
;SENDMAIL_CONVERT_CRLF = true
+;;
+;; convert links of attached images to inline images. Only for images hosted in this gitea instance.
+;BASE64_EMBED_IMAGES = false
+;;
+;; The maximum size of sum of all images in a single email. Default is 9.5MB
+;BASE64_EMBED_IMAGES_MAX_SIZE_PER_EMAIL = 9961472
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/models/fixtures/attachment.yml b/models/fixtures/attachment.yml
index 7882d8bff2089..f6b5393b4dc65 100644
--- a/models/fixtures/attachment.yml
+++ b/models/fixtures/attachment.yml
@@ -153,3 +153,16 @@
download_count: 0
size: 0
created_unix: 946684800
+
+-
+ id: 13
+ uuid: 1b267670-1793-4cd0-abc1-449269b7cff9
+ repo_id: 1
+ issue_id: 23
+ release_id: 0
+ uploader_id: 0
+ comment_id: 2
+ name: gitea.png
+ download_count: 0
+ size: 1458
+ created_unix: 946684800
diff --git a/models/fixtures/issue.yml b/models/fixtures/issue.yml
index ca5b1c6cd1df5..c829c755296ed 100644
--- a/models/fixtures/issue.yml
+++ b/models/fixtures/issue.yml
@@ -372,3 +372,20 @@
created_unix: 1707270422
updated_unix: 1707270422
is_locked: false
+
+-
+ id: 23
+ repo_id: 1
+ index: 6
+ poster_id: 1
+ original_author_id: 0
+ name: issue23
+ content: 'content including this image: with some more content behind it'
+ milestone_id: 0
+ priority: 0
+ is_closed: false
+ is_pull: false
+ num_comments: 0
+ created_unix: 946684801
+ updated_unix: 978307201
+ is_locked: false
diff --git a/models/fixtures/issue_index.yml b/models/fixtures/issue_index.yml
index 5aabc08e388c5..c1e0b546a4013 100644
--- a/models/fixtures/issue_index.yml
+++ b/models/fixtures/issue_index.yml
@@ -1,6 +1,6 @@
-
group_id: 1
- max_index: 5
+ max_index: 6
-
group_id: 2
diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml
index 552a78cbd2773..47f9cb8a5d012 100644
--- a/models/fixtures/repository.yml
+++ b/models/fixtures/repository.yml
@@ -9,7 +9,7 @@
num_watches: 4
num_stars: 0
num_forks: 0
- num_issues: 2
+ num_issues: 3
num_closed_issues: 1
num_pulls: 3
num_closed_pulls: 0
diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go
index dbbb1e417901f..77c387d0e0d40 100644
--- a/models/issues/issue_test.go
+++ b/models/issues/issue_test.go
@@ -57,7 +57,7 @@ func Test_GetIssueIDsByRepoID(t *testing.T) {
ids, err := issues_model.GetIssueIDsByRepoID(db.DefaultContext, 1)
assert.NoError(t, err)
- assert.Len(t, ids, 5)
+ assert.Len(t, ids, 6)
}
func TestIssueAPIURL(t *testing.T) {
@@ -170,7 +170,7 @@ func TestIssues(t *testing.T) {
PageSize: 4,
},
},
- []int64{1, 2, 3, 5},
+ []int64{1, 23, 2, 3},
},
{
issues_model.IssuesOptions{
@@ -249,11 +249,11 @@ func TestIssue_InsertIssue(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
// there are 5 issues and max index is 5 on repository 1, so this one should 6
- issue := testInsertIssue(t, "my issue1", "special issue's comments?", 6)
+ issue := testInsertIssue(t, "my issue1", "special issue's comments?", 7)
_, err := db.DeleteByID[issues_model.Issue](db.DefaultContext, issue.ID)
assert.NoError(t, err)
- issue = testInsertIssue(t, `my issue2, this is my son's love \n \r \ `, "special issue's '' comments?", 7)
+ issue = testInsertIssue(t, `my issue2, this is my son's love \n \r \ `, "special issue's '' comments?", 8)
_, err = db.DeleteByID[issues_model.Issue](db.DefaultContext, issue.ID)
assert.NoError(t, err)
}
@@ -380,7 +380,7 @@ func TestCountIssues(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
count, err := issues_model.CountIssues(db.DefaultContext, &issues_model.IssuesOptions{})
assert.NoError(t, err)
- assert.EqualValues(t, 22, count)
+ assert.EqualValues(t, 23, count)
}
func TestIssueLoadAttributes(t *testing.T) {
diff --git a/models/issues/issue_user_test.go b/models/issues/issue_user_test.go
index 7c21aa15eef6a..fef599a36641a 100644
--- a/models/issues/issue_user_test.go
+++ b/models/issues/issue_user_test.go
@@ -22,7 +22,7 @@ func Test_NewIssueUsers(t *testing.T) {
newIssue := &issues_model.Issue{
RepoID: repo.ID,
PosterID: 4,
- Index: 6,
+ Index: 7,
Title: "newTestIssueTitle",
Content: "newTestIssueContent",
}
diff --git a/models/repo/attachment_test.go b/models/repo/attachment_test.go
index c059ffd39a91e..48313aa382235 100644
--- a/models/repo/attachment_test.go
+++ b/models/repo/attachment_test.go
@@ -51,7 +51,7 @@ func TestDeleteAttachments(t *testing.T) {
count, err = repo_model.DeleteAttachmentsByComment(db.DefaultContext, 2, false)
assert.NoError(t, err)
- assert.Equal(t, 2, count)
+ assert.Equal(t, 3, count)
err = repo_model.DeleteAttachment(db.DefaultContext, &repo_model.Attachment{ID: 8}, false)
assert.NoError(t, err)
diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go
index 7a9ca9698d0e9..f1ce2bd7d58ba 100644
--- a/models/unittest/testdb.go
+++ b/models/unittest/testdb.go
@@ -49,6 +49,7 @@ func InitSettings() {
setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini")
_ = os.Remove(setting.CustomConf)
}
+ log.Info("CustomConf: %s", setting.CustomConf)
setting.InitCfgProvider(setting.CustomConf)
setting.LoadCommonSettings()
@@ -72,6 +73,7 @@ type TestOptions struct {
// MainTest a reusable TestMain(..) function for unit tests that need to use a
// test database. Creates the test database, and sets necessary settings.
func MainTest(m *testing.M, testOptsArg ...*TestOptions) {
+ log.Info("MainTest Alive: %v\n", testOptsArg)
testOpts := util.OptionalArg(testOptsArg, &TestOptions{})
giteaRoot = test.SetupGiteaRoot()
setting.CustomPath = filepath.Join(giteaRoot, "custom")
@@ -101,12 +103,14 @@ func MainTest(m *testing.M, testOptsArg ...*TestOptions) {
if err != nil {
fatalTestError("TempDir: %v\n", err)
}
+ log.Info("Appdata Findme: %s", appDataPath)
setting.AppDataPath = appDataPath
setting.AppWorkPath = giteaRoot
setting.StaticRootPath = giteaRoot
setting.GravatarSource = "https://secure.gravatar.com/avatar/"
setting.Attachment.Storage.Path = filepath.Join(setting.AppDataPath, "attachments")
+ log.Info("Attachment Path: %s", setting.Attachment.Storage.Path)
setting.LFS.Storage.Path = filepath.Join(setting.AppDataPath, "lfs")
diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go
index 8043d33eebb88..736936012eb80 100644
--- a/modules/indexer/issues/indexer_test.go
+++ b/modules/indexer/issues/indexer_test.go
@@ -57,7 +57,7 @@ func searchIssueWithKeyword(t *testing.T) {
Keyword: "issue2",
RepoIDs: []int64{1},
},
- []int64{2},
+ []int64{2, 23},
},
{
SearchOptions{
@@ -106,7 +106,7 @@ func searchIssueByIndex(t *testing.T) {
Keyword: "2",
RepoIDs: []int64{1, 2, 3, 32},
},
- []int64{17, 12, 7, 2},
+ []int64{17, 12, 7, 2, 23},
},
{
SearchOptions{
@@ -133,7 +133,7 @@ func searchIssueInRepo(t *testing.T) {
SearchOptions{
RepoIDs: []int64{1},
},
- []int64{11, 5, 3, 2, 1},
+ []int64{11, 5, 3, 2, 23, 1},
},
{
SearchOptions{
@@ -177,7 +177,7 @@ func searchIssueByID(t *testing.T) {
opts: SearchOptions{
PosterID: optional.Some(int64(1)),
},
- expectedIDs: []int64{11, 6, 3, 2, 1},
+ expectedIDs: []int64{11, 6, 3, 2, 23, 1},
},
{
opts: SearchOptions{
@@ -188,7 +188,7 @@ func searchIssueByID(t *testing.T) {
{
// NOTE: This tests no assignees filtering and also ToSearchOptions() to ensure it will set AssigneeID to 0 when it is passed as -1.
opts: *ToSearchOptions("", &issues.IssuesOptions{AssigneeID: optional.Some(db.NoConditionID)}),
- expectedIDs: []int64{22, 21, 16, 15, 14, 13, 12, 11, 20, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2},
+ expectedIDs: []int64{22, 21, 16, 15, 14, 13, 12, 11, 20, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 23},
},
{
opts: SearchOptions{
@@ -212,7 +212,7 @@ func searchIssueByID(t *testing.T) {
opts: SearchOptions{
SubscriberID: optional.Some(int64(1)),
},
- expectedIDs: []int64{11, 6, 5, 3, 2, 1},
+ expectedIDs: []int64{11, 6, 5, 3, 2, 23, 1},
},
{
// issue 20 request user 15 and team 5 which user 15 belongs to
@@ -247,7 +247,7 @@ func searchIssueIsPull(t *testing.T) {
SearchOptions{
IsPull: optional.Some(false),
},
- []int64{17, 16, 15, 14, 13, 6, 5, 18, 10, 7, 4, 1},
+ []int64{17, 16, 15, 14, 13, 6, 5, 18, 10, 7, 4, 23, 1},
},
{
SearchOptions{
@@ -272,7 +272,7 @@ func searchIssueIsClosed(t *testing.T) {
SearchOptions{
IsClosed: optional.Some(false),
},
- []int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 19, 18, 10, 7, 9, 8, 3, 2, 1},
+ []int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 19, 18, 10, 7, 9, 8, 3, 2, 23, 1},
},
{
SearchOptions{
@@ -297,7 +297,7 @@ func searchIssueIsArchived(t *testing.T) {
SearchOptions{
IsArchived: optional.Some(false),
},
- []int64{22, 21, 17, 16, 15, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 1},
+ []int64{22, 21, 17, 16, 15, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 23, 1},
},
{
SearchOptions{
@@ -359,7 +359,7 @@ func searchIssueByLabelID(t *testing.T) {
SearchOptions{
ExcludedLabelIDs: []int64{1},
},
- []int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3},
+ []int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 23},
},
}
for _, test := range tests {
@@ -378,7 +378,7 @@ func searchIssueByTime(t *testing.T) {
SearchOptions{
UpdatedAfterUnix: optional.Some(int64(0)),
},
- []int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 1},
+ []int64{22, 21, 17, 16, 15, 14, 13, 12, 11, 20, 6, 5, 19, 18, 10, 7, 4, 9, 8, 3, 2, 23, 1},
},
}
for _, test := range tests {
@@ -397,7 +397,7 @@ func searchIssueWithOrder(t *testing.T) {
SearchOptions{
SortBy: internal.SortByCreatedAsc,
},
- []int64{1, 2, 3, 8, 9, 4, 7, 10, 18, 19, 5, 6, 20, 11, 12, 13, 14, 15, 16, 17, 21, 22},
+ []int64{1, 23, 2, 3, 8, 9, 4, 7, 10, 18, 19, 5, 6, 20, 11, 12, 13, 14, 15, 16, 17, 21, 22},
},
}
for _, test := range tests {
@@ -451,7 +451,7 @@ func searchIssueWithPaginator(t *testing.T) {
},
},
[]int64{22, 21, 17, 16, 15},
- 22,
+ 23,
},
}
for _, test := range tests {
diff --git a/modules/setting/mailer.go b/modules/setting/mailer.go
index 4c3dff6850947..a850ee3c0c0c3 100644
--- a/modules/setting/mailer.go
+++ b/modules/setting/mailer.go
@@ -13,21 +13,23 @@ import (
"code.gitea.io/gitea/modules/log"
- shellquote "github.com/kballard/go-shellquote"
+ "github.com/kballard/go-shellquote"
)
// Mailer represents mail service.
type Mailer struct {
// Mailer
- Name string `ini:"NAME"`
- From string `ini:"FROM"`
- EnvelopeFrom string `ini:"ENVELOPE_FROM"`
- OverrideEnvelopeFrom bool `ini:"-"`
- FromName string `ini:"-"`
- FromEmail string `ini:"-"`
- SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"`
- SubjectPrefix string `ini:"SUBJECT_PREFIX"`
- OverrideHeader map[string][]string `ini:"-"`
+ Name string `ini:"NAME"`
+ From string `ini:"FROM"`
+ EnvelopeFrom string `ini:"ENVELOPE_FROM"`
+ OverrideEnvelopeFrom bool `ini:"-"`
+ FromName string `ini:"-"`
+ FromEmail string `ini:"-"`
+ SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"`
+ SubjectPrefix string `ini:"SUBJECT_PREFIX"`
+ OverrideHeader map[string][]string `ini:"-"`
+ Base64EmbedImages bool `ini:"BASE64_EMBED_IMAGES"`
+ Base64EmbedImagesMaxSizePerEmail int64 `ini:"BASE64_EMBED_IMAGES_MAX_SIZE_PER_EMAIL"`
// SMTP sender
Protocol string `ini:"PROTOCOL"`
@@ -150,6 +152,8 @@ func loadMailerFrom(rootCfg ConfigProvider) {
sec.Key("SENDMAIL_TIMEOUT").MustDuration(5 * time.Minute)
sec.Key("SENDMAIL_CONVERT_CRLF").MustBool(true)
sec.Key("FROM").MustString(sec.Key("USER").String())
+ sec.Key("BASE64_EMBED_IMAGES").MustBool(false)
+ sec.Key("BASE64_EMBED_IMAGES_MAX_SIZE_PER_EMAIL").MustInt64(9.5 * 1024 * 1024)
// Now map the values on to the MailService
MailService = &Mailer{}
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index 20da796b58d3d..c76cc6906dea2 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -66,6 +66,7 @@ func PrepareAppDataPath() error {
// For quickstart, the parent directories should be created automatically for first startup (eg: a flag or a check of INSTALL_LOCK).
// Now we can take the first step to do correctly (using Mkdir) in other packages, and prepare the AppDataPath here, then make a refactor in future.
+ fmt.Printf("AppDataPath: %s\n", AppDataPath)
st, err := os.Stat(AppDataPath)
if os.IsNotExist(err) {
err = os.MkdirAll(AppDataPath, os.ModePerm)
diff --git a/modules/storage/azureblob.go b/modules/storage/azureblob.go
index 837afd0ba62b4..d6c880e6432a4 100644
--- a/modules/storage/azureblob.go
+++ b/modules/storage/azureblob.go
@@ -179,6 +179,7 @@ func (a *AzureBlobStorage) Open(path string) (Object, error) {
// Save saves a file to azure blob storage
func (a *AzureBlobStorage) Save(path string, r io.Reader, size int64) (int64, error) {
+ log.Info("AzureBlobStorage.Save(%s, %d)\n", path, size)
rd := util.NewCountingReader(r)
_, err := a.client.UploadStream(
a.ctx,
diff --git a/modules/storage/helper.go b/modules/storage/helper.go
index 9e6cceb537da7..97b7c7eb3f1f9 100644
--- a/modules/storage/helper.go
+++ b/modules/storage/helper.go
@@ -4,6 +4,7 @@
package storage
import (
+ "code.gitea.io/gitea/modules/log"
"fmt"
"io"
"net/url"
@@ -19,6 +20,7 @@ func (s discardStorage) Open(_ string) (Object, error) {
}
func (s discardStorage) Save(_ string, _ io.Reader, _ int64) (int64, error) {
+ log.Info("DiscardStorage.Save(%s)\n", s)
return 0, fmt.Errorf("%s", s)
}
diff --git a/modules/storage/local.go b/modules/storage/local.go
index 00c7f668aa2c3..71a6f136bc054 100644
--- a/modules/storage/local.go
+++ b/modules/storage/local.go
@@ -60,6 +60,7 @@ func (l *LocalStorage) Open(path string) (Object, error) {
// Save a file
func (l *LocalStorage) Save(path string, r io.Reader, size int64) (int64, error) {
+ log.Info("LocalStorage.Save(%s, %d) local\n", path, size)
p := l.buildLocalPath(path)
if err := os.MkdirAll(filepath.Dir(p), os.ModePerm); err != nil {
return 0, err
diff --git a/modules/storage/minio.go b/modules/storage/minio.go
index 6b92be61fb7d2..5013d015ba2e8 100644
--- a/modules/storage/minio.go
+++ b/modules/storage/minio.go
@@ -207,6 +207,7 @@ func (m *MinioStorage) Open(path string) (Object, error) {
// Save saves a file to minio
func (m *MinioStorage) Save(path string, r io.Reader, size int64) (int64, error) {
+ log.Info("MinioStorage.Save(%s, %d)\n", path, size)
uploadInfo, err := m.client.PutObject(
m.ctx,
m.bucket,
diff --git a/modules/storage/storage.go b/modules/storage/storage.go
index b0529941e7da4..4d4e6046b4621 100644
--- a/modules/storage/storage.go
+++ b/modules/storage/storage.go
@@ -169,12 +169,21 @@ func initAvatars() (err error) {
}
func initAttachments() (err error) {
+ log.Info("initAttachments")
if !setting.Attachment.Enabled {
Attachments = discardStorage("Attachment isn't enabled")
return nil
}
log.Info("Initialising Attachment storage with type: %s", setting.Attachment.Storage.Type)
Attachments, err = NewStorage(setting.Attachment.Storage.Type, setting.Attachment.Storage)
+ log.Info("storage FINDME: %v\n", setting.Attachment.Storage.Path)
+ // check if directory exists and print the result human readable
+ if _, err := os.Stat(setting.Attachment.Storage.Path); os.IsNotExist(err) {
+ log.Info("Attachment storage path does not exist: %s\n", setting.Attachment.Storage.Path)
+ } else {
+ log.Info("Attachment storage path exists: %s\n", setting.Attachment.Storage.Path)
+ }
+
return err
}
diff --git a/modules/test/utils.go b/modules/test/utils.go
index ec4c9763881f8..ba3ddcf44d6e5 100644
--- a/modules/test/utils.go
+++ b/modules/test/utils.go
@@ -4,6 +4,8 @@
package test
import (
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/util"
"fmt"
"net/http"
"net/http/httptest"
@@ -11,9 +13,6 @@ import (
"path/filepath"
"runtime"
"strings"
-
- "code.gitea.io/gitea/modules/json"
- "code.gitea.io/gitea/modules/util"
)
// RedirectURL returns the redirect URL of a http response.
@@ -50,11 +49,13 @@ func MockVariableValue[T any](p *T, v ...T) (reset func()) {
// SetupGiteaRoot Sets GITEA_ROOT if it is not already set and returns the value
func SetupGiteaRoot() string {
giteaRoot := os.Getenv("GITEA_ROOT")
+ fmt.Printf("GITEA_ROOT1: %s\n", giteaRoot)
if giteaRoot != "" {
return giteaRoot
}
_, filename, _, _ := runtime.Caller(0)
giteaRoot = filepath.Dir(filepath.Dir(filepath.Dir(filename)))
+ fmt.Printf("GITEA_ROOT2: %s\n", giteaRoot)
fixturesDir := filepath.Join(giteaRoot, "models", "fixtures")
if exist, _ := util.IsDir(fixturesDir); !exist {
panic(fmt.Sprintf("fixtures directory not found: %s", fixturesDir))
diff --git a/services/issue/issue_test.go b/services/issue/issue_test.go
index 8806cec0e7c5d..d7c850e6eb071 100644
--- a/services/issue/issue_test.go
+++ b/services/issue/issue_test.go
@@ -37,7 +37,7 @@ func TestIssue_DeleteIssue(t *testing.T) {
issueIDs, err := issues_model.GetIssueIDsByRepoID(db.DefaultContext, 1)
assert.NoError(t, err)
- assert.Len(t, issueIDs, 5)
+ assert.Len(t, issueIDs, 6)
issue := &issues_model.Issue{
RepoID: 1,
@@ -48,7 +48,7 @@ func TestIssue_DeleteIssue(t *testing.T) {
assert.NoError(t, err)
issueIDs, err = issues_model.GetIssueIDsByRepoID(db.DefaultContext, 1)
assert.NoError(t, err)
- assert.Len(t, issueIDs, 4)
+ assert.Len(t, issueIDs, 5)
// check attachment removal
attachments, err := repo_model.GetAttachmentsByIssueID(db.DefaultContext, 4)
diff --git a/services/issue/suggestion_test.go b/services/issue/suggestion_test.go
index 84cfd520ac40a..84771694c4626 100644
--- a/services/issue/suggestion_test.go
+++ b/services/issue/suggestion_test.go
@@ -26,7 +26,7 @@ func Test_Suggestion(t *testing.T) {
}{
{
keyword: "",
- expectedIndexes: []int64{5, 1, 4, 2, 3},
+ expectedIndexes: []int64{5, 6, 1, 4, 2},
},
{
keyword: "1",
@@ -34,7 +34,7 @@ func Test_Suggestion(t *testing.T) {
},
{
keyword: "issue",
- expectedIndexes: []int64{4, 1, 2, 3},
+ expectedIndexes: []int64{6, 4, 1, 2, 3},
},
{
keyword: "pull",
diff --git a/services/mailer/mail.go b/services/mailer/mail.go
index 52e19bde6f261..8bf991da13d87 100644
--- a/services/mailer/mail.go
+++ b/services/mailer/mail.go
@@ -7,9 +7,12 @@ package mailer
import (
"bytes"
"context"
+ "encoding/base64"
"fmt"
"html/template"
+ "io"
"mime"
+ "net/http"
"regexp"
"strconv"
"strings"
@@ -18,19 +21,24 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
issues_model "code.gitea.io/gitea/models/issues"
+ access_model "code.gitea.io/gitea/models/perm/access"
"code.gitea.io/gitea/models/renderhelper"
repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/emoji"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation"
incoming_payload "code.gitea.io/gitea/services/mailer/incoming/payload"
sender_service "code.gitea.io/gitea/services/mailer/sender"
"code.gitea.io/gitea/services/mailer/token"
+
+ "golang.org/x/net/html"
)
const (
@@ -228,6 +236,15 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
return nil, err
}
+ if setting.MailService.Base64EmbedImages {
+ bodyStr := string(body)
+ bodyStr, err = Base64InlineImages(bodyStr, ctx)
+ if err != nil {
+ return nil, err
+ }
+ body = template.HTML(bodyStr)
+ }
+
actType, actName, tplName := actionToTemplate(ctx.Issue, ctx.ActionType, commentType, reviewType)
if actName != "new" {
@@ -359,6 +376,110 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
return msgs, nil
}
+func Base64InlineImages(body string, ctx *mailCommentContext) (string, error) {
+ doc, err := html.Parse(strings.NewReader(body))
+ if err != nil {
+ log.Error("Failed to parse HTML body: %v", err)
+ return "", err
+ }
+
+ var totalEmbeddedImagesSize int64
+
+ var processNode func(*html.Node)
+ processNode = func(n *html.Node) {
+ if n.Type == html.ElementNode {
+ if n.Data == "img" {
+ for i, attr := range n.Attr {
+ if attr.Key == "src" {
+ attachmentPath := attr.Val
+ dataURI, err := AttachmentSrcToBase64DataURI(attachmentPath, ctx, &totalEmbeddedImagesSize)
+ if err != nil {
+ log.Trace("attachmentSrcToDataURI not possible: %v", err) // Not an error, just skip. This is probably an image from outside the gitea instance.
+ continue
+ }
+ log.Trace("Old value of src attribute: %s, new value (first 100 characters): %s", attr.Val, dataURI[:100])
+ n.Attr[i].Val = dataURI
+ break
+ }
+ }
+ }
+ }
+
+ for c := n.FirstChild; c != nil; c = c.NextSibling {
+ processNode(c)
+ }
+ }
+
+ processNode(doc)
+
+ var buf bytes.Buffer
+ err = html.Render(&buf, doc)
+ if err != nil {
+ log.Error("Failed to render modified HTML: %v", err)
+ return "", err
+ }
+ return buf.String(), nil
+}
+
+func AttachmentSrcToBase64DataURI(attachmentPath string, ctx *mailCommentContext, totalEmbeddedImagesSize *int64) (string, error) {
+ if !strings.HasPrefix(attachmentPath, setting.AppURL) { // external image
+ return "", fmt.Errorf("external image")
+ }
+ parts := strings.Split(attachmentPath, "/attachments/")
+ if len(parts) <= 1 {
+ return "", fmt.Errorf("invalid attachment path: %s", attachmentPath)
+ }
+
+ attachmentUUID := parts[len(parts)-1]
+ attachment, err := repo_model.GetAttachmentByUUID(ctx, attachmentUUID)
+ if err != nil {
+ return "", err
+ }
+
+ // "Doer" is theoretically not the correct permission check (as Doer created the action on which to send), but as this is batch processed the receipants can't be accessed.
+ // Therefore we check the Doer, with which we counter leaking information as a Doer brute force attack on attachments would be possible.
+ perm, err := access_model.GetUserRepoPermission(ctx, ctx.Issue.Repo, ctx.Doer)
+ if err != nil {
+ return "", err
+ }
+ if !perm.CanRead(unit.TypeIssues) {
+ return "", fmt.Errorf("no permission")
+ }
+
+ fr, err := storage.Attachments.Open(attachment.RelativePath())
+ if err != nil {
+ return "", err
+ }
+ defer fr.Close()
+
+ maxSize := setting.MailService.Base64EmbedImagesMaxSizePerEmail // at maximum read the whole available combined email size, to prevent maliciously large file reads
+
+ lr := &io.LimitedReader{R: fr, N: maxSize + 1}
+ content, err := io.ReadAll(lr)
+ if err != nil {
+ return "", err
+ }
+ if len(content) > int(maxSize) {
+ return "", fmt.Errorf("file size exceeds the embedded image max limit \\(%d bytes\\)", maxSize)
+ }
+
+ if *totalEmbeddedImagesSize+int64(len(content)) > setting.MailService.Base64EmbedImagesMaxSizePerEmail {
+ return "", fmt.Errorf("total embedded images exceed max limit: %d > %d", *totalEmbeddedImagesSize+int64(len(content)), setting.MailService.Base64EmbedImagesMaxSizePerEmail)
+ }
+ *totalEmbeddedImagesSize += int64(len(content))
+
+ mimeType := http.DetectContentType(content)
+
+ if !strings.HasPrefix(mimeType, "image/") {
+ return "", fmt.Errorf("not an image")
+ }
+
+ encoded := base64.StdEncoding.EncodeToString(content)
+ dataURI := fmt.Sprintf("data:%s;base64,%s", mimeType, encoded)
+
+ return dataURI, nil
+}
+
func generateMessageIDForIssue(issue *issues_model.Issue, comment *issues_model.Comment, actionType activities_model.ActionType) string {
var path string
if issue.IsPull {
diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go
index 8298ac4a34281..e1eac1b17c5d8 100644
--- a/services/mailer/mail_test.go
+++ b/services/mailer/mail_test.go
@@ -5,25 +5,28 @@ package mailer
import (
"bytes"
- "context"
- "fmt"
- "html/template"
- "io"
- "mime/quotedprintable"
- "regexp"
- "strings"
- "testing"
- texttmpl "text/template"
-
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/storage"
sender_service "code.gitea.io/gitea/services/mailer/sender"
+ "context"
+ "fmt"
+ "html/template"
+ "io"
+ "mime/quotedprintable"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "testing"
+ texttmpl "text/template"
"github.com/stretchr/testify/assert"
)
@@ -59,6 +62,7 @@ func prepareMailerTest(t *testing.T) (doer *user_model.User, repo *repo_model.Re
setting.MailService = &mailService
setting.Domain = "localhost"
+ setting.AppURL = "https://try.gitea.io/"
doer = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1, Owner: doer})
@@ -450,3 +454,193 @@ func TestFromDisplayName(t *testing.T) {
assert.EqualValues(t, "Mister X (by Code IT on [code.it])", fromDisplayName(&user_model.User{FullName: "Mister X", Name: "tmp"}))
})
}
+
+func WalkDirectory(dirPath string, level int) {
+ entries, err := os.ReadDir(dirPath)
+ if err != nil {
+ log.Info("Error reading directory %s: %v", dirPath, err)
+ return
+ }
+
+ // Calculate indentation for the current level.
+ indent := strings.Repeat(" ", level)
+
+ for _, entry := range entries {
+ // Print the current entry name with indentation.
+ log.Info("%s- %s\n", indent, entry.Name())
+
+ // If this is a directory, recurse into it.
+ if entry.IsDir() {
+ subDir := filepath.Join(dirPath, entry.Name())
+ WalkDirectory(subDir, level+1)
+ }
+ }
+}
+
+func PrepareAttachmentsStorage(t testing.TB) { // same as in test_utils.go
+ // prepare attachments directory and files
+ log.Info("delete attachments: %s\n", storage.Attachments)
+ // wait 30s for the attachments to be deleted
+ // <-time.After(30 * time.Second)
+ assert.NoError(t, storage.Clean(storage.Attachments))
+
+ s, err := storage.NewStorage(setting.LocalStorageType, &setting.Storage{
+ Path: filepath.Join(filepath.Dir(setting.AppPath), "tests", "testdata", "data", "attachments"),
+ })
+
+ // print all directory contents of: filepath.Join(filepath.Dir(setting.AppPath), "tests", "testdata", "data", "attachments")
+ targetPath := filepath.Join(filepath.Dir(setting.AppPath), "tests", "testdata", "data", "attachments")
+ log.Info("Listing: %s\n", targetPath)
+
+ WalkDirectory(targetPath, 0)
+
+ assert.NoError(t, err)
+ assert.NoError(t, s.IterateObjects("", func(p string, obj storage.Object) error {
+ _, err = storage.Copy(storage.Attachments, p, s, p)
+ log.Info("copying %s to %s\n", p, storage.Attachments)
+ return err
+ }))
+ log.Info("Attachments are: %s\n", storage.Attachments)
+ // <-time.After(30 * time.Second)
+
+}
+
+func TestEmbedBase64ImagesInEmail(t *testing.T) {
+ // Fake context setup
+
+ assert.NoError(t, unittest.LoadFixtures())
+ assert.NoError(t, unittest.PrepareTestDatabase())
+ PrepareAttachmentsStorage(t)
+
+ doer, repo, _, _ := prepareMailerTest(t)
+
+ attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 13, IssueID: 23, RepoID: repo.ID})
+
+ assert.Equal(t, attachment.IssueID, int64(23))
+
+ // load attachment content
+
+ fr, err := storage.Attachments.Open(attachment.RelativePath())
+ assert.NoError(t, err)
+ defer fr.Close()
+ maxSize := setting.MailService.Base64EmbedImagesMaxSizePerEmail
+ lr := &io.LimitedReader{R: fr, N: maxSize + 1}
+ content, err := io.ReadAll(lr)
+ assert.NoError(t, err)
+
+ //assert content length is above 0
+ assert.Greater(t, len(content), 0)
+
+ setting.MailService.Base64EmbedImages = true
+ setting.MailService.Base64EmbedImagesMaxSizePerEmail = 10 * 1024 * 1024
+ issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 23, Repo: repo, Poster: doer})
+ assert.NoError(t, issue.LoadRepo(db.DefaultContext))
+
+ subjectTemplates = texttmpl.Must(texttmpl.New("issue/new").Parse(subjectTpl))
+ bodyTemplates = template.Must(template.New("issue/new").Parse(bodyTpl))
+
+ recipients := []*user_model.User{{Name: "Test", Email: "test@gitea.com"}}
+ msgs, err := composeIssueCommentMessages(&mailCommentContext{
+ Context: context.TODO(), // TODO: use a correct context
+ Issue: issue, Doer: doer, ActionType: activities_model.ActionCreateIssue,
+ Content: strings.ReplaceAll(issue.Content, `src="`, `src="`+setting.AppURL),
+ }, "en-US", recipients, false, "issue create")
+
+ mailBody := msgs[0].Body
+ re := regexp.MustCompile(`(?s)
(.*?)`)
+ matches := re.FindStringSubmatch(mailBody)
+ if len(matches) > 1 {
+ mailBody = matches[1]
+ }
+ // check if the mail body was correctly generated
+ assert.NoError(t, err)
+ assert.Contains(t, mailBody, "content including this image")
+
+ // check if an image was embedded
+ assert.Contains(t, mailBody, "data:image/png;base64,")
+
+ // check if the image was embedded only once
+ assert.Equal(t, 1, strings.Count(mailBody, "data:image/png;base64,"))
+
+ img2InternalBase64 := "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAAxAQMAAAB3d7wRAAAABlBMVEVgmyF6qkqITHmkAAAAAXRSTlMBN+Ho8AAAAJhJREFUKM+V0DsOwyAQBNCxXLjkCFwk0t7McDQfhS4tpQuEzWc/iaUU2eo1zC4DUMWYF3DxVKzGTXjBGb2RsjJEo6ZhN1Zj+cEgi/9hBQl3YflkkIsbo5IO5glKTuhPpavM3Hp4C7WdjEWYrL5GMkp/R+s4GPlh/CZn4MEwv9aHHiyD3ujm5X22eaMyDa5yAm+O0B1TPa1l3W2qZWMg+KgtAAAAAElFTkSuQmCC"
+
+ // check if the image was embedded correctly
+ assert.Contains(t, mailBody, img2InternalBase64)
+}
+
+//func TestEmbedBase64Images(t *testing.T) {
+// assert.NoError(t, unittest.LoadFixtures())
+// assert.NoError(t, unittest.PrepareTestDatabase())
+// PrepareAttachmentsStorage(t)
+//
+// user, repo, _, _ := prepareMailerTest(t)
+// setting.MailService.Base64EmbedImages = true
+// setting.MailService.Base64EmbedImagesMaxSizePerEmail = 10 * 1024 * 1024
+//
+// issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 23, Repo: repo, Poster: user})
+//
+// attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 13, IssueID: issue.ID, RepoID: repo.ID})
+// ctx0 := context.Background()
+//
+// ctx := &mailCommentContext{Context: ctx0 /* TODO: use a correct context */, Issue: issue, Doer: user}
+//
+// img1ExternalURL := "https://via.placeholder.com/10"
+// img1ExternalImg := "
"
+//
+// img2InternalURL := setting.AppURL + repo.Owner.Name + "/" + repo.Name + "/attachments/" + attachment.UUID
+// img2InternalImg := "
"
+// img2InternalBase64 := "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAAxAQMAAAB3d7wRAAAABlBMVEVgmyF6qkqITHmkAAAAAXRSTlMBN+Ho8AAAAJhJREFUKM+V0DsOwyAQBNCxXLjkCFwk0t7McDQfhS4tpQuEzWc/iaUU2eo1zC4DUMWYF3DxVKzGTXjBGb2RsjJEo6ZhN1Zj+cEgi/9hBQl3YflkkIsbo5IO5glKTuhPpavM3Hp4C7WdjEWYrL5GMkp/R+s4GPlh/CZn4MEwv9aHHiyD3ujm5X22eaMyDa5yAm+O0B1TPa1l3W2qZWMg+KgtAAAAAElFTkSuQmCC"
+// img2InternalBase64Img := "
"
+//
+// // 1st Test: convert internal image to base64
+// t.Run("replaceSpecifiedBase64ImagesInternal", func(t *testing.T) {
+// totalEmbeddedImagesSize := int64(0)
+//
+// resultImg1Internal, err := AttachmentSrcToBase64DataURI(img2InternalURL, ctx, &totalEmbeddedImagesSize)
+// assert.NoError(t, err)
+// assert.Equal(t, img2InternalBase64, resultImg1Internal) // replace cause internal image
+// })
+//
+// // 2nd Test: convert external image to base64 -> abort cause external image
+// t.Run("replaceSpecifiedBase64ImagesExternal", func(t *testing.T) {
+// totalEmbeddedImagesSize := int64(0)
+//
+// resultImg1External, err := AttachmentSrcToBase64DataURI(img1ExternalURL, ctx, &totalEmbeddedImagesSize)
+// assert.Error(t, err)
+// assert.Equal(t, "", resultImg1External) // don't replace cause external image
+// })
+//
+// // 3rd Test: generate email body with 1 internal and 1 external image, expect the result to have the internal image replaced with base64 data and the external not replaced
+// t.Run("generateEmailBody", func(t *testing.T) {
+// mailBody := "Test1
" + img1ExternalImg + "Test2
" + img2InternalImg + "Test3
"
+// expectedMailBody := "Test1
" + img1ExternalImg + "Test2
" + img2InternalBase64Img + "Test3
"
+// resultMailBody, err := Base64InlineImages(mailBody, ctx)
+//
+// assert.NoError(t, err)
+// assert.Equal(t, expectedMailBody, resultMailBody)
+// })
+//
+// // 4th Test, generate email body with 2 internal images, but set Mailer.Base64EmbedImagesMaxSizePerEmail to the size of the first image (+1), expect the first image to be replaced and the second not
+// t.Run("generateEmailBodyWithMaxSize", func(t *testing.T) {
+// setting.MailService.Base64EmbedImagesMaxSizePerEmail = int64(len(img2InternalBase64) + 1)
+//
+// mailBody := "Test1
" + img2InternalImg + "Test2
" + img2InternalImg + "Test3
"
+// expectedMailBody := "Test1
" + img2InternalBase64Img + "Test2
" + img2InternalImg + "Test3
"
+// resultMailBody, err := Base64InlineImages(mailBody, ctx)
+//
+// assert.NoError(t, err)
+// assert.Equal(t, expectedMailBody, resultMailBody)
+// })
+//
+// // 5th Test, generate email body with 3 internal images, but set Mailer.Base64EmbedImagesMaxSizePerEmail to the size of all 3 images (+1), expect all images to be replaced
+// t.Run("generateEmailBodyWith3Images", func(t *testing.T) {
+// setting.MailService.Base64EmbedImagesMaxSizePerEmail = int64(len(img2InternalBase64)*3 + 1)
+//
+// mailBody := "Test1
" + img2InternalImg + "Test2
" + img2InternalImg + "Test3
" + img2InternalImg + ""
+// expectedMailBody := "Test1
" + img2InternalBase64Img + "Test2
" + img2InternalBase64Img + "Test3
" + img2InternalBase64Img + ""
+// resultMailBody, err := Base64InlineImages(mailBody, ctx)
+//
+// assert.NoError(t, err)
+// assert.Equal(t, expectedMailBody, resultMailBody)
+// })
+//}
diff --git a/tests/integration/api_issue_test.go b/tests/integration/api_issue_test.go
index d8394a33d96f4..8b81b7196f43c 100644
--- a/tests/integration/api_issue_test.go
+++ b/tests/integration/api_issue_test.go
@@ -287,7 +287,7 @@ func TestAPISearchIssues(t *testing.T) {
req = NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.Len(t, apiIssues, 15) // 15 public issues
+ assert.Len(t, apiIssues, 16) // 16 public issues
since := "2000-01-01T00:50:01+00:00" // 946687801
before := time.Unix(999307200, 0).Format(time.RFC3339)
@@ -297,7 +297,7 @@ func TestAPISearchIssues(t *testing.T) {
req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.Len(t, apiIssues, 11)
+ assert.Len(t, apiIssues, 12)
query.Del("since")
query.Del("before")
@@ -313,7 +313,7 @@ func TestAPISearchIssues(t *testing.T) {
req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.EqualValues(t, "22", resp.Header().Get("X-Total-Count"))
+ assert.EqualValues(t, "23", resp.Header().Get("X-Total-Count"))
assert.Len(t, apiIssues, 20)
query.Add("limit", "10")
@@ -321,7 +321,7 @@ func TestAPISearchIssues(t *testing.T) {
req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.EqualValues(t, "22", resp.Header().Get("X-Total-Count"))
+ assert.EqualValues(t, "23", resp.Header().Get("X-Total-Count"))
assert.Len(t, apiIssues, 10)
query = url.Values{"assigned": {"true"}, "state": {"all"}}
@@ -350,7 +350,7 @@ func TestAPISearchIssues(t *testing.T) {
req = NewRequest(t, "GET", link.String()).AddTokenAuth(token)
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.Len(t, apiIssues, 8)
+ assert.Len(t, apiIssues, 9)
query = url.Values{"owner": {"org3"}} // organization
link.RawQuery = query.Encode()
diff --git a/tests/integration/api_nodeinfo_test.go b/tests/integration/api_nodeinfo_test.go
index 75f8dbb4ba3aa..c6b4663b4a4d9 100644
--- a/tests/integration/api_nodeinfo_test.go
+++ b/tests/integration/api_nodeinfo_test.go
@@ -33,7 +33,7 @@ func TestNodeinfo(t *testing.T) {
assert.True(t, nodeinfo.OpenRegistrations)
assert.Equal(t, "gitea", nodeinfo.Software.Name)
assert.Equal(t, 29, nodeinfo.Usage.Users.Total)
- assert.Equal(t, 22, nodeinfo.Usage.LocalPosts)
+ assert.Equal(t, 23, nodeinfo.Usage.LocalPosts)
assert.Equal(t, 3, nodeinfo.Usage.LocalComments)
})
}
diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go
index 22f26d87d4332..5180e3d17ee57 100644
--- a/tests/integration/api_repo_test.go
+++ b/tests/integration/api_repo_test.go
@@ -268,7 +268,7 @@ func TestAPIViewRepo(t *testing.T) {
assert.EqualValues(t, 1, repo.ID)
assert.EqualValues(t, "repo1", repo.Name)
assert.EqualValues(t, 2, repo.Releases)
- assert.EqualValues(t, 1, repo.OpenIssues)
+ assert.EqualValues(t, 2, repo.OpenIssues)
assert.EqualValues(t, 3, repo.OpenPulls)
req = NewRequest(t, "GET", "/api/v1/repos/user12/repo10")
diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go
index bd0cedd300baf..d66e78c9ae4c6 100644
--- a/tests/integration/issue_test.go
+++ b/tests/integration/issue_test.go
@@ -495,7 +495,7 @@ func TestSearchIssues(t *testing.T) {
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.Len(t, apiIssues, 11)
+ assert.Len(t, apiIssues, 12)
query.Del("since")
query.Del("before")
@@ -511,7 +511,7 @@ func TestSearchIssues(t *testing.T) {
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.EqualValues(t, "22", resp.Header().Get("X-Total-Count"))
+ assert.EqualValues(t, "23", resp.Header().Get("X-Total-Count"))
assert.Len(t, apiIssues, 20)
query.Add("limit", "5")
@@ -519,7 +519,7 @@ func TestSearchIssues(t *testing.T) {
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.EqualValues(t, "22", resp.Header().Get("X-Total-Count"))
+ assert.EqualValues(t, "23", resp.Header().Get("X-Total-Count"))
assert.Len(t, apiIssues, 5)
query = url.Values{"assigned": {"true"}, "state": {"all"}}
@@ -548,7 +548,7 @@ func TestSearchIssues(t *testing.T) {
req = NewRequest(t, "GET", link.String())
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiIssues)
- assert.Len(t, apiIssues, 8)
+ assert.Len(t, apiIssues, 9)
query = url.Values{"owner": {"org3"}} // organization
link.RawQuery = query.Encode()
diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go
index 169df8618e89d..6128b8d8a72d4 100644
--- a/tests/integration/pull_merge_test.go
+++ b/tests/integration/pull_merge_test.go
@@ -924,7 +924,7 @@ func TestPullAutoMergeAfterCommitStatusSucceedAndApprovalForAgitFlow(t *testing.
Run(&git.RunOpts{Dir: dstPath, Stderr: stderrBuf})
assert.NoError(t, err)
- assert.Contains(t, stderrBuf.String(), setting.AppURL+"user2/repo1/pulls/6")
+ assert.Contains(t, stderrBuf.String(), setting.AppURL+"user2/repo1/pulls/7")
baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{
@@ -1044,7 +1044,7 @@ func TestPullNonMergeForAdminWithBranchProtection(t *testing.T) {
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
- mergeReq := NewRequestWithValues(t, "POST", "/api/v1/repos/user2/repo1/pulls/6/merge", map[string]string{
+ mergeReq := NewRequestWithValues(t, "POST", "/api/v1/repos/user2/repo1/pulls/7/merge", map[string]string{
"_csrf": csrf,
"head_commit_id": "",
"merge_when_checks_succeed": "false",
diff --git a/tests/test_utils.go b/tests/test_utils.go
index 96eb5731b4ac1..484dd08a979a9 100644
--- a/tests/test_utils.go
+++ b/tests/test_utils.go
@@ -38,7 +38,9 @@ func InitTest(requireGitea bool) {
// setting.UI.Notification.EventSourceUpdateTime = time.Second
setting.AppWorkPath = giteaRoot
+ log.Info("AppWorkPath: %s", setting.AppWorkPath)
setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom")
+ log.Info("CustomPath: %s", setting.CustomPath)
if requireGitea {
giteaBinary := "gitea"
if setting.IsWindows {
@@ -66,6 +68,8 @@ func InitTest(requireGitea bool) {
} else {
setting.CustomConf = giteaConf
}
+ log.Info("GiteaConf: %s", setting.CustomConf)
+ log.Info("CustomConf: %s", setting.CustomConf)
unittest.InitSettings()
setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master"
diff --git a/tests/testdata/data/attachments/1/b/1b267670-1793-4cd0-abc1-449269b7cff9 b/tests/testdata/data/attachments/1/b/1b267670-1793-4cd0-abc1-449269b7cff9
new file mode 100644
index 0000000000000..8fc22b562f026
Binary files /dev/null and b/tests/testdata/data/attachments/1/b/1b267670-1793-4cd0-abc1-449269b7cff9 differ