route.go gains Route.Affinity/Queue with PerRoute(), Queues() and affinity validation (checkRoutes moved here; config.go calls it once). limiter.Track counts a request without holding or refusing it; a release hands the slot to a waiter only while in flight <= parallel. The proxy leases a PerRoute() route under an empty fingerprint (the row keeps the real one) and uses Track when Queues() is false. Implemented by Ornith (OpenCode); owner review removed a release-on-first-flush workaround for a race in the owner's given test (see implementer log). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
76 lines
1.9 KiB
Go
76 lines
1.9 KiB
Go
package config_test
|
|
|
|
// v2.3 task 02: the affinity and queue route keys.
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.wntrmute.dev/kyle/crossbar/internal/config"
|
|
)
|
|
|
|
const affinityBase = `
|
|
listen = "127.0.0.1:1"
|
|
[hosts.a]
|
|
base_url = "http://a:1"
|
|
models = { "m" = { } }
|
|
[routes.plain]
|
|
hosts = ["a"]
|
|
[routes.convo]
|
|
hosts = ["a"]
|
|
affinity = "conversation"
|
|
[routes.boxmaker]
|
|
hosts = ["a"]
|
|
affinity = "route"
|
|
queue = false
|
|
[routes."bm-*"]
|
|
hosts = ["a"]
|
|
affinity = "route"
|
|
queue = false
|
|
[routes.queued]
|
|
hosts = ["a"]
|
|
queue = true
|
|
`
|
|
|
|
func TestAffinityAndQueueKeys(t *testing.T) {
|
|
c, err := config.Parse(strings.NewReader(affinityBase))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, tc := range []struct {
|
|
route string
|
|
perRoute, queues bool
|
|
}{
|
|
{"plain", false, true}, // defaults: conversation affinity, queueing on
|
|
{"convo", false, true},
|
|
{"boxmaker", true, false},
|
|
{"bm-agent-1", true, false}, // a template's keys reach its concrete routes
|
|
{"queued", false, true},
|
|
} {
|
|
r, _, ok := c.Route(tc.route)
|
|
if !ok {
|
|
t.Fatalf("route %q not found", tc.route)
|
|
}
|
|
if r.PerRoute() != tc.perRoute || r.Queues() != tc.queues {
|
|
t.Errorf("%s: PerRoute %v Queues %v, want %v %v", tc.route, r.PerRoute(), r.Queues(), tc.perRoute, tc.queues)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAffinityRejectsUnknownValues(t *testing.T) {
|
|
for _, bad := range []string{`"session"`, `"Route"`, `1`} {
|
|
text := strings.Replace(affinityBase, `affinity = "conversation"`, "affinity = "+bad, 1)
|
|
_, err := config.Parse(strings.NewReader(text))
|
|
if err == nil || !strings.Contains(err.Error(), "routes.convo.affinity") {
|
|
t.Errorf("affinity = %s: err %v, want one naming routes.convo.affinity", bad, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQueueMustBeABool(t *testing.T) {
|
|
text := strings.Replace(affinityBase, "queue = true", `queue = "no"`, 1)
|
|
if _, err := config.Parse(strings.NewReader(text)); err == nil {
|
|
t.Error(`queue = "no" parsed; want an error`)
|
|
}
|
|
}
|