Files
tamabox/internal/conf/announcement_test.go
T
2026-10-22 17:49:25 +08:00

244 lines
8.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2026 tamakyi. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package conf
import (
"testing"
"time"
"github.com/tamakyi/TamaBox/internal/i18n"
)
// setAnnouncement 在测试期间覆盖全局公告配置,结束后恢复。
// 不设 Timezone,即走默认的东八区。
func setAnnouncement(t *testing.T, content, start, end string) {
t.Helper()
old := Announcement
t.Cleanup(func() { Announcement = old })
Announcement = announcementConfig{Content: content, Start: start, End: end}
sanitizeAnnouncement()
}
// setAnnouncementConfig 用完整配置覆盖,供分语言 / 时区用例使用。
func setAnnouncementConfig(t *testing.T, cfg announcementConfig) {
t.Helper()
old := Announcement
t.Cleanup(func() { Announcement = old })
Announcement = cfg
sanitizeAnnouncement()
}
func mustParseIn(t *testing.T, layout, value string) time.Time {
t.Helper()
loc, err := time.LoadLocation(defaultAnnouncementZone)
if err != nil {
t.Fatalf("load zone %s: %v", defaultAnnouncementZone, err)
}
parsed, err := time.ParseInLocation(layout, value, loc)
if err != nil {
t.Fatalf("parse %q: %v", value, err)
}
return parsed
}
func TestAnnouncementDisabledWhenContentEmpty(t *testing.T) {
setAnnouncement(t, " ", "2026-10-24 00:00", "2026-10-26 00:00")
if _, ok := Announcement.Active(time.Now(), i18n.LangZhCN); ok {
t.Fatal("所有语言文案都为空时公告应关闭")
}
}
func TestAnnouncementWindowIsHalfOpen(t *testing.T) {
setAnnouncement(t, "维护通知", "2026-10-24 00:00", "2026-10-26 00:00")
start := mustParseIn(t, "2006-01-02 15:04", "2026-10-24 00:00")
end := mustParseIn(t, "2006-01-02 15:04", "2026-10-26 00:00")
cases := []struct {
name string
now time.Time
want bool
}{
{"开始前一秒", start.Add(-time.Second), false},
{"开始时刻(闭区间)", start, true},
{"窗口内", start.Add(12 * time.Hour), true},
{"结束时刻(开区间)", end, false},
{"结束后", end.Add(time.Hour), false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
content, ok := Announcement.Active(c.now, i18n.LangZhCN)
if ok != c.want {
t.Fatalf("Active(%s) = %v, want %v", c.now, ok, c.want)
}
if ok && content != "维护通知" {
t.Fatalf("content = %q, want 维护通知", content)
}
})
}
}
// 只写一侧的边界必须保持「该侧不限」,不能因为另一侧为空就整体失效。
func TestAnnouncementOneSidedWindow(t *testing.T) {
now := mustParseIn(t, "2006-01-02 15:04", "2026-10-22 12:00")
setAnnouncement(t, "只有开始", "2026-10-23 00:00", "")
if _, ok := Announcement.Active(now, i18n.LangZhCN); ok {
t.Fatal("只写 start 且尚未到点时应不展示")
}
if _, ok := Announcement.Active(now.Add(48*time.Hour), i18n.LangZhCN); !ok {
t.Fatal("只写 start 时到点后应一直展示")
}
setAnnouncement(t, "只有结束", "", "2026-10-23 00:00")
if _, ok := Announcement.Active(now, i18n.LangZhCN); !ok {
t.Fatal("只写 end 且未到点时应立即展示")
}
if _, ok := Announcement.Active(now.Add(48*time.Hour), i18n.LangZhCN); ok {
t.Fatal("只写 end 时过点后应下线")
}
setAnnouncement(t, "一直展示", "", "")
if _, ok := Announcement.Active(now.AddDate(5, 0, 0), i18n.LangZhCN); !ok {
t.Fatal("起止都留空时应一直展示")
}
}
// 分语言文案:配了就用自己的,没配回退 content;content 也没配则该语言不显示。
func TestAnnouncementLanguageSelection(t *testing.T) {
setAnnouncementConfig(t, announcementConfig{
Content: "fallback",
ContentZhCN: "简中",
ContentEn: "english",
ContentJa: "日本語",
})
cases := []struct{ lang, want string }{
{i18n.LangZhCN, "简中"},
{i18n.LangEn, "english"},
{i18n.LangJa, "日本語"},
{i18n.LangZhTW, "fallback"}, // 没配繁中 → 回退默认文案
{"en-US", "english"}, // Resolve 会归一化
}
for _, c := range cases {
got, ok := Announcement.Active(time.Now(), c.lang)
if !ok || got != c.want {
t.Fatalf("Active(lang=%s) = %q ok=%v, want %q", c.lang, got, ok, c.want)
}
}
// 只配英文、content 留空:简中访客不应看到英文公告。
setAnnouncementConfig(t, announcementConfig{ContentEn: "english only"})
if _, ok := Announcement.Active(time.Now(), i18n.LangZhCN); ok {
t.Fatal("该语言未配文案且无默认文案时不应展示")
}
if got, ok := Announcement.Active(time.Now(), i18n.LangEn); !ok || got != "english only" {
t.Fatalf("英文应正常展示,got %q ok=%v", got, ok)
}
}
func TestAnnouncementTimeFormats(t *testing.T) {
loc, err := time.LoadLocation(defaultAnnouncementZone)
if err != nil {
t.Fatalf("load zone: %v", err)
}
// 带偏移的 RFC3339 必须按字面时刻解析,而不是再套一层默认时区。
at, ok := parseAnnouncementTime("2026-10-25T02:00:00+08:00", loc)
if !ok {
t.Fatal("RFC3339 应可解析")
}
want := time.Date(2026, 10, 24, 18, 0, 0, 0, time.UTC)
if !at.Equal(want) {
t.Fatalf("解析结果 = %s, want %s", at.UTC(), want)
}
if at, ok := parseAnnouncementTime("2026-10-25", loc); !ok || at.Hour() != 0 {
t.Fatalf("纯日期应解析为当天 00:00,got %s ok=%v", at, ok)
}
if at, ok := parseAnnouncementTime("2026-10-25 02:00:59", loc); !ok || at.Second() != 59 {
t.Fatalf("秒级格式应可解析,got %s ok=%v", at, ok)
}
if _, ok := parseAnnouncementTime("2026/10/25 02:00", loc); ok {
t.Fatal("错误格式应解析失败")
}
if _, ok := parseAnnouncementTime("2026-13-01", loc); ok {
t.Fatal("非法月份应解析失败")
}
}
// 裸时间必须按东八区解释:运维写「10-25 02:00」指北京时间,
// 而容器里的 time.Local 是 UTC(旧实现整体偏 8 小时)。
func TestAnnouncementBareTimeUsesBeijing(t *testing.T) {
setAnnouncement(t, "维护通知", "2026-10-25 02:00", "")
want := time.Date(2026, 10, 24, 18, 0, 0, 0, time.UTC) // 北京 10-25 02:00
if !Announcement.StartAt.Equal(want) {
t.Fatalf("裸时间应按 +08:00 解释,got %s, want %s", Announcement.StartAt.UTC(), want)
}
if _, offset := Announcement.StartAt.Zone(); offset != 8*60*60 {
t.Fatalf("解析后的偏移 = %d 秒, want 28800", offset)
}
}
// timezone 显式指定时以它为准,不能继续套东八区。
func TestAnnouncementTimezoneOverride(t *testing.T) {
setAnnouncementConfig(t, announcementConfig{
Content: "维护通知",
Start: "2026-10-25 00:00",
Timezone: "UTC",
})
want := time.Date(2026, 10, 25, 0, 0, 0, 0, time.UTC)
if !Announcement.StartAt.Equal(want) {
t.Fatalf("timezone=UTC 时应按 UTC 解析,got %s", Announcement.StartAt.UTC())
}
setAnnouncementConfig(t, announcementConfig{
Content: "维护通知",
Start: "2026-10-25 00:00",
Timezone: "America/New_York",
})
// 纽约 10-25 00:00 = UTC 10-25 04:00(EDT,UTC-4)
wantNY := time.Date(2026, 10, 25, 4, 0, 0, 0, time.UTC)
if !Announcement.StartAt.Equal(wantNY) {
t.Fatalf("timezone=America/New_York 解析错误,got %s", Announcement.StartAt.UTC())
}
}
// 时区名写错和日期写错一样:只关公告,不阻断启动。
func TestAnnouncementInvalidTimezoneDisables(t *testing.T) {
setAnnouncementConfig(t, announcementConfig{
Content: "维护通知",
Start: "2026-10-25 00:00",
Timezone: "Mars/Olympus",
})
if Announcement.Content != "" {
t.Fatal("时区无效时公告应被关闭")
}
}
// 时间写错只关公告,不能让站点起不来;同时必须清空解析结果,避免残留时间窗。
func TestAnnouncementInvalidTimeDisables(t *testing.T) {
for _, c := range []struct{ name, start, end string }{
{"start 格式错", "2026/10/25 02:00", ""},
{"end 格式错", "", "10月26日"},
{"end 早于 start", "2026-10-26 00:00", "2026-10-25 00:00"},
{"end 等于 start", "2026-10-25 00:00", "2026-10-25 00:00"},
} {
t.Run(c.name, func(t *testing.T) {
setAnnouncement(t, "内容", c.start, c.end)
if Announcement.Content != "" {
t.Fatal("配置无效时公告应被关闭")
}
if !Announcement.StartAt.IsZero() || !Announcement.EndAt.IsZero() {
t.Fatal("配置无效时应清空已解析的时间点")
}
})
}
}