// 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" "gopkg.in/ini.v1" ) // TestLimitsDefaults 保证内置默认值与旧版硬编码一致:未配置 [limits] 段(或 conf.Init // 尚未调用)时,程序按这些默认值运行。 func TestLimitsDefaults(t *testing.T) { cases := []struct { name string got int want int }{ {"MaxTags", Limits.MaxTags, 5}, {"MaxTagLen", Limits.MaxTagLen, 5}, {"MaxBlockWords", Limits.MaxBlockWords, 10}, {"MaxBlockWordLen", Limits.MaxBlockWordLen, 10}, {"CustomAnswerLen", Limits.CustomAnswerLen, 200}, {"VoteOptionLen", Limits.VoteOptionLen, 300}, {"QuestionContentLen", Limits.QuestionContentLen, 1000}, {"AnswerContentLen", Limits.AnswerContentLen, 2000}, {"FollowUpContentLen", Limits.FollowUpContentLen, 2000}, {"ChatMessageLen", Limits.ChatMessageLen, 2000}, {"ChatMaxMessages", Limits.ChatMaxMessages, 200}, {"DailyMessageLimitMax", Limits.DailyMessageLimitMax, 999}, } for _, c := range cases { if c.got != c.want { t.Errorf("%s = %d, want %d", c.name, c.got, c.want) } } } // TestLimitsMapTo 模拟 conf.Init 的映射行为:显式键被覆盖,缺失键保留默认。 // 启动逻辑是 File.Section("limits").MapTo(&Limits),与这里同一路径。 func TestLimitsMapTo(t *testing.T) { src := ` [limits] max_tags = 7 chat_message_len = 500 answer_content_len = 300 daily_message_limit_max = 0 ` f, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, []byte(src)) if err != nil { t.Fatalf("parse ini: %v", err) } l := Limits // 拷贝默认值,等价于启动前状态 if err := f.Section("limits").MapTo(&l); err != nil { t.Fatalf("map [limits]: %v", err) } if l.MaxTags != 7 { t.Errorf("MaxTags = %d, want 7 (ini 覆盖失败)", l.MaxTags) } if l.ChatMessageLen != 500 { t.Errorf("ChatMessageLen = %d, want 500", l.ChatMessageLen) } if l.AnswerContentLen != 300 { t.Errorf("AnswerContentLen = %d, want 300(ini 覆盖失败)", l.AnswerContentLen) } if l.DailyMessageLimitMax != 0 { t.Errorf("DailyMessageLimitMax = %d, want 0(配置为 0 应生效)", l.DailyMessageLimitMax) } // 未显式出现的键保留内置默认 if l.MaxTagLen != 5 { t.Errorf("MaxTagLen = %d, want 5(缺失键应保留默认)", l.MaxTagLen) } if l.VoteOptionLen != 300 { t.Errorf("VoteOptionLen = %d, want 300", l.VoteOptionLen) } if l.QuestionContentLen != 1000 || l.FollowUpContentLen != 2000 { t.Errorf("QuestionContentLen/FollowUpContentLen = %d/%d, want 1000/2000(缺失键应保留默认)", l.QuestionContentLen, l.FollowUpContentLen) } } // TestLimitsEmptyValueSanitized 验证「键留空」的防护:ini 会把空 int 解析为 0, // sanitizeLimits 随后将其回退到内置默认 —— 防止留空把上限静默置零(标签全拒等)。 func TestLimitsEmptyValueSanitized(t *testing.T) { src := ` [limits] max_tags = chat_message_len = 0 followup_content_len = -1 daily_message_limit_max = -3 ` f, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, []byte(src)) if err != nil { t.Fatalf("parse ini: %v", err) } l := Limits if err := f.Section("limits").MapTo(&l); err != nil { t.Fatalf("map [limits]: %v", err) } // 前提确认:空串对 int 是「保留原值」而非 0(gopkg.in/ini 行为); // 显式 0 / 负数则照常映射。真正的 0 陷阱来自显式填 0 / 负数,由 sanitize 兜底。 if l.MaxTags != defaultMaxTags { t.Fatalf("precondition broken: 空值 max_tags 应保留默认, got %d", l.MaxTags) } if l.ChatMessageLen != 0 || l.DailyMessageLimitMax != -3 { t.Fatalf("precondition broken: got %d/%d, want 0/-3", l.ChatMessageLen, l.DailyMessageLimitMax) } sanitizeLimits(&l) if l.MaxTags != defaultMaxTags { t.Errorf("MaxTags = %d, want 默认 %d", l.MaxTags, defaultMaxTags) } if l.ChatMessageLen != defaultChatMessageLen { t.Errorf("ChatMessageLen = %d, want 默认 %d(0 应回退默认)", l.ChatMessageLen, defaultChatMessageLen) } if l.FollowUpContentLen != defaultFollowUpContentLen { t.Errorf("FollowUpContentLen = %d, want 默认 %d(负数应回退默认)", l.FollowUpContentLen, defaultFollowUpContentLen) } if l.DailyMessageLimitMax != defaultDailyMessageLimitMax { t.Errorf("DailyMessageLimitMax = %d, want 默认 %d(负数应回退默认)", l.DailyMessageLimitMax, defaultDailyMessageLimitMax) } if l.ChatMaxMessages != defaultChatMaxMessages { t.Errorf("ChatMaxMessages = %d, want 默认 %d(缺失键不受影响)", l.ChatMaxMessages, defaultChatMaxMessages) } }