178 lines
4.9 KiB
Go
178 lines
4.9 KiB
Go
package evolution_test
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/sipeed/picoclaw/pkg/evolution"
|
|
)
|
|
|
|
func TestStore_AppendLearningRecordsPersistsCaseAndRule(t *testing.T) {
|
|
root := t.TempDir()
|
|
paths := evolution.NewPaths(root, "")
|
|
store := evolution.NewStore(paths)
|
|
|
|
records := []evolution.LearningRecord{
|
|
{
|
|
ID: "case-1",
|
|
Kind: evolution.RecordKindCase,
|
|
WorkspaceID: "ws-1",
|
|
CreatedAt: time.Unix(1700000000, 0).UTC(),
|
|
Summary: "weather task completed",
|
|
Status: evolution.RecordStatus("new"),
|
|
},
|
|
{
|
|
ID: "rule-1",
|
|
Kind: evolution.RecordKindRule,
|
|
WorkspaceID: "ws-1",
|
|
CreatedAt: time.Unix(1700000100, 0).UTC(),
|
|
Summary: "prefer native-name weather path",
|
|
Status: evolution.RecordStatus("ready"),
|
|
},
|
|
}
|
|
|
|
if err := store.AppendLearningRecords(records); err != nil {
|
|
t.Fatalf("AppendLearningRecords: %v", err)
|
|
}
|
|
|
|
loaded, err := store.LoadLearningRecords()
|
|
if err != nil {
|
|
t.Fatalf("LoadLearningRecords: %v", err)
|
|
}
|
|
if len(loaded) != 2 {
|
|
t.Fatalf("len(loaded) = %d, want 2", len(loaded))
|
|
}
|
|
if loaded[1].Kind != evolution.RecordKindRule {
|
|
t.Fatalf("loaded[1].Kind = %q, want %q", loaded[1].Kind, evolution.RecordKindRule)
|
|
}
|
|
}
|
|
|
|
func TestStore_SaveDraftsOverwritesByID(t *testing.T) {
|
|
root := t.TempDir()
|
|
paths := evolution.NewPaths(root, "")
|
|
store := evolution.NewStore(paths)
|
|
|
|
first := evolution.SkillDraft{
|
|
ID: "draft-1",
|
|
WorkspaceID: "ws-1",
|
|
CreatedAt: time.Unix(1700000000, 0).UTC(),
|
|
SourceRecordID: "rule-1",
|
|
TargetSkillName: "weather",
|
|
DraftType: evolution.DraftTypeShortcut,
|
|
ChangeKind: evolution.ChangeKindAppend,
|
|
HumanSummary: "prefer native-name path first",
|
|
BodyOrPatch: "## Start Here",
|
|
Status: evolution.DraftStatusCandidate,
|
|
}
|
|
second := first
|
|
second.HumanSummary = "updated summary"
|
|
|
|
if err := store.SaveDrafts([]evolution.SkillDraft{first}); err != nil {
|
|
t.Fatalf("SaveDrafts(first): %v", err)
|
|
}
|
|
if err := store.SaveDrafts([]evolution.SkillDraft{second}); err != nil {
|
|
t.Fatalf("SaveDrafts(second): %v", err)
|
|
}
|
|
|
|
loaded, err := store.LoadDrafts()
|
|
if err != nil {
|
|
t.Fatalf("LoadDrafts: %v", err)
|
|
}
|
|
if len(loaded) != 1 {
|
|
t.Fatalf("len(loaded) = %d, want 1", len(loaded))
|
|
}
|
|
if loaded[0].HumanSummary != "updated summary" {
|
|
t.Fatalf("HumanSummary = %q, want %q", loaded[0].HumanSummary, "updated summary")
|
|
}
|
|
}
|
|
|
|
func TestStore_SaveDraftsKeepsSameIDDifferentWorkspace(t *testing.T) {
|
|
root := t.TempDir()
|
|
paths := evolution.NewPaths(root, "")
|
|
store := evolution.NewStore(paths)
|
|
|
|
first := evolution.SkillDraft{
|
|
ID: "draft-1",
|
|
WorkspaceID: "ws-1",
|
|
CreatedAt: time.Unix(1700000000, 0).UTC(),
|
|
SourceRecordID: "rule-1",
|
|
TargetSkillName: "weather",
|
|
DraftType: evolution.DraftTypeShortcut,
|
|
ChangeKind: evolution.ChangeKindAppend,
|
|
HumanSummary: "workspace one",
|
|
BodyOrPatch: "## Start Here",
|
|
Status: evolution.DraftStatusCandidate,
|
|
}
|
|
second := first
|
|
second.WorkspaceID = "ws-2"
|
|
second.HumanSummary = "workspace two"
|
|
|
|
if err := store.SaveDrafts([]evolution.SkillDraft{first}); err != nil {
|
|
t.Fatalf("SaveDrafts(first): %v", err)
|
|
}
|
|
if err := store.SaveDrafts([]evolution.SkillDraft{second}); err != nil {
|
|
t.Fatalf("SaveDrafts(second): %v", err)
|
|
}
|
|
|
|
loaded, err := store.LoadDrafts()
|
|
if err != nil {
|
|
t.Fatalf("LoadDrafts: %v", err)
|
|
}
|
|
if len(loaded) != 2 {
|
|
t.Fatalf("len(loaded) = %d, want 2", len(loaded))
|
|
}
|
|
if loaded[0].WorkspaceID == loaded[1].WorkspaceID {
|
|
t.Fatalf("loaded drafts should keep distinct workspace IDs: %+v", loaded)
|
|
}
|
|
}
|
|
|
|
func TestStore_LoadLearningRecordsIgnoresTruncatedTrailingLine(t *testing.T) {
|
|
root := t.TempDir()
|
|
paths := evolution.NewPaths(root, "")
|
|
store := evolution.NewStore(paths)
|
|
|
|
record := evolution.LearningRecord{
|
|
ID: "case-1",
|
|
Kind: evolution.RecordKindCase,
|
|
WorkspaceID: "ws-1",
|
|
CreatedAt: time.Unix(1700000000, 0).UTC(),
|
|
Summary: "weather task completed",
|
|
Status: evolution.RecordStatus("new"),
|
|
}
|
|
if err := store.AppendLearningRecords([]evolution.LearningRecord{record}); err != nil {
|
|
t.Fatalf("AppendLearningRecords: %v", err)
|
|
}
|
|
|
|
f, err := os.OpenFile(paths.LearningRecords, os.O_APPEND|os.O_WRONLY, 0o644)
|
|
if err != nil {
|
|
t.Fatalf("OpenFile: %v", err)
|
|
}
|
|
if _, err := f.WriteString("{\"id\":\"broken\""); err != nil {
|
|
f.Close()
|
|
t.Fatalf("WriteString: %v", err)
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
|
|
loaded, err := store.LoadLearningRecords()
|
|
if err != nil {
|
|
t.Fatalf("LoadLearningRecords: %v", err)
|
|
}
|
|
if len(loaded) != 1 {
|
|
t.Fatalf("len(loaded) = %d, want 1", len(loaded))
|
|
}
|
|
if loaded[0].ID != "case-1" {
|
|
t.Fatalf("loaded[0].ID = %q, want %q", loaded[0].ID, "case-1")
|
|
}
|
|
|
|
data, err := os.ReadFile(paths.LearningRecords)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile: %v", err)
|
|
}
|
|
if !strings.Contains(string(data), "\"broken\"") {
|
|
t.Fatalf("expected test fixture to include broken trailing line")
|
|
}
|
|
}
|