mirror of
https://github.com/HugeFrog24/go-telegram-bot.git
synced 2026-10-27 21:35:26 +00:00
Toolchain and CI: - Go 1.27 (go directive 1.27.0, CI 1.27.3) - actions/checkout v7, actions/setup-go v7 - golangci-lint v2.15.0, gosec v2.30.0 - Dockerfile: drop obsolete -a -installsuffix cgo build flags Go modules: - anthropic-sdk-go v1.71.0 -> v2.3.0 (new /v2 module path, API version 2026-10-21); go-telegram/bot v1.27.0; x/sync v0.24.0; indirects to latest - BetaMessageParam is now a per-role union: build assistant turns with OfAssistant and read content through the role in the cache helper - Pass the Anthropic API key only when set: v2 sends an explicitly passed credential as is, so an empty key became an empty bearer token instead of falling back to the SDK's default credential chain as v1 did API 2026-10-21 / deprecations: - Stop sending temperature (rejected on every model); the config key is still parsed and a startup notice flags it - Name refusals in the empty-response error instead of reporting an unexpected response format - ElevenLabs STT: scribe_v1 (deprecated) -> scribe_v2 - Template config: claude-haiku-5-5 with effort low, temperature removed Docs: replace the unsupported `go get -u` install step, document the ignored temperature key, default-on thinking, and per-model effort defaults.
158 lines
4.4 KiB
Go
158 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/anthropics/anthropic-sdk-go/v2"
|
|
"github.com/anthropics/anthropic-sdk-go/v2/packages/param"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestMarkTrailingCacheBreakpoint(t *testing.T) {
|
|
t.Run("marks the final text block of the final turn", func(t *testing.T) {
|
|
msgs := []anthropic.BetaMessageParam{
|
|
anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("older")),
|
|
anthropic.NewBetaUserMessage(
|
|
anthropic.NewBetaTextBlock("first"),
|
|
anthropic.NewBetaTextBlock("last"),
|
|
),
|
|
}
|
|
|
|
markTrailingCacheBreakpoint(msgs)
|
|
|
|
last := msgs[1].OfUser.Content[1].OfText
|
|
require.NotNil(t, last)
|
|
assert.False(t, param.IsOmitted(last.CacheControl),
|
|
"the trailing block carries the breakpoint")
|
|
|
|
// Everything earlier stays unmarked: one breakpoint, not one per block.
|
|
assert.True(t, param.IsOmitted(msgs[1].OfUser.Content[0].OfText.CacheControl))
|
|
assert.True(t, param.IsOmitted(msgs[0].OfUser.Content[0].OfText.CacheControl))
|
|
})
|
|
|
|
t.Run("marks a trailing image block", func(t *testing.T) {
|
|
msgs := []anthropic.BetaMessageParam{
|
|
anthropic.NewBetaUserMessage(
|
|
anthropic.NewBetaImageBlock(anthropic.BetaFileImageSourceParam{FileID: "file_1"}),
|
|
),
|
|
}
|
|
|
|
markTrailingCacheBreakpoint(msgs)
|
|
|
|
require.NotNil(t, msgs[0].OfUser.Content[0].OfImage)
|
|
assert.False(t, param.IsOmitted(msgs[0].OfUser.Content[0].OfImage.CacheControl))
|
|
})
|
|
|
|
t.Run("tolerates empty input", func(t *testing.T) {
|
|
assert.NotPanics(t, func() { markTrailingCacheBreakpoint(nil) })
|
|
assert.NotPanics(t, func() {
|
|
markTrailingCacheBreakpoint([]anthropic.BetaMessageParam{{}})
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestPrepareContextMessages_CacheHistoryToggle(t *testing.T) { //NOSONAR go:S100 -- underscore separation is idiomatic in Go test names
|
|
b, _ := setupBotForTest(t, 123)
|
|
memory := &ChatMemory{
|
|
Messages: []Message{
|
|
{IsUser: true, Text: "hello"},
|
|
{IsUser: false, Text: "hi there"},
|
|
},
|
|
Size: 10,
|
|
}
|
|
|
|
t.Run("enabled by default", func(t *testing.T) {
|
|
msgs := b.prepareContextMessages(memory)
|
|
require.Len(t, msgs, 2)
|
|
require.NotNil(t, msgs[0].OfUser, "user memory becomes a user turn")
|
|
require.NotNil(t, msgs[1].OfAssistant, "bot memory becomes an assistant turn")
|
|
assert.False(t, param.IsOmitted(msgs[1].OfAssistant.Content[0].OfText.CacheControl))
|
|
})
|
|
|
|
t.Run("opt-out leaves history unmarked", func(t *testing.T) {
|
|
off := false
|
|
b.config.CacheHistory = &off
|
|
defer func() { b.config.CacheHistory = nil }()
|
|
|
|
msgs := b.prepareContextMessages(memory)
|
|
require.Len(t, msgs, 2)
|
|
assert.True(t, param.IsOmitted(msgs[1].OfAssistant.Content[0].OfText.CacheControl))
|
|
})
|
|
}
|
|
|
|
func TestValidateConfig_DebounceMs(t *testing.T) { //NOSONAR go:S100 -- underscore separation is idiomatic in Go test names
|
|
base := func(ms int) *BotConfig {
|
|
return &BotConfig{
|
|
ID: "b",
|
|
TelegramToken: "t",
|
|
Model: "claude-sonnet-4-6",
|
|
MessagePerHour: 1,
|
|
MessagePerDay: 1,
|
|
DebounceMs: ms,
|
|
}
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
ms int
|
|
wantErr bool
|
|
}{
|
|
{"omitted is valid", 0, false},
|
|
{"typical chat window is valid", 2500, false},
|
|
{"at the ceiling is valid", maxDebounceMs, false},
|
|
{"negative is rejected", -1, true},
|
|
{"above the ceiling is rejected", maxDebounceMs + 1, true},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := validateConfig(base(tc.ms), map[string]bool{}, map[string]bool{})
|
|
if tc.wantErr {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateConfig_Effort(t *testing.T) { //NOSONAR go:S100 -- underscore separation is idiomatic in Go test names
|
|
base := func(effort string) *BotConfig {
|
|
return &BotConfig{
|
|
ID: "b",
|
|
TelegramToken: "t",
|
|
Model: "claude-sonnet-5",
|
|
MessagePerHour: 1,
|
|
MessagePerDay: 1,
|
|
Effort: effort,
|
|
}
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
effort string
|
|
wantErr bool
|
|
}{
|
|
{"omitted is valid", "", false},
|
|
{"low is valid", "low", false},
|
|
{"medium is valid", "medium", false},
|
|
{"high is valid", "high", false},
|
|
{"xhigh is valid", "xhigh", false},
|
|
{"max is valid", "max", false},
|
|
{"unknown level is rejected", "turbo", true},
|
|
{"wrong case is rejected", "Low", true},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := validateConfig(base(tc.effort), map[string]bool{}, map[string]bool{})
|
|
if tc.wantErr {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|
|
}
|