package memory import ( "strings" "testing" "time" ) // The rule that makes storing an observation about a person defensible: it can // be found. A memory about somebody that names nobody cannot be shown to them // on request and cannot be erased for them, so it is refused at the door. func TestAPersonalMemoryWithoutASubjectIsRefused(t *testing.T) { for _, subject := range []Subject{SubjectCandidate, SubjectUser} { w := Write{SubjectType: subject, Text: "seemed unreliable", Author: AuthorModel} if err := w.Validate(); err != ErrSubjectRequired { t.Errorf("%s without a subject id: got %v, want ErrSubjectRequired", subject, err) } } } // A workspace fact has no personal subject and must not be made to invent one. func TestAWorkspaceMemoryNeedsNoSubject(t *testing.T) { w := Write{SubjectType: SubjectWorkspace, Text: "This venue staffs on Thursdays.", Author: AuthorModel} if err := w.Validate(); err != nil { t.Errorf("a workspace fact was refused: %v", err) } } func TestAnEmptyMemoryIsRefused(t *testing.T) { w := Write{SubjectType: SubjectWorkspace, Text: " ", Author: AuthorModel} if err := w.Validate(); err != ErrEmpty { t.Errorf("got %v, want ErrEmpty", err) } } // A memory is a sentence. The long form of something belongs in the corpus, // which has ingestion review and search; this table has neither. func TestAMemoryLongerThanASentenceIsRefused(t *testing.T) { w := Write{SubjectType: SubjectWorkspace, Text: strings.Repeat("x", MaxTextRunes+1), Author: AuthorModel} if err := w.Validate(); err == nil { t.Error("an over-long memory was accepted") } } func TestAnUnknownSubjectOrAuthorIsRefused(t *testing.T) { if err := (Write{SubjectType: "anything", Text: "x", Author: AuthorModel}).Validate(); err == nil { t.Error("an invented subject type was accepted") } if err := (Write{SubjectType: SubjectWorkspace, Text: "x", Author: "nobody"}).Validate(); err == nil { t.Error("an invented author was accepted") } } // Everything written decays. A fact with no end date is read back long after // it stopped being true. func TestTheDefaultTTLIsBounded(t *testing.T) { if DefaultTTL <= 0 || DefaultTTL > 365*24*time.Hour { t.Errorf("DefaultTTL = %v; a memory must expire, and within a year", DefaultTTL) } } /* ── What the model is shown ─────────────────────────────────────────────── */ func TestRenderFencesAndLabelsMemories(t *testing.T) { out := Render([]Record{ {SubjectType: SubjectWorkspace, Author: AuthorModel, Text: "Thursdays are short-staffed."}, }) for _, want := range []string{"", "", "never", "instructions"} { if !strings.Contains(out, want) { t.Errorf("the memory block does not contain %q:\n%s", want, out) } } } // An inference and a recruiter's note are different kinds of claim. Flattening // them lets "the model thought X" be read back later as "X". func TestRenderSaysWhetherAMemoryWasInferredOrWritten(t *testing.T) { out := Render([]Record{ {SubjectType: SubjectCandidate, SubjectID: "c1", Author: AuthorModel, Text: "A"}, {SubjectType: SubjectCandidate, SubjectID: "c2", Author: AuthorPerson, Text: "B"}, }) if !strings.Contains(out, "inferred by an agent") || !strings.Contains(out, "noted by a person") { t.Errorf("the origin of each memory is not stated:\n%s", out) } } // The block says plainly that a memory is not a reason to reject somebody. // This is the sentence that keeps a remembered impression from being read as a // decision, so it is pinned by a test rather than left to an edit. func TestRenderRefusesToLetAMemoryDecide(t *testing.T) { out := Render([]Record{{SubjectType: SubjectCandidate, SubjectID: "c1", Author: AuthorModel, Text: "A"}}) if !strings.Contains(out, "never a reason on their own to accept or reject") { t.Errorf("the block does not say a memory cannot decide:\n%s", out) } } func TestRenderIsEmptyWhenThereIsNothingToRemember(t *testing.T) { if Render(nil) != "" { t.Error("an empty memory set must add nothing to the prompt") } }