internal/memory/carry_forward_test.go
Rama main · réplica de lectura del repositorio original.
package memory_test
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/yeomyeonggeori/blueclaw/internal/memory"
"github.com/yeomyeonggeori/blueclaw/internal/memory/memorytest"
"github.com/yeomyeonggeori/bluememo"
"github.com/yeomyeonggeori/bluememo/bluememotest"
)
func oldPathOf(t *testing.T, root string, parts ...string) string {
t.Helper()
directory := filepath.Join(append([]string{root, ".blueclaw", "memory"}, parts[:len(parts)-1]...)...)
if errorValue := os.MkdirAll(directory, 0o700); errorValue != nil {
t.Fatalf("make the old directory: %v", errorValue)
}
return filepath.Join(directory, parts[len(parts)-1])
}
// writeOldStore leaves a store at the old path holding one memory, closed, so
// its log is folded into the one file a cleanly stopped host leaves behind.
func writeOldStore(t *testing.T, path string, content string) {
t.Helper()
store, errorValue := bluememo.Open(context.Background(), path, bluememo.Configuration{Embedder: bluememotest.HashEmbedder{}, EmbeddingModel: "carry-test"})
if errorValue != nil {
t.Fatalf("open the old store: %v", errorValue)
}
if _, errorValue := store.Adopt(context.Background(), []bluememo.AdoptedMemory{{
Memory: bluememo.Memory{MemoryID: bluememo.NewIdentifier(), Content: content},
}}); errorValue != nil {
t.Fatalf("write the old store: %v", errorValue)
}
if errorValue := store.Close(); errorValue != nil {
t.Fatalf("close the old store: %v", errorValue)
}
}
func recalledContent(t *testing.T, stores *memory.Stores, scope memory.Scope, query string) []string {
t.Helper()
path, errorValue := stores.Path(scope)
if errorValue != nil {
t.Fatalf("where the memory belongs: %v", errorValue)
}
store, errorValue := bluememo.Open(context.Background(), path, bluememo.Configuration{Embedder: bluememotest.HashEmbedder{}, EmbeddingModel: "carry-test"})
if errorValue != nil {
t.Fatalf("open the carried store: %v", errorValue)
}
defer store.Close()
recalled, errorValue := store.Recall(context.Background(), query, 10)
if errorValue != nil {
t.Fatalf("recall from the carried store: %v", errorValue)
}
contents := []string{}
for _, held := range recalled.Memories {
contents = append(contents, held.Memory.Content)
}
return contents
}
func TestMemoryWrittenAtTheOldPathIsReadableAtTheNewOne(t *testing.T) {
stores, root := memorytest.OpenWithRoot(t)
writeOldStore(t, oldPathOf(t, root, "persons", "person-1.db"), "the quarterly ledger lives in Numbers")
writeOldStore(t, oldPathOf(t, root, "circles", "member.db"), "the all-hands is on Thursday")
writeOldStore(t, oldPathOf(t, root, "workspace.db"), "the office closes at seven")
report, errorValue := stores.CarryForward(context.Background())
if errorValue != nil {
t.Fatalf("carry the memory forward: %v", errorValue)
}
if report.Carried != 3 {
t.Fatalf("carried %d of the three stores, report %+v", report.Carried, report)
}
for _, carried := range []struct {
scope memory.Scope
query string
content string
}{
{memory.PersonScope("person-1"), "where is the ledger", "the quarterly ledger lives in Numbers"},
{memory.CircleScope("member"), "when is the all-hands", "the all-hands is on Thursday"},
{memory.WorkspaceScope(), "when does the office close", "the office closes at seven"},
} {
contents := recalledContent(t, stores, carried.scope, carried.query)
if len(contents) != 1 || contents[0] != carried.content {
t.Fatalf("%s holds %q, wanted %q", carried.scope.Kind, contents, carried.content)
}
}
}
func TestWhatOnlyTheLogHeldIsCarriedRatherThanTruncatedAway(t *testing.T) {
stores, root := memorytest.OpenWithRoot(t)
staging := filepath.Join(t.TempDir(), "memory.db")
store, errorValue := bluememo.Open(context.Background(), staging, bluememo.Configuration{Embedder: bluememotest.HashEmbedder{}, EmbeddingModel: "carry-test"})
if errorValue != nil {
t.Fatalf("open the staging store: %v", errorValue)
}
if _, errorValue := store.Adopt(context.Background(), []bluememo.AdoptedMemory{{
Memory: bluememo.Memory{MemoryID: bluememo.NewIdentifier(), Content: "the audit was filed on a Friday"},
}}); errorValue != nil {
t.Fatalf("write the staging store: %v", errorValue)
}
// Copy while it is still open, so the old path holds what a host killed
// mid-write leaves: a database whose recent writes live only in its log.
oldPath := oldPathOf(t, root, "persons", "person-1.db")
for _, suffix := range []string{"", "-wal", "-shm"} {
bytes, errorValue := os.ReadFile(staging + suffix)
if errorValue != nil {
t.Fatalf("read the staging %s: %v", suffix, errorValue)
}
if errorValue := os.WriteFile(oldPath+suffix, bytes, 0o600); errorValue != nil {
t.Fatalf("stage the old %s: %v", suffix, errorValue)
}
}
store.Close()
if _, errorValue := stores.CarryForward(context.Background()); errorValue != nil {
t.Fatalf("carry the memory forward: %v", errorValue)
}
contents := recalledContent(t, stores, memory.PersonScope("person-1"), "when was the audit filed")
if len(contents) != 1 || contents[0] != "the audit was filed on a Friday" {
t.Fatalf("the carried store holds %q, so what only the log held was lost", contents)
}
for _, suffix := range []string{"", "-wal", "-shm"} {
if _, errorValue := os.Stat(oldPath + suffix); !os.IsNotExist(errorValue) {
t.Fatalf("%s was left at the old path", filepath.Base(oldPath+suffix))
}
}
}
func TestAnOldFileIsFoldedIntoTheFileAlreadyThere(t *testing.T) {
stores, root := memorytest.OpenWithRoot(t)
oldPath := oldPathOf(t, root, "persons", "person-1.db")
writeOldStore(t, oldPath, "what the old path holds")
memorytest.Remember(t, stores, memory.PersonScope("person-1"), "what the new path holds")
report, errorValue := stores.CarryForward(context.Background())
if errorValue != nil {
t.Fatalf("carry the memory forward: %v", errorValue)
}
if report.Carried != 0 || report.Adopted != 1 {
t.Fatalf("report %+v, wanted one file folded in", report)
}
held := heldContent(t, stores, memory.PersonScope("person-1"))
if !held["what the old path holds"] || !held["what the new path holds"] {
t.Fatalf("the store holds %v, wanted what both paths held", held)
}
if _, errorValue := os.Stat(oldPath); !os.IsNotExist(errorValue) {
t.Fatal("the old file was left behind once its memory was folded in")
}
}
func heldContent(t *testing.T, stores *memory.Stores, scope memory.Scope) map[string]bool {
t.Helper()
store, errorValue := stores.Store(context.Background(), scope)
if errorValue != nil {
t.Fatalf("open the store: %v", errorValue)
}
memories, errorValue := store.Memories(context.Background())
if errorValue != nil {
t.Fatalf("read the store: %v", errorValue)
}
held := map[string]bool{}
for _, memory := range memories {
held[memory.Content] = true
}
return held
}
func TestCarryingForwardTwiceCarriesNothingTheSecondTime(t *testing.T) {
stores, root := memorytest.OpenWithRoot(t)
writeOldStore(t, oldPathOf(t, root, "persons", "person-1.db"), "the ledger lives in Numbers")
first, errorValue := stores.CarryForward(context.Background())
if errorValue != nil || first.Carried != 1 {
t.Fatalf("first carry %+v: %v", first, errorValue)
}
second, errorValue := stores.CarryForward(context.Background())
if errorValue != nil {
t.Fatalf("second carry: %v", errorValue)
}
if second.Carried != 0 || second.Adopted != 0 {
t.Fatalf("second carry %+v, wanted nothing to do", second)
}
}
func TestAHostThatNeverWroteAtTheOldPathCarriesNothing(t *testing.T) {
stores, _ := memorytest.OpenWithRoot(t)
report, errorValue := stores.CarryForward(context.Background())
if errorValue != nil {
t.Fatalf("a fresh host is not a failure: %v", errorValue)
}
if report.Carried != 0 || report.Adopted != 0 {
t.Fatalf("report %+v, wanted nothing to do", report)
}
}
func TestTheAdminCirclesOldFileIsLeftWhereItIs(t *testing.T) {
stores, root := memorytest.OpenWithRoot(t)
oldPath := oldPathOf(t, root, "circles", "admin.db")
writeOldStore(t, oldPath, "the board meets on the first Monday")
report, errorValue := stores.CarryForward(context.Background())
if errorValue != nil {
t.Fatalf("carry the memory forward: %v", errorValue)
}
if report.Carried != 0 || len(report.Left) != 1 || report.Left[0] != oldPath {
t.Fatalf("report %+v; the admin circle has no directory to carry into", report)
}
if _, errorValue := os.Stat(filepath.Join(root, "circles", "admin")); !os.IsNotExist(errorValue) {
t.Fatalf("circles/admin was made (%v); nothing sets up its group, so nobody could read it", errorValue)
}
}
func emptyCircleStore(t *testing.T, root string, circleID string) string {
t.Helper()
protected := filepath.Join(root, "circles", circleID, ".protected")
if errorValue := os.MkdirAll(protected, 0o700); errorValue != nil {
t.Fatal(errorValue)
}
store, errorValue := bluememo.Open(context.Background(), filepath.Join(protected, "memory.db"), bluememo.Configuration{Embedder: bluememotest.HashEmbedder{}, EmbeddingModel: "carry-test"})
if errorValue != nil {
t.Fatal(errorValue)
}
if errorValue := store.Close(); errorValue != nil {
t.Fatal(errorValue)
}
return filepath.Join(root, "circles", circleID)
}
func TestTheAdminCirclesFolderIsRemovedWhenItsStoreKeepsNothing(t *testing.T) {
stores, root := memorytest.OpenWithRoot(t)
folder := emptyCircleStore(t, root, "admin")
report, errorValue := stores.CarryForward(context.Background())
if errorValue != nil {
t.Fatalf("carry the memory forward: %v", errorValue)
}
if _, errorValue := os.Stat(folder); !os.IsNotExist(errorValue) {
t.Fatalf("circles/admin is still there (%v); report %+v", errorValue, report)
}
if len(report.Removed) != 1 || report.Removed[0] != folder {
t.Fatalf("report %+v names no removed folder", report)
}
}
func TestACircleStoreHoldingAMemoryIsKept(t *testing.T) {
stores, root := memorytest.OpenWithRoot(t)
protected := filepath.Join(root, "circles", "c-level", ".protected")
if errorValue := os.MkdirAll(protected, 0o700); errorValue != nil {
t.Fatal(errorValue)
}
writeOldStore(t, filepath.Join(protected, "memory.db"), "the board meets on the first Monday")
report, errorValue := stores.RemoveRetiredCircleFolders(context.Background(), []string{"c-level"})
if errorValue != nil {
t.Fatalf("remove retired circle folders: %v", errorValue)
}
if _, errorValue := os.Stat(filepath.Join(protected, "memory.db")); errorValue != nil {
t.Fatalf("a store that kept a memory was removed: %v", errorValue)
}
if len(report.Left) != 1 || len(report.Removed) != 0 {
t.Fatalf("report %+v; a kept folder must be named and none removed", report)
}
}
func TestACircleFolderStaysWhenSomebodyPutAFileThere(t *testing.T) {
stores, root := memorytest.OpenWithRoot(t)
folder := emptyCircleStore(t, root, "hr")
if errorValue := os.WriteFile(filepath.Join(folder, "agenda.md"), []byte("이샘플 presents"), 0o600); errorValue != nil {
t.Fatal(errorValue)
}
report, errorValue := stores.RemoveRetiredCircleFolders(context.Background(), []string{"hr"})
if errorValue != nil {
t.Fatalf("remove retired circle folders: %v", errorValue)
}
if _, errorValue := os.Stat(filepath.Join(folder, "agenda.md")); errorValue != nil {
t.Fatalf("a file somebody put in circles/hr was touched: %v", errorValue)
}
if len(report.Left) != 1 || report.Left[0] != folder {
t.Fatalf("report %+v; the folder kept for its file must be named", report)
}
}