First Commit
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
detections / detections (push) Canceled after 0s
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
detections / detections (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
package db
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestProfileMaxTokensRoundTrip pins the output-cap columns through the whole
|
||||
// read/write surface: both column lists (list vs keyed loads) must carry them,
|
||||
// and an update must not drop them. A miscounted column list silently shifts
|
||||
// every later Scan target, so this is the test that catches it.
|
||||
func TestProfileMaxTokensRoundTrip(t *testing.T) {
|
||||
d, err := Open(testDSN(t))
|
||||
if err != nil {
|
||||
t.Skipf("postgres unavailable (%v) — skipping", err)
|
||||
}
|
||||
// Close via Cleanup, registered FIRST so it runs LAST: cleanups are LIFO, and a
|
||||
// plain `defer d.Close()` would fire before them — the row-deleting cleanups
|
||||
// would then run against a closed pool and silently leave test rows behind.
|
||||
t.Cleanup(func() { d.Close() })
|
||||
|
||||
id, err := d.SaveProfile(&LLMProfile{
|
||||
Name: "t-maxtok", Format: "openai", Model: "m", APIKey: "k",
|
||||
MaxTokens: 8192, MaxTokensField: "max_completion_tokens",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { d.Exec(`DELETE FROM llm_profiles WHERE id=$1`, id) })
|
||||
|
||||
// Keyed single-row load (profileColsKey).
|
||||
p, err := d.ProfileByID(id)
|
||||
if err != nil || p == nil {
|
||||
t.Fatalf("ProfileByID: %v, p=%v", err, p)
|
||||
}
|
||||
if p.MaxTokens != 8192 || p.MaxTokensField != "max_completion_tokens" {
|
||||
t.Fatalf("keyed load: max_tokens=%d field=%q", p.MaxTokens, p.MaxTokensField)
|
||||
}
|
||||
|
||||
// List load (profileCols — the hint variant, a separate column list).
|
||||
ps, err := d.ListProfiles()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var found *LLMProfile
|
||||
for _, x := range ps {
|
||||
if x.ID == id {
|
||||
found = x
|
||||
}
|
||||
}
|
||||
if found == nil {
|
||||
t.Fatal("profile missing from ListProfiles")
|
||||
}
|
||||
if found.MaxTokens != 8192 || found.MaxTokensField != "max_completion_tokens" {
|
||||
t.Fatalf("list load: max_tokens=%d field=%q", found.MaxTokens, found.MaxTokensField)
|
||||
}
|
||||
|
||||
// Update with a blank key takes the "keep existing key" UPDATE branch, which
|
||||
// has its own column list and is the easiest one to forget.
|
||||
p.APIKey = ""
|
||||
p.MaxTokens = 4096
|
||||
p.MaxTokensField = ""
|
||||
if _, err := d.SaveProfile(p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
after, err := d.ProfileByID(id)
|
||||
if err != nil || after == nil {
|
||||
t.Fatalf("reload: %v", err)
|
||||
}
|
||||
if after.MaxTokens != 4096 || after.MaxTokensField != "" {
|
||||
t.Fatalf("after update: max_tokens=%d field=%q", after.MaxTokens, after.MaxTokensField)
|
||||
}
|
||||
if after.APIKey != "k" {
|
||||
t.Fatalf("blank key on update must keep the stored one, got %q", after.APIKey)
|
||||
}
|
||||
}
|
||||
|
||||
// A profile saved without touching the new fields must read back as "no cap,
|
||||
// classic field name" — the pre-feature behaviour old rows also get.
|
||||
func TestProfileMaxTokensDefaults(t *testing.T) {
|
||||
d, err := Open(testDSN(t))
|
||||
if err != nil {
|
||||
t.Skipf("postgres unavailable (%v) — skipping", err)
|
||||
}
|
||||
// Close via Cleanup, registered FIRST so it runs LAST: cleanups are LIFO, and a
|
||||
// plain `defer d.Close()` would fire before them — the row-deleting cleanups
|
||||
// would then run against a closed pool and silently leave test rows behind.
|
||||
t.Cleanup(func() { d.Close() })
|
||||
|
||||
id, err := d.SaveProfile(&LLMProfile{Name: "t-maxtok-default", Format: "openai", Model: "m", APIKey: "k"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { d.Exec(`DELETE FROM llm_profiles WHERE id=$1`, id) })
|
||||
|
||||
p, err := d.ProfileByID(id)
|
||||
if err != nil || p == nil {
|
||||
t.Fatalf("ProfileByID: %v", err)
|
||||
}
|
||||
if p.MaxTokens != 0 || p.MaxTokensField != "" {
|
||||
t.Fatalf("defaults: max_tokens=%d field=%q, want 0 and \"\"", p.MaxTokens, p.MaxTokensField)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user