Files
Jay Brown 72de690894 Merged in feature/chatbot1 (pull request #223)
Chatbot functionality

* baseline working

* missing test file

* more tests
2026-05-07 20:56:18 +00:00

105 lines
3.5 KiB
Go

// Milestone 2 service-layer tests for the chatbot session preview
// turn-limit math (plan §3, §10 M2, §11).
//
// Two contracts are locked in here:
//
// 1. Turn-limit derivation: the service caller hands the repository
// turn_limit = min(request.limit, cfg.RecentTurns) per plan §3.
// This test exercises the boundary set so a future refactor cannot
// silently drop the cap.
// 2. Config validation: a service constructed with a config whose
// RecentTurns is out of [1, 5] must not be accepted. The
// authoritative validator lives in
// internal/serviceconfig/chatbot.ChatbotConfig.Validate; this test
// proves that path catches the boundaries plan §8 mandates.
//
// Compile contract: this file references bot.Service.New plus the
// bot.SessionListInput / bot.MinTurnLimit symbols that backend-eng
// emits during M2. Until then the file fails to compile — the failing
// state the team-lead M2 dispatch endorsed.
package bot_test
import (
"testing"
"queryorchestration/internal/bot"
"queryorchestration/internal/serviceconfig/chatbot"
"github.com/stretchr/testify/require"
)
// TestSessionList_TurnLimitMin proves min(request.limit, cfg.RecentTurns)
// at the service layer. The bot package exports MinTurnLimit (or
// equivalently named helper) so the service and any other caller agree
// on the cap arithmetic.
//
// With cfg.RecentTurns=5, the brief lists these (request.limit, want)
// pairs: (2, 2), (3, 3), (5, 5), (7, 5), (10, 5).
func TestSessionList_TurnLimitMin(t *testing.T) {
t.Parallel()
const recentTurns = 5
cases := []struct {
name string
requestLimit int
want int
}{
{name: "below_cap_two", requestLimit: 2, want: 2},
{name: "below_cap_three", requestLimit: 3, want: 3},
{name: "at_cap_five", requestLimit: 5, want: 5},
{name: "above_cap_seven", requestLimit: 7, want: 5},
{name: "above_cap_ten", requestLimit: 10, want: 5},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := bot.MinTurnLimit(c.requestLimit, recentTurns)
require.Equal(t, c.want, got,
"plan §3: turn_limit = min(request.limit, cfg.RecentTurns); "+
"min(%d, %d) expected %d, got %d",
c.requestLimit, recentTurns, c.want, got)
})
}
}
// TestSessionList_RejectsConfigOutOfRange proves that the chatbot
// config validator catches the same boundaries plan §8 lists, and
// that bot.New refuses a config whose Validate fails. Two layers,
// one assertion each — keeps the test focused on the service-level
// contract.
func TestSessionList_RejectsConfigOutOfRange(t *testing.T) {
t.Parallel()
cases := []struct {
name string
recentTurns int
}{
{name: "zero_rejected", recentTurns: 0},
{name: "six_rejected", recentTurns: 6},
{name: "negative_rejected", recentTurns: -1},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
cfg := chatbot.ChatbotConfig{
ServiceURL: "http://chatbot.local:8000",
APIKey: "k",
RequestTimeoutSeconds: 60,
MaxTurnChars: 2000,
RecentTurns: c.recentTurns,
}
require.Error(t, cfg.Validate(),
"plan §8: ChatbotConfig.Validate must reject RecentTurns=%d", c.recentTurns)
// bot.New must refuse the same invalid config so the
// service can never be constructed with an out-of-range
// turn-limit cap. The exact error type is not asserted
// here — only that construction fails.
_, err := bot.New(cfg, nil)
require.Errorf(t, err,
"bot.New must refuse a config with RecentTurns=%d", c.recentTurns)
})
}
}