192 lines
7.1 KiB
Go
192 lines
7.1 KiB
Go
package service
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/QuantumNous/new-api/constant"
|
|
"github.com/QuantumNous/new-api/i18n"
|
|
"github.com/QuantumNous/new-api/model"
|
|
"github.com/QuantumNous/new-api/relaykit/dto"
|
|
"github.com/QuantumNous/new-api/relaykit/types"
|
|
"github.com/QuantumNous/new-api/setting/system_setting"
|
|
|
|
"github.com/glebarez/sqlite"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// A low-balance reminder goes out once, on the charge that takes a wallet or
|
|
// subscription balance below the threshold. A balance already below it earns
|
|
// nothing more until a top-up lifts it back above the line. The site switch
|
|
// and the user's switch both turn reminders off; a user who never chose gets
|
|
// them.
|
|
func TestQuotaReminderFiresOncePerDrop(t *testing.T) {
|
|
previousEnabled, previousThreshold := common.QuotaRemindEnabled, common.QuotaRemindThreshold
|
|
t.Cleanup(func() {
|
|
common.QuotaRemindEnabled, common.QuotaRemindThreshold = previousEnabled, previousThreshold
|
|
})
|
|
common.QuotaRemindThreshold = 1000
|
|
on, off := true, false
|
|
// changes move the balance from start: negative ones are charges, which
|
|
// may remind; positive ones are top-ups. want holds one entry per charge.
|
|
for _, tc := range []struct {
|
|
name string
|
|
siteEnabled bool
|
|
setting dto.UserSetting
|
|
start int64
|
|
changes []int64
|
|
want []bool
|
|
}{
|
|
{
|
|
name: "never chosen: one reminder per drop, again after a top-up",
|
|
siteEnabled: true,
|
|
start: 1500,
|
|
changes: []int64{-400, -200, -300, +1500, -1200},
|
|
want: []bool{false, true, false, true},
|
|
},
|
|
{
|
|
name: "switched on with its own threshold",
|
|
siteEnabled: true,
|
|
setting: dto.UserSetting{QuotaWarningEnabled: &on, QuotaWarningThreshold: 5000},
|
|
start: 6000,
|
|
changes: []int64{-500, -600, -2000},
|
|
want: []bool{false, true, false},
|
|
},
|
|
{
|
|
name: "a charge that lands exactly on the threshold does not remind",
|
|
siteEnabled: true,
|
|
start: 1200,
|
|
changes: []int64{-200, -1},
|
|
want: []bool{false, true},
|
|
},
|
|
{
|
|
name: "user switched off",
|
|
siteEnabled: true,
|
|
setting: dto.UserSetting{QuotaWarningEnabled: &off},
|
|
start: 1500,
|
|
changes: []int64{-600},
|
|
want: []bool{false},
|
|
},
|
|
{
|
|
name: "site switched off",
|
|
setting: dto.UserSetting{QuotaWarningEnabled: &on},
|
|
start: 1500,
|
|
changes: []int64{-600},
|
|
want: []bool{false},
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
common.QuotaRemindEnabled = tc.siteEnabled
|
|
got := make([]bool, 0, len(tc.want))
|
|
balance := tc.start
|
|
for _, change := range tc.changes {
|
|
if change < 0 {
|
|
got = append(got, quotaReminderDue(tc.setting, balance, balance+change))
|
|
}
|
|
balance += change
|
|
}
|
|
assert.Equal(t, tc.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Channel notices go to every enabled administrator whose channel switch is
|
|
// on, in that administrator's saved language. Until an administrator chooses,
|
|
// root receives them and other administrators do not; other users never do.
|
|
func TestChannelNoticesFollowEachAdministratorsSwitch(t *testing.T) {
|
|
previousDB := model.DB
|
|
previousCache, previousRedis := common.MemoryCacheEnabled, common.RedisEnabled
|
|
previousNotifyLimit := constant.NotifyLimitCount
|
|
previousClient, previousWorker := httpClient, system_setting.WorkerUrl
|
|
fetch := system_setting.GetFetchSetting()
|
|
previousFetch := *fetch
|
|
t.Cleanup(func() {
|
|
model.DB = previousDB
|
|
common.MemoryCacheEnabled, common.RedisEnabled = previousCache, previousRedis
|
|
constant.NotifyLimitCount = previousNotifyLimit
|
|
httpClient, system_setting.WorkerUrl = previousClient, previousWorker
|
|
*fetch = previousFetch
|
|
})
|
|
database, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
sqlDB, err := database.DB()
|
|
require.NoError(t, err)
|
|
sqlDB.SetMaxOpenConns(1)
|
|
t.Cleanup(func() { require.NoError(t, sqlDB.Close()) })
|
|
require.NoError(t, database.AutoMigrate(&model.Channel{}, &model.Ability{}, &model.User{}))
|
|
model.DB = database
|
|
common.MemoryCacheEnabled, common.RedisEnabled = false, false
|
|
constant.NotifyLimitCount = 100
|
|
|
|
var mutex sync.Mutex
|
|
received := map[string][]string{}
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var payload WebhookPayload
|
|
if err := common.DecodeJson(r.Body, &payload); err == nil {
|
|
mutex.Lock()
|
|
recipient := strings.TrimPrefix(r.URL.Path, "/")
|
|
received[recipient] = append(received[recipient], payload.Type+" "+payload.Title)
|
|
mutex.Unlock()
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}))
|
|
t.Cleanup(server.Close)
|
|
httpClient, system_setting.WorkerUrl = server.Client(), ""
|
|
fetch.EnableSSRFProtection = false
|
|
|
|
on, off := true, false
|
|
for _, user := range []struct {
|
|
name string
|
|
role int
|
|
status int
|
|
receives *bool
|
|
language string
|
|
}{
|
|
{"root-never-chose", common.RoleRootUser, common.UserStatusEnabled, nil, ""},
|
|
{"root-turned-off", common.RoleRootUser, common.UserStatusEnabled, &off, ""},
|
|
{"admin-never-chose", common.RoleAdminUser, common.UserStatusEnabled, nil, ""},
|
|
{"admin-turned-on", common.RoleAdminUser, common.UserStatusEnabled, &on, "zhCN"},
|
|
{"disabled-admin-turned-on", common.RoleAdminUser, common.UserStatusDisabled, &on, ""},
|
|
{"user-with-switch-stored-on", common.RoleCommonUser, common.UserStatusEnabled, &on, ""},
|
|
} {
|
|
setting, err := common.Marshal(dto.UserSetting{
|
|
NotifyType: dto.NotifyTypeWebhook,
|
|
WebhookUrl: server.URL + "/" + user.name,
|
|
ChannelNotifyEnabled: user.receives,
|
|
Language: user.language,
|
|
})
|
|
require.NoError(t, err)
|
|
require.NoError(t, database.Create(&model.User{Username: user.name, Role: user.role, Status: user.status, Setting: string(setting), AffCode: user.name}).Error)
|
|
}
|
|
channel := &model.Channel{Name: "notice-review", Key: "fixture-key", Type: 1, Status: common.ChannelStatusEnabled, Group: "default", Models: "test-model"}
|
|
require.NoError(t, channel.Insert())
|
|
|
|
DisableChannel(types.ChannelError{ChannelId: channel.Id, ChannelName: channel.Name, AutoBan: true}, "upstream failed")
|
|
EnableChannel(channel.Id, "", channel.Name)
|
|
NotifyChannelWatchers(dto.NotifyTypeChannelTest, func(lang string) (string, string) {
|
|
return i18n.Translate(lang, i18n.MsgChannelTestCompletedSubject), i18n.Translate(lang, i18n.MsgChannelTestCompletedContent)
|
|
})
|
|
|
|
disabledType := formatNotifyType(channel.Id, common.ChannelStatusAutoDisabled)
|
|
enabledType := formatNotifyType(channel.Id, common.ChannelStatusEnabled)
|
|
assert.Equal(t, map[string][]string{
|
|
"root-never-chose": {
|
|
fmt.Sprintf(`%s Channel "notice-review" (#%d) has been disabled`, disabledType, channel.Id),
|
|
fmt.Sprintf(`%s Channel "notice-review" (#%d) has been enabled`, enabledType, channel.Id),
|
|
"channel_test Channel test completed",
|
|
},
|
|
"admin-turned-on": {
|
|
fmt.Sprintf("%s 通道「notice-review」(#%d)已被禁用", disabledType, channel.Id),
|
|
fmt.Sprintf("%s 通道「notice-review」(#%d)已被启用", enabledType, channel.Id),
|
|
"channel_test 通道测试完成",
|
|
},
|
|
}, received)
|
|
}
|