Merged in feature/chatbot-limits (pull request #228)
fix chatbot limits and test * add tests
This commit is contained in:
@@ -15,6 +15,16 @@ import "fmt"
|
||||
// 64 KiB matches the plan literal `64 * 1024`.
|
||||
const StateMaxBytes = 64 * 1024
|
||||
|
||||
// MaxTurnCharsCeiling is the largest legal value for MaxTurnChars.
|
||||
// It mirrors the OpenAPI schema constraint on the prompt body at
|
||||
// serviceAPIs/queryAPI.yaml (BotTurnAddRequest.prompt.maxLength=65536).
|
||||
// Setting CHATBOT_MAX_TURN_CHARS above this ceiling would create a
|
||||
// silent inconsistency: the schema validator would reject oversized
|
||||
// prompts before the service-layer length guard ran, leaving operators
|
||||
// with no signal that the configured cap is unreachable. Any change to
|
||||
// this value must be made in lockstep with the OpenAPI spec.
|
||||
const MaxTurnCharsCeiling = 65536
|
||||
|
||||
// ChatbotConfig is the queryAPI-side configuration block for the FastAPI
|
||||
// chatbot agent service. Fields, env-var names, defaults, and the
|
||||
// notEmpty/required tags are dictated verbatim by plan §8.
|
||||
@@ -53,6 +63,11 @@ func (c ChatbotConfig) Validate() error {
|
||||
if c.MaxTurnChars < 1 {
|
||||
return fmt.Errorf("chatbot config: MaxTurnChars must be >= 1, got %d", c.MaxTurnChars)
|
||||
}
|
||||
if c.MaxTurnChars > MaxTurnCharsCeiling {
|
||||
return fmt.Errorf(
|
||||
"chatbot config: MaxTurnChars must be <= %d (OpenAPI prompt.maxLength), got %d",
|
||||
MaxTurnCharsCeiling, c.MaxTurnChars)
|
||||
}
|
||||
if c.RecentTurns < 1 || c.RecentTurns > 5 {
|
||||
return fmt.Errorf("chatbot config: RecentTurns must be in [1, 5], got %d", c.RecentTurns)
|
||||
}
|
||||
|
||||
@@ -266,6 +266,96 @@ func TestChatbotConfig_Validate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestChatbotConfig_MaxTurnChars_NonDefault_Loaded covers Gap 1: prior
|
||||
// to this test no case asserted that CHATBOT_MAX_TURN_CHARS=5000 (a
|
||||
// non-default value) was both loaded by env.ParseWithOptions and
|
||||
// accepted by Validate(). The plan permits any operator-chosen value
|
||||
// >= 1; this test pins that contract for a representative larger cap
|
||||
// so a regression that hard-codes the default cannot pass silently.
|
||||
func TestChatbotConfig_MaxTurnChars_NonDefault_Loaded(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
envMap := requiredEnvBase()
|
||||
envMap["CHATBOT_MAX_TURN_CHARS"] = "5000"
|
||||
|
||||
cfg, err := loadConfig(t, envMap)
|
||||
require.NoError(t, err, "loader must accept CHATBOT_MAX_TURN_CHARS=5000")
|
||||
require.Equal(t, 5000, cfg.MaxTurnChars,
|
||||
"non-default CHATBOT_MAX_TURN_CHARS must be honored by the loader")
|
||||
require.NoError(t, cfg.Validate(),
|
||||
"Validate must accept a non-default MaxTurnChars within the legal range")
|
||||
}
|
||||
|
||||
// TestChatbotConfig_MaxTurnChars_AtOpenAPILimit_Accepted locks the
|
||||
// upper-edge boundary: MaxTurnChars=65536 (exactly the OpenAPI
|
||||
// prompt.maxLength) must load and validate. Pinned alongside the
|
||||
// rejection cases so an off-by-one in the ceiling check fails loudly.
|
||||
func TestChatbotConfig_MaxTurnChars_AtOpenAPILimit_Accepted(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
envMap := requiredEnvBase()
|
||||
envMap["CHATBOT_MAX_TURN_CHARS"] = "65536"
|
||||
|
||||
cfg, err := loadConfig(t, envMap)
|
||||
require.NoError(t, err, "loader must accept CHATBOT_MAX_TURN_CHARS=65536")
|
||||
require.Equal(t, chatbot.MaxTurnCharsCeiling, cfg.MaxTurnChars,
|
||||
"loaded value must equal the documented ceiling")
|
||||
require.NoError(t, cfg.Validate(),
|
||||
"Validate must accept MaxTurnChars exactly at MaxTurnCharsCeiling")
|
||||
}
|
||||
|
||||
// TestChatbotConfig_MaxTurnChars_AboveOpenAPILimit_Rejected covers Gap 2:
|
||||
// the OpenAPI schema (serviceAPIs/queryAPI.yaml: prompt.maxLength=65536)
|
||||
// caps the prompt body at 65,536 runes. If CHATBOT_MAX_TURN_CHARS is set
|
||||
// above that ceiling the system enters an inconsistent state — the
|
||||
// generated request validator silently truncates the cap to 65,536 while
|
||||
// the service-layer guard believes a higher cap is in force. Validate()
|
||||
// MUST refuse this configuration so the inconsistency is caught at boot,
|
||||
// not on the first oversized prompt. Expected to FAIL until backend-eng
|
||||
// adds an upper-bound check tied to the OpenAPI maxLength.
|
||||
func TestChatbotConfig_MaxTurnChars_AboveOpenAPILimit_Rejected(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
envMap := requiredEnvBase()
|
||||
envMap["CHATBOT_MAX_TURN_CHARS"] = "65537"
|
||||
|
||||
cfg, err := loadConfig(t, envMap)
|
||||
require.NoError(t, err, "loader must parse the integer value")
|
||||
validateErr := cfg.Validate()
|
||||
require.Error(t, validateErr,
|
||||
"Validate must reject MaxTurnChars > OpenAPI prompt.maxLength=65536; "+
|
||||
"otherwise oversized prompts are silently rejected by the schema validator "+
|
||||
"with no operator-visible signal that the configured cap is unreachable")
|
||||
require.True(t,
|
||||
strings.Contains(validateErr.Error(), "MaxTurnChars"),
|
||||
"error %q must name the offending field for actionable diagnostics",
|
||||
validateErr.Error())
|
||||
}
|
||||
|
||||
// TestChatbotConfig_MaxTurnChars_GrosslyOverLimit_Rejected covers Gap 3:
|
||||
// Validate() currently only enforces MaxTurnChars >= 1 — there is no
|
||||
// upper bound at all. An operator typo such as CHATBOT_MAX_TURN_CHARS=
|
||||
// 10000000 loads cleanly today, silently exceeds the OpenAPI ceiling,
|
||||
// and creates the inconsistency described in Gap 2 at scale. This test
|
||||
// asserts that any value above the documented ceiling is rejected.
|
||||
// Expected to FAIL until backend-eng adds the upper-bound check.
|
||||
func TestChatbotConfig_MaxTurnChars_GrosslyOverLimit_Rejected(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
envMap := requiredEnvBase()
|
||||
envMap["CHATBOT_MAX_TURN_CHARS"] = "10000000"
|
||||
|
||||
cfg, err := loadConfig(t, envMap)
|
||||
require.NoError(t, err, "loader must parse the integer value")
|
||||
validateErr := cfg.Validate()
|
||||
require.Error(t, validateErr,
|
||||
"Validate must reject grossly over-limit MaxTurnChars to fail-fast on operator typos")
|
||||
require.True(t,
|
||||
strings.Contains(validateErr.Error(), "MaxTurnChars"),
|
||||
"error %q must name the offending field",
|
||||
validateErr.Error())
|
||||
}
|
||||
|
||||
// TestChatbotConfig_InflightGraceSeconds locks down the derived value
|
||||
// from plan §8: "InflightGraceSeconds = 2 * RequestTimeoutSeconds". The
|
||||
// helper is consumed by AddTurn (M4) to size the grace window passed to
|
||||
|
||||
Reference in New Issue
Block a user