mirror of
https://github.com/Jinnrry/PMail.git
synced 2026-04-22 16:07:14 +08:00
+25
-1
@@ -1,3 +1,27 @@
|
||||
测试imap协议返回
|
||||
|
||||
`openssl s_client -crlf -connect imap.xxxx.com:993`
|
||||
`openssl s_client -crlf -connect imap.xxxx.com:993`
|
||||
`openssl s_client -crlf -connect 127.0.0.1:993`
|
||||
|
||||
|
||||
删除邮件
|
||||
```bash
|
||||
A1 LOGIN testCase testCase
|
||||
A3 SELECT "Deleted Messages"
|
||||
A4 UID STORE 1:* +FLAGS.SILENT (\Deleted)
|
||||
A5 EXPUNGE
|
||||
```
|
||||
|
||||
搜索邮件
|
||||
```bash
|
||||
A1 LOGIN testCase testCase
|
||||
114 SELECT "INBOX"
|
||||
115 UID SEARCH 1:5 NOT DELETED
|
||||
```
|
||||
|
||||
|
||||
执行全部测试用例(linux macos不加sudo没有权限监听小于1024的端口,因此会失败)
|
||||
`sudo env "PATH=$PATH" make test`
|
||||
|
||||
执行单个测试用例
|
||||
`go test -v -run ^Test ./services/del_email`
|
||||
@@ -16,9 +16,9 @@ import (
|
||||
|
||||
func GetUserGroupList(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
|
||||
defaultGroup := []*models.Group{
|
||||
{models.INBOX, i18n.GetText(ctx.Lang, "inbox"), 0, 0, "/"},
|
||||
{models.Junk, i18n.GetText(ctx.Lang, "junk"), 0, 0, "/"},
|
||||
{models.Deleted, i18n.GetText(ctx.Lang, "deleted"), 0, 0, "/"},
|
||||
{models.INBOX, i18n.GetText(ctx.Lang, "inbox"), 0, 0, "/"}, // 收件箱
|
||||
{models.Junk, i18n.GetText(ctx.Lang, "junk"), 0, 0, "/"}, //垃圾邮件
|
||||
{models.Deleted, i18n.GetText(ctx.Lang, "deleted"), 0, 0, "/"}, //已删除
|
||||
}
|
||||
|
||||
infos := group.GetGroupList(ctx)
|
||||
@@ -42,7 +42,7 @@ func GetUserGroup(ctx *context.Context, w http.ResponseWriter, req *http.Request
|
||||
},
|
||||
{
|
||||
Label: i18n.GetText(ctx.Lang, "sketch"),
|
||||
Tag: dto.SearchTag{Type: 1, Status: 0}.ToString(),
|
||||
Tag: dto.SearchTag{Type: 0, Status: 4}.ToString(),
|
||||
},
|
||||
{
|
||||
Label: i18n.GetText(ctx.Lang, "junk"),
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ var (
|
||||
"login_exp": "登录已失效",
|
||||
"ip_taps": "这是你服务器IP,确保这个IP正确",
|
||||
"invalid_email_address": "无效的邮箱地址!",
|
||||
"deleted": "垃圾箱",
|
||||
"deleted": "已删除",
|
||||
"junk": "广告箱",
|
||||
}
|
||||
en = map[string]string{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package http_server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/Jinnrry/pmail/config"
|
||||
@@ -11,7 +12,6 @@ import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -34,12 +34,7 @@ func SetupStart() {
|
||||
flag.Parse()
|
||||
|
||||
if HttpPort == 80 {
|
||||
envs := os.Environ()
|
||||
for _, env := range envs {
|
||||
if strings.HasPrefix(env, "setup_port=") {
|
||||
HttpPort = cast.ToInt(strings.TrimSpace(strings.ReplaceAll(env, "setup_port=", "")))
|
||||
}
|
||||
}
|
||||
HttpPort = cast.ToInt(os.Getenv("setup_port"))
|
||||
}
|
||||
|
||||
if HttpPort <= 0 || HttpPort > 65535 {
|
||||
@@ -61,7 +56,7 @@ func SetupStart() {
|
||||
WriteTimeout: time.Second * 60,
|
||||
}
|
||||
err = setupServer.ListenAndServe()
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,23 +376,32 @@ func TestSearch(t *testing.T) {
|
||||
func TestMove(t *testing.T) {
|
||||
clientLogin.Select("INBOX", &imap.SelectOptions{}).Wait()
|
||||
|
||||
_, err := clientLogin.Move(imap.UIDSetNum(21), "Junk").Wait()
|
||||
res, err := clientLogin.Fetch(imap.SeqSetNum(1), &imap.FetchOptions{
|
||||
Envelope: true,
|
||||
Flags: true,
|
||||
InternalDate: true,
|
||||
RFC822Size: true,
|
||||
UID: true,
|
||||
BodySection: []*imap.FetchItemBodySection{
|
||||
{
|
||||
Specifier: imap.PartSpecifierText,
|
||||
Peek: true,
|
||||
},
|
||||
},
|
||||
}).Collect()
|
||||
if err != nil {
|
||||
t.Errorf("%+v", err)
|
||||
t.Logf("%+v", res)
|
||||
t.Error("Fetch error")
|
||||
}
|
||||
_, err = clientLogin.Move(imap.UIDSetNum(23), "一级菜单").Wait()
|
||||
if err != nil {
|
||||
t.Errorf("%+v", err)
|
||||
}
|
||||
var ue []models.UserEmail
|
||||
db.Instance.Table("user_email").Where("id=21 or id=23").Find(&ue)
|
||||
for _, v := range ue {
|
||||
if v.ID == 21 && (v.GroupId != 0 || v.Status != 5) {
|
||||
t.Errorf("TestMove Error")
|
||||
}
|
||||
if v.ID == 23 && v.GroupId != 4 {
|
||||
t.Errorf("TestMove Error")
|
||||
|
||||
if len(res) > 0 {
|
||||
uid := res[0].UID
|
||||
|
||||
_, err = clientLogin.Move(imap.UIDSetNum(uid), "Junk").Wait()
|
||||
if err != nil {
|
||||
t.Errorf("%+v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -400,14 +409,31 @@ func TestMove(t *testing.T) {
|
||||
func TestCopy(t *testing.T) {
|
||||
clientLogin.Select("INBOX", &imap.SelectOptions{}).Wait()
|
||||
|
||||
_, err := clientLogin.Copy(imap.UIDSetNum(25), "Junk").Wait()
|
||||
res, err := clientLogin.Fetch(imap.SeqSetNum(1), &imap.FetchOptions{
|
||||
Envelope: true,
|
||||
Flags: true,
|
||||
InternalDate: true,
|
||||
RFC822Size: true,
|
||||
UID: true,
|
||||
BodySection: []*imap.FetchItemBodySection{
|
||||
{
|
||||
Specifier: imap.PartSpecifierText,
|
||||
Peek: true,
|
||||
},
|
||||
},
|
||||
}).Collect()
|
||||
if err != nil {
|
||||
t.Errorf("%+v", err)
|
||||
t.Logf("%+v", res)
|
||||
t.Error("Fetch error")
|
||||
}
|
||||
|
||||
_, err = clientLogin.Copy(imap.UIDSetNum(27), "一级菜单").Wait()
|
||||
if err != nil {
|
||||
t.Errorf("%+v", err)
|
||||
if len(res) > 0 {
|
||||
_, err = clientLogin.Copy(imap.UIDSetNum(res[0].UID), "Junk").Wait()
|
||||
if err != nil {
|
||||
t.Errorf("%+v", err)
|
||||
}
|
||||
} else {
|
||||
t.Error("No Fetch Result")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/imapserver"
|
||||
"github.com/spf13/cast"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
func (s *serverSession) Expunge(w *imapserver.ExpungeWriter, uids *imap.UIDSet) error {
|
||||
@@ -31,6 +32,8 @@ func (s *serverSession) Expunge(w *imapserver.ExpungeWriter, uids *imap.UIDSet)
|
||||
return nil
|
||||
}
|
||||
|
||||
slog.Debug("DeleteUidList:", slog.Any("uidList", uidList))
|
||||
|
||||
err := del_email.DelByUID(s.ctx, uidList)
|
||||
s.deleteUidList = []int{}
|
||||
if err != nil {
|
||||
|
||||
+101
-2
@@ -37,10 +37,12 @@ func TestMain(m *testing.M) {
|
||||
httpClient = &http.Client{Jar: cookeieJar, Timeout: 5 * time.Minute}
|
||||
os.Remove("config/config.json")
|
||||
os.Remove("config/pmail_temp.db")
|
||||
os.Setenv("setup_port", cast.ToString(TestPort))
|
||||
|
||||
go func() {
|
||||
main()
|
||||
}()
|
||||
time.Sleep(3 * time.Second)
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
m.Run()
|
||||
|
||||
@@ -79,6 +81,7 @@ func TestMaster(t *testing.T) {
|
||||
t.Run("testDelEmail", testDelEmail)
|
||||
|
||||
t.Run("testSendEmail2User1", testSendEmail2User1)
|
||||
t.Run("testSendEmail2User12", testSendEmail2User12)
|
||||
t.Run("testSendEmail2User2", testSendEmail2User2)
|
||||
t.Run("testSendEmail2User3", testSendEmail2User3)
|
||||
time.Sleep(8 * time.Second)
|
||||
@@ -89,6 +92,8 @@ func TestMaster(t *testing.T) {
|
||||
|
||||
t.Run("testUser2EmailList", testUser2EmailList)
|
||||
|
||||
t.Run("testUser2DelEmail", testUser2DelEmail) // 删除2个人共同拥有的邮件
|
||||
|
||||
// 创建group
|
||||
t.Run("testCreateGroup", testCreateGroup)
|
||||
|
||||
@@ -708,6 +713,46 @@ func genTestEmailData(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func testSendEmail2User12(t *testing.T) {
|
||||
ret, err := httpClient.Post(TestHost+"/api/email/send", "application/json", strings.NewReader(`
|
||||
{
|
||||
"from": {
|
||||
"name": "i",
|
||||
"email": "i@test.domain"
|
||||
},
|
||||
"to": [
|
||||
{
|
||||
"name": "y",
|
||||
"email": "user1@test.domain"
|
||||
},
|
||||
{
|
||||
"name": "y2",
|
||||
"email": "user2@test.domain"
|
||||
}
|
||||
],
|
||||
"cc": [
|
||||
|
||||
],
|
||||
"subject": "HelloUser1User2",
|
||||
"text": "HelloUser1User2",
|
||||
"html": "<div>HelloUser1User2</div>"
|
||||
}
|
||||
|
||||
`))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
data, err := readResponse(ret.Body)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if data.ErrorNo != 0 {
|
||||
t.Error("Send Email Api Error!")
|
||||
}
|
||||
|
||||
t.Logf("testSendEmail2User1 Success! Response: %+v", data)
|
||||
}
|
||||
|
||||
func testSendEmail2User1(t *testing.T) {
|
||||
ret, err := httpClient.Post(TestHost+"/api/email/send", "application/json", strings.NewReader(`
|
||||
{
|
||||
@@ -859,7 +904,7 @@ func testUser2EmailList(t *testing.T) {
|
||||
}
|
||||
dt := data.Data.(map[string]interface{})
|
||||
|
||||
if dt["list"] == nil || len(dt["list"].([]interface{})) != 1 {
|
||||
if dt["list"] == nil || len(dt["list"].([]interface{})) != 2 {
|
||||
t.Error("Email List Is Empty!")
|
||||
}
|
||||
|
||||
@@ -909,6 +954,60 @@ func testDelEmail(t *testing.T) {
|
||||
t.Logf("testDelEmail Success! Response: %+v", data)
|
||||
}
|
||||
|
||||
func testUser2DelEmail(t *testing.T) {
|
||||
ret, err := httpClient.Post(TestHost+"/api/email/list", "application/json", strings.NewReader(`{}`))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
data, err := readResponse(ret.Body)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if data.ErrorNo != 0 {
|
||||
t.Error("Get Email List Api Error!")
|
||||
}
|
||||
dt := data.Data.(map[string]interface{})
|
||||
if len(dt["list"].([]interface{})) == 0 {
|
||||
t.Error("Email List Is Empty!")
|
||||
}
|
||||
lst := dt["list"].([]interface{})
|
||||
|
||||
for _, item := range lst {
|
||||
// 删除两个用户的邮件
|
||||
title := cast.ToString(item.(map[string]interface{})["title"])
|
||||
id := cast.ToInt(item.(map[string]interface{})["id"])
|
||||
if title == "HelloUser1User2" {
|
||||
ret, err = httpClient.Post(TestHost+"/api/email/del", "application/json", strings.NewReader(fmt.Sprintf(`{
|
||||
"ids":[%d]
|
||||
}`, id)))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
data, err = readResponse(ret.Body)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if data.ErrorNo != 0 {
|
||||
t.Error("Email Delete Api Error!")
|
||||
}
|
||||
var mails []models.UserEmail
|
||||
db.Instance.Where("email_id = ?", id).Find(&mails)
|
||||
for _, mail := range mails {
|
||||
if mail.Status != 3 && mail.UserID == 3 {
|
||||
t.Error("Email Delete Api Error!")
|
||||
}
|
||||
if mail.UserID != 3 && mail.Status == 3 {
|
||||
t.Error("Email Delete Api Error!")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
t.Logf("testDelEmail Success! Response: %+v", data)
|
||||
}
|
||||
|
||||
// portCheck 检查端口是占用
|
||||
func portCheck(port int) bool {
|
||||
l, err := net.Listen("tcp", fmt.Sprintf(":%s", strconv.Itoa(port)))
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/Jinnrry/pmail/utils/context"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cast"
|
||||
"log/slog"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
import . "xorm.io/builder"
|
||||
@@ -64,7 +65,7 @@ func DelByUID(ctx *context.Context, ids []int) error {
|
||||
defer session.Close()
|
||||
for _, id := range ids {
|
||||
var ue models.UserEmail
|
||||
session.Table("user_email").Where(Eq{"id": ids, "user_id": ctx.UserID}).Get(&ue)
|
||||
session.Table("user_email").Where(Eq{"id": id, "user_id": ctx.UserID}).Get(&ue)
|
||||
if ue.ID == 0 {
|
||||
log.WithContext(ctx).Warn("no user email found")
|
||||
return nil
|
||||
@@ -74,6 +75,7 @@ func DelByUID(ctx *context.Context, ids []int) error {
|
||||
// 先删除关联关系
|
||||
_, err := session.Table(&models.UserEmail{}).Where("id=? and user_id=?", id, ctx.UserID).Delete(&ue)
|
||||
if err != nil {
|
||||
slog.Error("SQLError", slog.Any("err", err))
|
||||
session.Rollback()
|
||||
return err
|
||||
}
|
||||
@@ -82,12 +84,16 @@ func DelByUID(ctx *context.Context, ids []int) error {
|
||||
var Num num
|
||||
_, err = session.Table(&models.UserEmail{}).Select("count(1) as num").Where("email_id=? ", emailId).Get(&Num)
|
||||
if err != nil {
|
||||
slog.Error("SQLError", slog.Any("err", err))
|
||||
session.Rollback()
|
||||
return err
|
||||
}
|
||||
if Num.Num == 0 {
|
||||
var email models.Email
|
||||
_, err = session.Table(&email).Where("id=?", id).Delete(&email)
|
||||
|
||||
_, err = session.Table(&email).Where("id=?", emailId).Delete(&email)
|
||||
if err != nil {
|
||||
slog.Error("SQLError", slog.Any("err", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
session.Commit()
|
||||
|
||||
@@ -218,7 +218,7 @@ func GetGroupStatus(ctx *context.Context, groupName string, params []string) (st
|
||||
case "MESSAGES":
|
||||
db.Instance.Table("user_email").Select("count(1)").Where("group_id=?", group.ID).Get(&value)
|
||||
case "UIDNEXT":
|
||||
db.Instance.Table("user_email").Select("id").OrderBy("id desc").Get(&value)
|
||||
db.Instance.Table("user_email").Select("id").Where("group_id=?", group.ID).OrderBy("id desc").Get(&value)
|
||||
value += 1
|
||||
case "UIDVALIDITY":
|
||||
value = group.ID
|
||||
@@ -242,8 +242,7 @@ func GetGroupStatus(ctx *context.Context, groupName string, params []string) (st
|
||||
case "MESSAGES":
|
||||
value = getGroupNum(ctx, groupName, false)
|
||||
case "UIDNEXT":
|
||||
db.Instance.Table("user_email").Select("id").OrderBy("id desc").Get(&value)
|
||||
value += 1
|
||||
value = getNextUID(ctx, groupName)
|
||||
case "UIDVALIDITY":
|
||||
value = models.GroupNameToCode[groupName]
|
||||
case "UNSEEN":
|
||||
@@ -262,6 +261,23 @@ func GetGroupStatus(ctx *context.Context, groupName string, params []string) (st
|
||||
|
||||
}
|
||||
|
||||
func getNextUID(ctx *context.Context, groupName string) int {
|
||||
var lastId int
|
||||
switch groupName {
|
||||
case "INBOX":
|
||||
db.Instance.Table("user_email").Select("id").Where("user_id=? and group_id=0 and status = 0", ctx.UserID).OrderBy("id desc").Get(&lastId)
|
||||
case "Sent Messages":
|
||||
db.Instance.Table("user_email").Select("id").Where("user_id=? and group_id=0 and status = 1", ctx.UserID).OrderBy("id desc").Get(&lastId)
|
||||
case "Drafts":
|
||||
db.Instance.Table("user_email").Select("id").Where("user_id=? and group_id=0 and status = 4", ctx.UserID).OrderBy("id desc").Get(&lastId)
|
||||
case "Deleted Messages":
|
||||
db.Instance.Table("user_email").Select("id").Where("user_id=? and status = 3", ctx.UserID).OrderBy("id desc").Get(&lastId)
|
||||
case "Junk":
|
||||
db.Instance.Table("user_email").Select("id").Where("user_id=? and group_id=0 and status = 5", ctx.UserID).OrderBy("id desc").Get(&lastId)
|
||||
}
|
||||
return lastId + 1
|
||||
}
|
||||
|
||||
func getGroupNum(ctx *context.Context, groupName string, mustUnread bool) int {
|
||||
var count int
|
||||
switch groupName {
|
||||
|
||||
@@ -167,15 +167,42 @@ func GetUEListByUID(ctx *context.Context, groupName string, star, end int, uidLi
|
||||
func getEmailListByUidList(ctx *context.Context, groupName string, req ImapListReq, uid bool) []*response.EmailResponseData {
|
||||
var ret []*response.EmailResponseData
|
||||
var ue []*response.UserEmailUIDData
|
||||
sql := fmt.Sprintf("SELECT id,email_id, is_read, ROW_NUMBER() OVER (ORDER BY id) AS serial_number FROM `user_email` WHERE (user_id = ? and id in (%s))", array.Join(req.UidList, ","))
|
||||
sql := fmt.Sprintf("SELECT id,email_id, is_read, ROW_NUMBER() OVER (ORDER BY id) AS serial_number FROM `user_email` WHERE (user_id = ? and id in (%s) and status = ?)", array.Join(req.UidList, ","))
|
||||
if req.Star > 0 && req.End != 0 {
|
||||
sql = fmt.Sprintf("SELECT id,email_id, is_read, ROW_NUMBER() OVER (ORDER BY id) AS serial_number FROM `user_email` WHERE (user_id = ? and id >=%d and id <= %d)", req.Star, req.End)
|
||||
sql = fmt.Sprintf("SELECT id,email_id, is_read, ROW_NUMBER() OVER (ORDER BY id) AS serial_number FROM `user_email` WHERE (user_id = ? and id >=%d and id <= %d and status = ?)", req.Star, req.End)
|
||||
}
|
||||
if req.Star > 0 && req.End == 0 {
|
||||
sql = fmt.Sprintf("SELECT id,email_id, is_read, ROW_NUMBER() OVER (ORDER BY id) AS serial_number FROM `user_email` WHERE (user_id = ? and id >=%d )", req.Star)
|
||||
sql = fmt.Sprintf("SELECT id,email_id, is_read, ROW_NUMBER() OVER (ORDER BY id) AS serial_number FROM `user_email` WHERE (user_id = ? and id >=%d and status = ?)", req.Star)
|
||||
}
|
||||
|
||||
var err error
|
||||
switch groupName {
|
||||
case "INBOX":
|
||||
err = db.Instance.SQL(sql, ctx.UserID, 0).Find(&ue)
|
||||
case "Sent Messages":
|
||||
err = db.Instance.SQL(sql, ctx.UserID, 1).Find(&ue)
|
||||
case "Drafts":
|
||||
err = db.Instance.SQL(sql, ctx.UserID, 4).Find(&ue)
|
||||
case "Deleted Messages":
|
||||
err = db.Instance.SQL(sql, ctx.UserID, 3).Find(&ue)
|
||||
case "Junk":
|
||||
err = db.Instance.SQL(sql, ctx.UserID, 5).Find(&ue)
|
||||
default:
|
||||
groupNames := strings.Split(groupName, "/")
|
||||
groupName = groupNames[len(groupNames)-1]
|
||||
|
||||
var group models.Group
|
||||
db.Instance.Table("group").Where("user_id=? and name=?", ctx.UserID, groupName).Get(&group)
|
||||
if group.ID == 0 {
|
||||
return ret
|
||||
}
|
||||
err = db.Instance.
|
||||
SQL(fmt.Sprintf(
|
||||
"SELECT * from (SELECT id,email_id, is_read, ROW_NUMBER() OVER (ORDER BY id) AS serial_number FROM `user_email` WHERE (user_id = ? and group_id = ?)) a WHERE serial_number in (%s)",
|
||||
array.Join(req.UidList, ","))).
|
||||
Find(&ue, ctx.UserID, group.ID)
|
||||
}
|
||||
|
||||
err := db.Instance.SQL(sql, ctx.UserID).Find(&ue)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Errorf("SQL ERROR: %s ,Error:%s", sql, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user