Phase 4b shipped Seed + RenderFaded + Tick + Refresh against the sdkfade primitives but the test file still only covered the original no-fade behavior. These add: - Add() populates Seed - RenderFaded == Render when d <= N (safe period) - RenderFaded masks underscores once d crosses the safe period - Tick drops entries past the M% threshold + leaves fresh ones - Refresh resets LastRefreshAtTurn + regenerates Seed
209 lines
5.4 KiB
Go
209 lines
5.4 KiB
Go
package kbblock
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
sdkfade "git.hangman-lab.top/hzhang/Plexum-sdk-go/fade"
|
|
)
|
|
|
|
func TestSessionDir(t *testing.T) {
|
|
got := SessionDir("/root/.plexum", "alice", "s_1")
|
|
want := "/root/.plexum/agents/alice/sessions/s_1/plugins/harbor-forge"
|
|
if got != want {
|
|
t.Errorf("SessionDir = %q want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestOpenMissingFile(t *testing.T) {
|
|
b, err := Open(t.TempDir(), "alice", "s_1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if b.Len() != 0 || b.NextSeq != 1 {
|
|
t.Errorf("empty open: Len=%d NextSeq=%d", b.Len(), b.NextSeq)
|
|
}
|
|
}
|
|
|
|
func TestOpenRequiresAllArgs(t *testing.T) {
|
|
if _, err := Open("", "a", "s"); err == nil {
|
|
t.Error("want error on empty profileRoot")
|
|
}
|
|
if _, err := Open("/x", "", "s"); err == nil {
|
|
t.Error("want error on empty agentID")
|
|
}
|
|
if _, err := Open("/x", "a", ""); err == nil {
|
|
t.Error("want error on empty sessionID")
|
|
}
|
|
}
|
|
|
|
func TestAddAndDuplicate(t *testing.T) {
|
|
b, _ := Open(t.TempDir(), "a", "s")
|
|
e := b.Add(42, "KB-PAYROT", "debugging", "OOM fix", 3)
|
|
if e == nil || e.ID != 42 || e.KBCode != "KB-PAYROT" || e.SourceTopic != "debugging" ||
|
|
e.InsertSeq != 1 || e.AddedAtTurn != 3 {
|
|
t.Errorf("entry wrong: %+v", e)
|
|
}
|
|
if b.Add(42, "X", "y", "z", 7) != nil {
|
|
t.Error("dup should return nil")
|
|
}
|
|
if b.Len() != 1 {
|
|
t.Errorf("Len=%d", b.Len())
|
|
}
|
|
}
|
|
|
|
func TestRoundtrip(t *testing.T) {
|
|
dir := t.TempDir()
|
|
b, _ := Open(dir, "a", "s")
|
|
b.Add(101, "KB-A", "topic-a", "a", 1)
|
|
b.Add(202, "KB-A", "topic-b", "b", 2)
|
|
if err := b.Save(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b2, _ := Open(dir, "a", "s")
|
|
if b2.Len() != 2 || b2.NextSeq != 3 {
|
|
t.Errorf("reopen Len=%d NextSeq=%d", b2.Len(), b2.NextSeq)
|
|
}
|
|
if !b2.Has(101) || !b2.Has(202) {
|
|
t.Error("entries missing after reopen")
|
|
}
|
|
}
|
|
|
|
func TestRenderInsertOrder(t *testing.T) {
|
|
b, _ := Open(t.TempDir(), "a", "s")
|
|
b.Add(999, "K", "t", "first", 0)
|
|
b.Add(1, "K", "t", "second", 0)
|
|
out := b.Render()
|
|
idx999 := strings.Index(out, "id=999")
|
|
idx1 := strings.Index(out, "id=1 ")
|
|
if !(idx999 >= 0 && idx1 > idx999) {
|
|
t.Errorf("order broken: %q", out)
|
|
}
|
|
}
|
|
|
|
func TestRenderAttributes(t *testing.T) {
|
|
b, _ := Open(t.TempDir(), "a", "s")
|
|
b.Add(42, "KB-PAYROT", "debugging", "OOM fix: bump heap", 0)
|
|
out := b.Render()
|
|
if !strings.HasPrefix(out, "<kb-fact id=42 kb=KB-PAYROT source=topic:debugging>\n") {
|
|
t.Errorf("head wrong: %q", out)
|
|
}
|
|
if !strings.HasSuffix(out, "</kb-fact>\n") {
|
|
t.Errorf("tail wrong: %q", out)
|
|
}
|
|
}
|
|
|
|
func TestRenderOmitsSourceWhenEmpty(t *testing.T) {
|
|
b, _ := Open(t.TempDir(), "a", "s")
|
|
b.Add(7, "KB-X", "", "raw", 0)
|
|
out := b.Render()
|
|
if strings.Contains(out, "source=topic:") {
|
|
t.Errorf("source attr leaked: %q", out)
|
|
}
|
|
if !strings.Contains(out, "<kb-fact id=7 kb=KB-X>") {
|
|
t.Errorf("tight tag missing: %q", out)
|
|
}
|
|
}
|
|
|
|
func TestRemove(t *testing.T) {
|
|
b, _ := Open(t.TempDir(), "a", "s")
|
|
b.Add(1, "K", "t", "a", 0)
|
|
b.Add(2, "K", "t", "b", 0)
|
|
b.Add(3, "K", "t", "c", 0)
|
|
removed := b.Remove(2, 99, 3)
|
|
if len(removed) != 2 {
|
|
t.Errorf("removed=%v", removed)
|
|
}
|
|
if b.Len() != 1 || !b.Has(1) {
|
|
t.Errorf("post-state wrong: Len=%d", b.Len())
|
|
}
|
|
}
|
|
|
|
func TestRenderEmpty(t *testing.T) {
|
|
b, _ := Open(t.TempDir(), "a", "s")
|
|
if b.Render() != "" {
|
|
t.Error("empty Render should be \"\"")
|
|
}
|
|
}
|
|
|
|
func TestAddPopulatesSeed(t *testing.T) {
|
|
b, _ := Open(t.TempDir(), "a", "s")
|
|
e := b.Add(1, "K", "t", "x", 0)
|
|
if e == nil || e.Seed == 0 {
|
|
t.Errorf("Seed should be non-zero, got %d", e.Seed)
|
|
}
|
|
}
|
|
|
|
func TestRenderFadedDistanceZeroIsIdentity(t *testing.T) {
|
|
b, _ := Open(t.TempDir(), "a", "s")
|
|
b.Add(1, "K", "t", "hello world", 5)
|
|
plain := b.Render()
|
|
faded := b.RenderFaded(5, sdkfade.DefaultFadeParams())
|
|
if plain != faded {
|
|
t.Errorf("d=0 should be no-op:\nplain=%q\nfaded=%q", plain, faded)
|
|
}
|
|
}
|
|
|
|
func TestRenderFadedAppliesMaskOverTime(t *testing.T) {
|
|
b, _ := Open(t.TempDir(), "a", "s")
|
|
b.Add(1, "K", "t", "the quick brown fox jumps over the lazy dog", 0)
|
|
plain := b.Render()
|
|
faded := b.RenderFaded(20, sdkfade.DefaultFadeParams())
|
|
if plain == faded {
|
|
t.Errorf("d=20 should mask, but identical: %q", plain)
|
|
}
|
|
if !strings.Contains(faded, "_") {
|
|
t.Errorf("expected underscore masking in faded output: %q", faded)
|
|
}
|
|
}
|
|
|
|
func TestTickDropsHighlyFaded(t *testing.T) {
|
|
b, _ := Open(t.TempDir(), "a", "s")
|
|
b.Add(1, "K", "t", "short", 0)
|
|
b.Add(2, "K", "t", "this entry stays freshish", 100)
|
|
res := b.Tick(105, sdkfade.DefaultFadeParams())
|
|
hasID1Dropped := false
|
|
for _, id := range res.FadedOut {
|
|
if id == 1 {
|
|
hasID1Dropped = true
|
|
}
|
|
}
|
|
if !hasID1Dropped {
|
|
t.Errorf("entry 1 (d=105) should have crossed drop threshold: faded=%v", res.FadedOut)
|
|
}
|
|
if !b.Has(2) {
|
|
t.Error("entry 2 (d=5) should still be present")
|
|
}
|
|
}
|
|
|
|
func TestRefreshResetsClock(t *testing.T) {
|
|
b, _ := Open(t.TempDir(), "a", "s")
|
|
b.Add(1, "K", "t", "content", 0)
|
|
oldSeed := b.Lookup(1).Seed
|
|
got := b.Refresh([]int{1, 99}, 50)
|
|
if len(got) != 1 || got[0] != 1 {
|
|
t.Errorf("Refresh returned %v, want [1]", got)
|
|
}
|
|
e := b.Lookup(1)
|
|
if e.LastRefreshAtTurn != 50 {
|
|
t.Errorf("LastRefreshAtTurn = %d, want 50", e.LastRefreshAtTurn)
|
|
}
|
|
if e.Seed == oldSeed {
|
|
t.Error("Refresh should regenerate Seed")
|
|
}
|
|
}
|
|
|
|
func TestSaveEmptyNoFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
b, _ := Open(dir, "a", "s")
|
|
if err := b.Save(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
path := filepath.Join(SessionDir(dir, "a", "s"), FileName)
|
|
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
|
t.Errorf("file should not exist: %v", err)
|
|
}
|
|
}
|