internal/learning/store_test.go
Rama main · réplica de lectura del repositorio original.
package learning
import (
"encoding/json"
"os"
"path/filepath"
"sync"
"testing"
)
func TestStoreCapsActiveSkillsAndRestoresRetiredVersion(t *testing.T) {
store, errorValue := Open(t.TempDir()+"/skills.json", 1)
if errorValue != nil {
t.Fatal(errorValue)
}
created, errorValue := store.Put(Skill{ID: "skill-1", Audience: "company", Instruction: "Do the work", Status: "active"})
if errorValue != nil || created.Version != 1 {
t.Fatalf("create failed: %+v %v", created, errorValue)
}
if _, errorValue := store.Put(Skill{ID: "skill-2", Audience: "company", Instruction: "Another procedure", Status: "active"}); errorValue == nil {
t.Fatal("expected active cap")
}
if errorValue := store.Retire("skill-1"); errorValue != nil {
t.Fatal(errorValue)
}
if _, errorValue := store.Restore("skill-1"); errorValue != nil {
t.Fatal(errorValue)
}
if active := store.List("company", false); len(active) != 1 || active[0].Status != "active" {
t.Fatalf("unexpected active list: %+v", active)
}
}
func TestStoreFailedPersistenceKeepsState(t *testing.T) {
path := filepath.Join(t.TempDir(), "skills.json")
store, errorValue := Open(path, 2)
if errorValue != nil {
t.Fatal(errorValue)
}
if errorValue := os.Mkdir(path, 0o700); errorValue != nil {
t.Fatal(errorValue)
}
if _, errorValue := store.Put(Skill{ID: "skill-1", Audience: "company", Instruction: "procedure"}); errorValue == nil {
t.Fatal("expected persistence failure")
}
if len(store.List("company", true)) != 0 {
t.Fatal("failed persistence changed memory")
}
}
func TestStoreRejectsMalformedSkillDocumentWithoutMutation(t *testing.T) {
store, errorValue := Open(filepath.Join(t.TempDir(), "skills.json"), 2)
if errorValue != nil {
t.Fatal(errorValue)
}
if _, errorValue := store.Put(Skill{ID: "broken", Audience: "company", Description: "Broken", Instruction: "---\nname: broken\n---\n", Status: "active"}); errorValue == nil {
t.Fatal("malformed learned document was accepted")
}
if len(store.List("company", true)) != 0 {
t.Fatal("malformed learned document changed store state")
}
created, errorValue := store.Put(Skill{ID: "valid", Audience: "company", Instruction: "---\nname: valid\ndescription: A valid procedure.\n---\nUse the verified procedure.", Status: "active"})
if errorValue != nil || created.Version != 1 {
t.Fatalf("valid learned document rejected: %+v %v", created, errorValue)
}
}
func TestStoreRejectsMalformedReplacementAndPersistedDocument(t *testing.T) {
path := filepath.Join(t.TempDir(), "skills.json")
store, errorValue := Open(path, 2)
if errorValue != nil {
t.Fatal(errorValue)
}
created, errorValue := store.Put(Skill{ID: "replaceable", Audience: "company", Description: "A procedure", Instruction: "Use the procedure.", Status: "active"})
if errorValue != nil {
t.Fatal(errorValue)
}
if _, errorValue := store.ApplyDecision(StoreDecision{Action: "replace", ReplaceID: created.ID, ExpectedVersion: created.Version, Skill: Skill{ID: "replacement", Audience: "company", Description: "Broken", Instruction: "---\nname: replacement\n---\n", Status: "active"}}); errorValue == nil {
t.Fatal("malformed replacement was accepted")
}
if skills := store.List("company", true); len(skills) != 1 || skills[0].ID != created.ID {
t.Fatalf("malformed replacement changed store state: %+v", skills)
}
malformed, errorValue := json.Marshal(map[string][]Skill{"bad": {{ID: "bad", Audience: "company", Instruction: "---\nname: bad\n---\n", Status: "active"}}})
if errorValue != nil {
t.Fatal(errorValue)
}
if errorValue := os.WriteFile(path, malformed, 0600); errorValue != nil {
t.Fatal(errorValue)
}
if _, errorValue := Open(path, 2); errorValue == nil {
t.Fatal("malformed persisted skill was silently accepted")
}
}
func TestStoreConcurrentActivationRespectsCap(t *testing.T) {
store, errorValue := Open(filepath.Join(t.TempDir(), "skills.json"), 1)
if errorValue != nil {
t.Fatal(errorValue)
}
for _, id := range []string{"a", "b"} {
if _, errorValue := store.Put(Skill{ID: id, Audience: "company", Instruction: id}); errorValue != nil {
t.Fatal(errorValue)
}
}
var waitGroup sync.WaitGroup
successes := 0
var mutex sync.Mutex
for _, id := range []string{"a", "b"} {
waitGroup.Add(1)
go func(id string) {
defer waitGroup.Done()
if _, errorValue := store.Activate(id, 1); errorValue == nil {
mutex.Lock()
successes++
mutex.Unlock()
}
}(id)
}
waitGroup.Wait()
if successes != 1 {
t.Fatalf("expected one activation, got %d", successes)
}
}
func TestStoreSettingsSurviveRestart(t *testing.T) {
path := filepath.Join(t.TempDir(), "skills.json")
store, errorValue := Open(path, 20)
if errorValue != nil {
t.Fatal(errorValue)
}
if errorValue := store.UpdateSettings(Settings{Enabled: false, ActiveLimit: 7}); errorValue != nil {
t.Fatal(errorValue)
}
restarted, errorValue := Open(path, 20)
if errorValue != nil {
t.Fatal(errorValue)
}
settings := restarted.Settings()
if settings.Enabled || settings.ActiveLimit != 7 {
t.Fatalf("settings did not persist: %+v", settings)
}
}
func TestStoreSettingsCreateNestedDirectoryAndStayPrivate(t *testing.T) {
path := filepath.Join(t.TempDir(), "nested", "learning", "skills.json")
store, errorValue := Open(path, 20)
if errorValue != nil {
t.Fatal(errorValue)
}
if errorValue := store.UpdateSettings(Settings{Enabled: true, ActiveLimit: 7}); errorValue != nil {
t.Fatal(errorValue)
}
settingsPath := path + ".settings"
fileInformation, errorValue := os.Stat(settingsPath)
if errorValue != nil {
t.Fatal(errorValue)
}
if fileInformation.Mode().Perm() != 0o600 {
t.Fatalf("settings permissions = %o, want 600", fileInformation.Mode().Perm())
}
restarted, errorValue := Open(path, 20)
if errorValue != nil {
t.Fatal(errorValue)
}
if settings := restarted.Settings(); !settings.Enabled || settings.ActiveLimit != 7 {
t.Fatalf("settings did not persist after creating the nested directory: %+v", settings)
}
}
func TestStoreRejectsOversizedLearnedInstruction(t *testing.T) {
store, errorValue := Open(filepath.Join(t.TempDir(), "skills.json"), 20)
if errorValue != nil {
t.Fatal(errorValue)
}
if _, errorValue := store.Put(Skill{ID: "large", Audience: "company", Instruction: string(make([]byte, maximumSkillInstructionBytes+1))}); errorValue == nil {
t.Fatal("expected instruction size rejection")
}
}
func TestStoreLimitsCandidates(t *testing.T) {
store, errorValue := Open(filepath.Join(t.TempDir(), "skills.json"), 20)
if errorValue != nil {
t.Fatal(errorValue)
}
for index := 0; index < maximumCandidateSkills; index++ {
if _, errorValue := store.Put(Skill{ID: "candidate-" + string(rune('a'+index)), Audience: "company", Instruction: "procedure"}); errorValue != nil {
t.Fatal(errorValue)
}
}
if _, errorValue := store.Put(Skill{ID: "candidate-over", Audience: "company", Instruction: "procedure"}); errorValue == nil {
t.Fatal("expected candidate cap")
}
}
func TestStoreReplacementKeepsCapacityAndAudience(t *testing.T) {
for _, scenario := range []struct {
name string
audience string
version int
isAllowed bool
}{
{name: "same audience at capacity", audience: "person:sample", version: 1, isAllowed: true},
{name: "other audience", audience: "person:another", version: 1},
{name: "missing revision", audience: "person:sample"},
{name: "stale revision", audience: "person:sample", version: 2},
} {
t.Run(scenario.name, func(t *testing.T) {
store, errorValue := Open(filepath.Join(t.TempDir(), "skills.json"), 1)
if errorValue != nil {
t.Fatal(errorValue)
}
_, errorValue = store.Put(Skill{ID: "previous", Audience: "person:sample", Instruction: "Previous procedure", Status: "active"})
if errorValue != nil {
t.Fatal(errorValue)
}
_, errorValue = store.ApplyDecision(StoreDecision{Action: "replace", ReplaceID: "previous", ExpectedVersion: scenario.version, Skill: Skill{ID: "replacement", Audience: scenario.audience, Instruction: "Updated procedure", Status: "active"}})
if (errorValue == nil) != scenario.isAllowed {
t.Fatalf("replacement allowed=%v: %v", scenario.isAllowed, errorValue)
}
if store.ActiveCount() != 1 {
t.Fatal("replacement changed active capacity")
}
previous, errorValue := store.Get("previous", "person:sample", false)
if errorValue != nil {
t.Fatal(errorValue)
}
if (previous[0].Status == "retired") != scenario.isAllowed {
t.Fatal("unexpected retirement")
}
})
}
}
func TestStoreRetireDecisionChecksAudienceAndVersionBeforeMutation(t *testing.T) {
store, errorValue := Open(filepath.Join(t.TempDir(), "skills.json"), 20)
if errorValue != nil {
t.Fatal(errorValue)
}
if _, errorValue := store.Put(Skill{ID: "private", Audience: "person:sample", Instruction: "procedure", Status: "active"}); errorValue != nil {
t.Fatal(errorValue)
}
for _, decision := range []StoreDecision{
{Action: "retire", ID: "private", ExpectedVersion: 1, Skill: Skill{Audience: "person:other"}},
{Action: "retire", ID: "private", ExpectedVersion: 2, Skill: Skill{Audience: "person:sample"}},
} {
if _, errorValue := store.ApplyDecision(decision); errorValue == nil {
t.Fatal("unauthorized or stale retirement succeeded")
}
}
items, errorValue := store.Get("private", "person:sample", false)
if errorValue != nil || len(items) != 1 || items[0].Status != "active" {
t.Fatalf("failed retirement changed the skill: %+v %v", items, errorValue)
}
retired, errorValue := store.ApplyDecision(StoreDecision{Action: "retire", ID: "private", ExpectedVersion: 1, Skill: Skill{Audience: "person:sample"}})
if errorValue != nil || retired.Status != "retired" {
t.Fatalf("authorized retirement failed: %+v %v", retired, errorValue)
}
}