92 lines
3.7 KiB
Go
92 lines
3.7 KiB
Go
package creator
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestMetricScheduleUsesUTCAndCumulativeIntervals(t *testing.T) {
|
|
published := time.Date(2026, 1, 1, 0, 0, 0, 0, time.FixedZone("CST", 8*60*60))
|
|
points := MetricSchedule(published, time.Hour, 24*time.Hour, 2, 6)
|
|
want := []time.Duration{time.Hour, 3 * time.Hour, 7 * time.Hour, 15 * time.Hour, 31 * time.Hour, 55 * time.Hour}
|
|
for index, duration := range want {
|
|
if got := points[index].Sub(published.UTC()); got != duration {
|
|
t.Fatalf("point %d: got %s, want %s", index, got, duration)
|
|
}
|
|
}
|
|
if next, reason := NextMetricAt(published, published.Add(3*time.Hour), time.Hour, 24*time.Hour, 2, 30*24*time.Hour); next != published.UTC().Add(7*time.Hour) || reason != "" {
|
|
t.Fatalf("strict next point: got %s/%q", next, reason)
|
|
}
|
|
if next, reason := NextMetricAt(published, published.Add(30*24*time.Hour), time.Hour, 24*time.Hour, 2, 30*24*time.Hour); !next.IsZero() || reason != "monitoring_age_reached" {
|
|
t.Fatalf("age boundary: got %s/%q", next, reason)
|
|
}
|
|
}
|
|
|
|
func TestKeywordMatchingDoesNotNormalizeText(t *testing.T) {
|
|
if got := TrimKeyword(" \tAI \r\n"); got != "AI" {
|
|
t.Fatalf("trimmed keyword = %q", got)
|
|
}
|
|
if ok, _ := MatchKeywords("AI 买 车", []string{"AI"}, nil); !ok {
|
|
t.Fatal("expected exact substring match")
|
|
}
|
|
if ok, _ := MatchKeywords("ai 买车", []string{"AI"}, nil); ok {
|
|
t.Fatal("case should remain significant")
|
|
}
|
|
if ok, _ := MatchKeywords("AI 买车", []string{"买 车"}, nil); ok {
|
|
t.Fatal("internal whitespace should remain significant")
|
|
}
|
|
if ok, _ := MatchKeywords("AI 买 车", []string{"AI"}, []string{"买 车"}); ok {
|
|
t.Fatal("exclude keyword should veto the match")
|
|
}
|
|
}
|
|
|
|
func TestValidateSettingsRejectsUnsafeNumbers(t *testing.T) {
|
|
base := SettingsUpdate{LookbackDays: 30, NewWorkIntervalSeconds: 1800, MetricInitialIntervalSeconds: 3600, MetricMultiplier: 2, MetricMaxIntervalSeconds: 86400, MetricAgeSeconds: 30 * 24 * 60 * 60}
|
|
for _, mutate := range []func(*SettingsUpdate){
|
|
func(value *SettingsUpdate) { value.MetricMultiplier = math.NaN() },
|
|
func(value *SettingsUpdate) { value.MetricAgeSeconds = maxDurationSeconds + 1 },
|
|
func(value *SettingsUpdate) { value.LookbackDays = int(maxDurationSeconds/(24*60*60) + 1) },
|
|
} {
|
|
value := base
|
|
mutate(&value)
|
|
if err := ValidateSettings(value); err != ErrInvalid {
|
|
t.Fatalf("expected invalid settings, got %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNextMetricIntervalRejectsNonFiniteMultiplier(t *testing.T) {
|
|
if _, err := nextMetricInterval(3600, math.NaN(), 86400); err != ErrInvalid {
|
|
t.Fatalf("expected NaN multiplier to be invalid, got %v", err)
|
|
}
|
|
if next, err := nextMetricInterval(3600, math.Inf(1), 86400); err != nil || next != 86400 {
|
|
t.Fatalf("expected infinite growth to clamp at maximum, got %d/%v", next, err)
|
|
}
|
|
}
|
|
|
|
func TestActionTargetAndBusinessStatusGates(t *testing.T) {
|
|
if ActionTargetValid(ActionReplyComment, "uid-1", "", "", "comment") {
|
|
t.Fatal("reply without a comment target must be blocked")
|
|
}
|
|
profile := AccountProfile{AuthorizationStatus: "authorized", LoginStatus: "logged_in", BusinessStatus: "muted"}
|
|
if err := CanWrite(profile, false, ActionDM); err == nil {
|
|
t.Fatal("muted accounts cannot manually send DMs")
|
|
}
|
|
profile.BusinessStatus = "normal"
|
|
if err := CanWrite(profile, false, ActionDM); err != nil {
|
|
t.Fatalf("normal logged-in account should be writable: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidMessageTypeKeepsNonTextMessagesExplicit(t *testing.T) {
|
|
for _, messageType := range []string{MessageTypeText, MessageTypeImage, MessageTypeVoice, MessageTypeVideo, MessageTypeSticker, MessageTypeUnknown} {
|
|
if !ValidMessageType(messageType) {
|
|
t.Fatalf("message type %q should be accepted", messageType)
|
|
}
|
|
}
|
|
if ValidMessageType("text-like") {
|
|
t.Fatal("unknown message type must not be accepted")
|
|
}
|
|
}
|