Ctrl K
ComunidadGuías…

internal/learning/store_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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)
	}
}