240 lines
8.0 KiB
Go
240 lines
8.0 KiB
Go
package email
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"testing"
|
||
"time"
|
||
|
||
"git.pcux.cn/xuans/Smail/db"
|
||
"git.pcux.cn/xuans/Smail/models"
|
||
"git.pcux.cn/xuans/Smail/utils/context"
|
||
_ "modernc.org/sqlite"
|
||
"xorm.io/xorm"
|
||
)
|
||
|
||
func setupContactsTestDB(t *testing.T) {
|
||
engine, err := xorm.NewEngine("sqlite", ":memory:")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := engine.Sync2(&models.UserContact{}, &models.UserTrustedSender{}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
db.Instance = engine
|
||
}
|
||
|
||
func TestContactsListAndTenantIsolation(t *testing.T) {
|
||
setupContactsTestDB(t)
|
||
|
||
// User 1 联系人
|
||
_, _ = db.Instance.Insert(&models.UserContact{
|
||
UserID: 1,
|
||
Email: "user1_friend@example.com",
|
||
Name: "Friend One",
|
||
LastUsedAt: time.Now(),
|
||
})
|
||
// User 2 联系人(隔离测试)
|
||
_, _ = db.Instance.Insert(&models.UserContact{
|
||
UserID: 2,
|
||
Email: "user2_secret@example.com",
|
||
Name: "Friend Two",
|
||
LastUsedAt: time.Now(),
|
||
})
|
||
|
||
// 测试 User 1 查询联系人,只能看到自己的
|
||
ctxUser1 := &context.Context{UserID: 1}
|
||
req := httptest.NewRequest(http.MethodGet, "/api/v1/email/contacts", nil)
|
||
w := httptest.NewRecorder()
|
||
ContactsList(ctxUser1, w, req)
|
||
|
||
if w.Code != http.StatusOK {
|
||
t.Fatalf("expected 200, got %d", w.Code)
|
||
}
|
||
|
||
var resp struct {
|
||
Code int `json:"code"`
|
||
Data []models.UserContact `json:"data"`
|
||
}
|
||
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
if len(resp.Data) != 1 {
|
||
t.Fatalf("expected 1 contact for user 1, got %d", len(resp.Data))
|
||
}
|
||
if resp.Data[0].Email != "user1_friend@example.com" {
|
||
t.Fatalf("expected user1_friend@example.com, got %s", resp.Data[0].Email)
|
||
}
|
||
|
||
// 测试删除联系人
|
||
delReqBody, _ := json.Marshal(deleteContactRequest{Email: "user1_friend@example.com"})
|
||
delReq := httptest.NewRequest(http.MethodPost, "/api/v1/email/contacts/delete", bytes.NewReader(delReqBody))
|
||
delW := httptest.NewRecorder()
|
||
DeleteContact(ctxUser1, delW, delReq)
|
||
|
||
if delW.Code != http.StatusOK {
|
||
t.Fatalf("expected 200, got %d", delW.Code)
|
||
}
|
||
|
||
// 再次查询应该为空
|
||
w2 := httptest.NewRecorder()
|
||
ContactsList(ctxUser1, w2, req)
|
||
var resp2 struct {
|
||
Code int `json:"code"`
|
||
Data []models.UserContact `json:"data"`
|
||
}
|
||
_ = json.Unmarshal(w2.Body.Bytes(), &resp2)
|
||
if len(resp2.Data) != 0 {
|
||
t.Fatalf("expected 0 contacts after delete, got %d", len(resp2.Data))
|
||
}
|
||
|
||
// 确认 User 2 的联系人未受影响
|
||
var u2Count int
|
||
has, err := db.Instance.Table(&models.UserContact{}).Where("user_id = 2").Count(&models.UserContact{})
|
||
if err != nil || has != 1 {
|
||
t.Fatalf("user 2 contact was corrupted: count=%d, err=%v", u2Count, err)
|
||
}
|
||
}
|
||
|
||
func TestTrustSenderToggle(t *testing.T) {
|
||
setupContactsTestDB(t)
|
||
ctx := &context.Context{UserID: 1}
|
||
|
||
// 添加信任
|
||
body, _ := json.Marshal(trustSenderRequest{Email: "trusted@example.com", Trusted: true})
|
||
req := httptest.NewRequest(http.MethodPost, "/api/v1/email/trust-sender", bytes.NewReader(body))
|
||
w := httptest.NewRecorder()
|
||
TrustSender(ctx, w, req)
|
||
|
||
if w.Code != http.StatusOK {
|
||
t.Fatalf("expected 200, got %d", w.Code)
|
||
}
|
||
|
||
has, err := db.Instance.Table(&models.UserTrustedSender{}).Where("user_id = ? AND email = ?", 1, "trusted@example.com").Exist(&models.UserTrustedSender{})
|
||
if err != nil || !has {
|
||
t.Fatalf("expected trusted sender to exist")
|
||
}
|
||
|
||
// 取消信任
|
||
body2, _ := json.Marshal(trustSenderRequest{Email: "trusted@example.com", Trusted: false})
|
||
req2 := httptest.NewRequest(http.MethodPost, "/api/v1/email/trust-sender", bytes.NewReader(body2))
|
||
w2 := httptest.NewRecorder()
|
||
TrustSender(ctx, w2, req2)
|
||
|
||
if w2.Code != http.StatusOK {
|
||
t.Fatalf("expected 200, got %d", w2.Code)
|
||
}
|
||
|
||
hasAfter, _ := db.Instance.Table(&models.UserTrustedSender{}).Where("user_id = ? AND email = ?", 1, "trusted@example.com").Exist(&models.UserTrustedSender{})
|
||
if hasAfter {
|
||
t.Fatalf("expected trusted sender to be removed")
|
||
}
|
||
}
|
||
|
||
func TestSenderFiltersCRUDAndTenantIsolation(t *testing.T) {
|
||
setupContactsTestDB(t)
|
||
ctxUser1 := &context.Context{UserID: 1}
|
||
ctxUser2 := &context.Context{UserID: 2}
|
||
|
||
// 1. User 1 添加白名单
|
||
addBody, _ := json.Marshal(senderFilterRequest{Email: "partner@example.com", FilterType: models.FilterTypeWhitelist})
|
||
req := httptest.NewRequest(http.MethodPost, "/api/v1/email/sender-filters", bytes.NewReader(addBody))
|
||
w := httptest.NewRecorder()
|
||
AddSenderFilter(ctxUser1, w, req)
|
||
if w.Code != http.StatusOK {
|
||
t.Fatalf("expected 200, got %d", w.Code)
|
||
}
|
||
|
||
// 2. User 1 添加黑名单
|
||
addBody2, _ := json.Marshal(senderFilterRequest{Email: "spammer@badsite.com", FilterType: models.FilterTypeBlacklist})
|
||
req2 := httptest.NewRequest(http.MethodPost, "/api/v1/email/sender-filters", bytes.NewReader(addBody2))
|
||
w2 := httptest.NewRecorder()
|
||
AddSenderFilter(ctxUser1, w2, req2)
|
||
if w2.Code != http.StatusOK {
|
||
t.Fatalf("expected 200, got %d", w2.Code)
|
||
}
|
||
|
||
// 3. User 2 添加名单(用于多租户隔离验证)
|
||
addBody3, _ := json.Marshal(senderFilterRequest{Email: "secret@user2.com", FilterType: models.FilterTypeWhitelist})
|
||
req3 := httptest.NewRequest(http.MethodPost, "/api/v1/email/sender-filters", bytes.NewReader(addBody3))
|
||
w3 := httptest.NewRecorder()
|
||
AddSenderFilter(ctxUser2, w3, req3)
|
||
if w3.Code != http.StatusOK {
|
||
t.Fatalf("expected 200, got %d", w3.Code)
|
||
}
|
||
|
||
// 4. User 1 查询列表(应有 2 条:1 白 1 黑,统计正确)
|
||
listReq := httptest.NewRequest(http.MethodGet, "/api/v1/email/sender-filters", nil)
|
||
listW := httptest.NewRecorder()
|
||
SenderFiltersList(ctxUser1, listW, listReq)
|
||
if listW.Code != http.StatusOK {
|
||
t.Fatalf("expected 200, got %d", listW.Code)
|
||
}
|
||
|
||
var listResp struct {
|
||
Code int `json:"code"`
|
||
Data struct {
|
||
List []models.UserTrustedSender `json:"list"`
|
||
Total int64 `json:"total"`
|
||
WhitelistCount int64 `json:"whitelist_count"`
|
||
BlacklistCount int64 `json:"blacklist_count"`
|
||
} `json:"data"`
|
||
}
|
||
if err := json.Unmarshal(listW.Body.Bytes(), &listResp); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if listResp.Data.Total != 2 || listResp.Data.WhitelistCount != 1 || listResp.Data.BlacklistCount != 1 {
|
||
t.Fatalf("unexpected counts: total=%d, white=%d, black=%d",
|
||
listResp.Data.Total, listResp.Data.WhitelistCount, listResp.Data.BlacklistCount)
|
||
}
|
||
for _, item := range listResp.Data.List {
|
||
if item.Email == "secret@user2.com" {
|
||
t.Fatalf("tenant isolation violation: user1 saw user2 filter")
|
||
}
|
||
}
|
||
|
||
// 5. 快速切换(Toggle)白名单 -> 黑名单
|
||
toggleBody, _ := json.Marshal(toggleSenderFilterRequest{Email: "partner@example.com"})
|
||
toggleReq := httptest.NewRequest(http.MethodPost, "/api/v1/email/sender-filters/toggle", bytes.NewReader(toggleBody))
|
||
toggleW := httptest.NewRecorder()
|
||
ToggleSenderFilter(ctxUser1, toggleW, toggleReq)
|
||
if toggleW.Code != http.StatusOK {
|
||
t.Fatalf("expected 200, got %d", toggleW.Code)
|
||
}
|
||
|
||
var updated models.UserTrustedSender
|
||
has, err := db.Instance.Table(&models.UserTrustedSender{}).
|
||
Where("user_id = ? AND LOWER(email) = ?", 1, "partner@example.com").
|
||
Get(&updated)
|
||
if err != nil || !has || updated.FilterType != models.FilterTypeBlacklist {
|
||
t.Fatalf("expected partner@example.com to be toggled to blacklist, got filter_type=%s", updated.FilterType)
|
||
}
|
||
|
||
// 6. 删除黑名单条目
|
||
delBody, _ := json.Marshal(senderFilterRequest{Email: "spammer@badsite.com"})
|
||
delReq := httptest.NewRequest(http.MethodDelete, "/api/v1/email/sender-filters", bytes.NewReader(delBody))
|
||
delW := httptest.NewRecorder()
|
||
DeleteSenderFilter(ctxUser1, delW, delReq)
|
||
if delW.Code != http.StatusOK {
|
||
t.Fatalf("expected 200, got %d", delW.Code)
|
||
}
|
||
|
||
hasSpammer, _ := db.Instance.Table(&models.UserTrustedSender{}).
|
||
Where("user_id = ? AND LOWER(email) = ?", 1, "spammer@badsite.com").
|
||
Exist(&models.UserTrustedSender{})
|
||
if hasSpammer {
|
||
t.Fatalf("expected spammer to be deleted")
|
||
}
|
||
|
||
// 7. 再次确认 User 2 数据完整未受影响
|
||
hasU2, _ := db.Instance.Table(&models.UserTrustedSender{}).
|
||
Where("user_id = ? AND LOWER(email) = ?", 2, "secret@user2.com").
|
||
Exist(&models.UserTrustedSender{})
|
||
if !hasU2 {
|
||
t.Fatalf("user 2 data was corrupted")
|
||
}
|
||
}
|