Ctrl K
ComunidadGuías…

internal/sessionquery/sessionquery_test.go

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

Archivo original ↗Preguntar a KiBot
package sessionquery

import (
	"strings"
	"testing"
	"time"

	"github.com/yeomyeonggeori/blueclaw/internal/task"
)

type ledgerStub struct {
	taskRunsByPersonID map[string][]task.TaskRun
	eventsByTaskRunID  map[string][]task.TaskEvent
}

func (stub ledgerStub) ListTaskRunByPersonID(personID string) []task.TaskRun {
	return stub.taskRunsByPersonID[personID]
}

func (stub ledgerStub) ListTaskEvent(taskRunID string) []task.TaskEvent {
	return stub.eventsByTaskRunID[taskRunID]
}

func twoPeopleLedger() ledgerStub {
	return ledgerStub{
		taskRunsByPersonID: map[string][]task.TaskRun{
			"person-1": {
				{TaskRunID: "run-1", OriginConversationID: "conversation-1", Status: task.TaskStatusCompleted, Prompt: "회의록 보내줘", CreatedAt: time.Unix(1, 0)},
				{TaskRunID: "run-2", OriginConversationID: "conversation-2", Status: task.TaskStatusFailed, Prompt: "캘린더에 등록해줘", FailureReason: "calendar refused the entry", CreatedAt: time.Unix(2, 0)},
			},
			"person-2": {
				{TaskRunID: "run-3", OriginConversationID: "conversation-3", Status: task.TaskStatusCompleted, Prompt: "회의록 보내줘", CreatedAt: time.Unix(3, 0)},
			},
		},
		eventsByTaskRunID: map[string][]task.TaskEvent{
			"run-1": {{Name: "tool.message_send.result", Body: `{"output":{"content":"sent to 박예시"}}`}},
		},
	}
}

func TestASearchReadsOnlyTheLedgerOfThePersonItNames(t *testing.T) {
	service := New(twoPeopleLedger())

	result, errorValue := service.Search(Request{RequesterPersonID: "person-1", Text: "회의록"})

	if errorValue != nil {
		t.Fatalf("searching failed: %v", errorValue)
	}
	if len(result.Matches) != 1 || result.Matches[0].TaskRunID != "run-1" {
		t.Fatalf("the ledger holds message text and tool inputs from everyone on this device, so a search reaches one person's runs and no further: %+v", result.Matches)
	}
}

func TestASearchThatNamesNobodyIsRefused(t *testing.T) {
	service := New(twoPeopleLedger())

	if _, errorValue := service.Search(Request{Text: "회의록"}); errorValue == nil {
		t.Fatal("a search with no person is a search of everyone's ledger, and there is no safe default for that")
	}
}

func TestASearchReachesIntoEventBodiesAndSaysWhereItMatched(t *testing.T) {
	service := New(twoPeopleLedger())

	result, errorValue := service.Search(Request{RequesterPersonID: "person-1", Text: "박예시"})

	if errorValue != nil {
		t.Fatalf("searching failed: %v", errorValue)
	}
	if len(result.Matches) != 1 || strings.Join(result.Matches[0].MatchedIn, ",") != "tool.message_send.result" {
		t.Fatalf("what a run actually did lives in its events, and a hit has to say which one it was: %+v", result.Matches)
	}

	failed, _ := service.Search(Request{RequesterPersonID: "person-1", Text: "calendar refused"})
	if len(failed.Matches) != 1 || strings.Join(failed.Matches[0].MatchedIn, ",") != "failureReason" {
		t.Fatalf("expected the failure reason to be searchable: %+v", failed.Matches)
	}
}

func TestEveryReadIsBoundedAndSaysWhatItLeftOut(t *testing.T) {
	taskRuns := []task.TaskRun{}
	for index := 0; index < 40; index++ {
		taskRuns = append(taskRuns, task.TaskRun{TaskRunID: "run", Prompt: "회의록 보내줘"})
	}
	service := New(ledgerStub{taskRunsByPersonID: map[string][]task.TaskRun{"person-1": taskRuns}})

	result, errorValue := service.Search(Request{RequesterPersonID: "person-1", Text: "회의록", Limit: 5})

	if errorValue != nil {
		t.Fatalf("searching failed: %v", errorValue)
	}
	if len(result.Matches) != 5 || result.TotalMatched != 40 || !result.IsTruncated {
		t.Fatalf("an unbounded ledger read into a turn's context is a way to lose the turn, and a silent truncation reads as covering everything: %+v", result)
	}
	if boundedLimit(5000) != maximumLimit || boundedLimit(0) != defaultLimit {
		t.Fatal("the caller does not get to ask for the whole ledger, and asking for nothing does not mean all of it")
	}
}

func TestAConversationIsTheLineageThisProductHas(t *testing.T) {
	service := New(twoPeopleLedger())

	result, errorValue := service.Search(Request{RequesterPersonID: "person-1", ConversationID: "conversation-2"})

	if errorValue != nil {
		t.Fatalf("searching failed: %v", errorValue)
	}
	if len(result.Matches) != 1 || result.Matches[0].TaskRunID != "run-2" {
		t.Fatalf("a follow-up is a new run in the same conversation, so that is how a task's history is walked: %+v", result.Matches)
	}
}

type countingLedger struct {
	taskRuns  []task.TaskRun
	readCount int
}

func (ledger *countingLedger) ListTaskRunByPersonID(string) []task.TaskRun { return ledger.taskRuns }

func (ledger *countingLedger) ListTaskEvent(string) []task.TaskEvent {
	ledger.readCount++
	return nil
}

func TestOneSearchCannotIssueOneQueryPerTaskRunEverRecorded(t *testing.T) {
	taskRuns := []task.TaskRun{}
	for index := 0; index < 2000; index++ {
		taskRuns = append(taskRuns, task.TaskRun{TaskRunID: "run", Prompt: "무언가"})
	}
	ledger := &countingLedger{taskRuns: taskRuns}

	result, errorValue := New(ledger).Search(Request{RequesterPersonID: "person-1", Text: "회의록"})

	if errorValue != nil {
		t.Fatalf("searching failed: %v", errorValue)
	}
	if ledger.readCount > maximumScannedTaskRuns {
		t.Fatalf("reading a run's events is a query per run against Postgres, so an unbounded scan is an unbounded query fan-out: %d reads", ledger.readCount)
	}
	if !result.DidStopScanning {
		t.Fatal("a scan that stopped early has to say so, or a caller reads no matches as nothing happened")
	}
	if result.Scanned != maximumScannedTaskRuns {
		t.Fatalf("expected the scan to stop at its ceiling, got %d", result.Scanned)
	}
}

func TestASearchThatReachedTheEndSaysItDidNotStop(t *testing.T) {
	result, errorValue := New(twoPeopleLedger()).Search(Request{RequesterPersonID: "person-1", Text: "회의록"})

	if errorValue != nil {
		t.Fatalf("searching failed: %v", errorValue)
	}
	if result.DidStopScanning {
		t.Fatal("a ledger smaller than the ceiling was read whole, and saying otherwise sends the reader looking for pages that do not exist")
	}
}