summaryrefslogtreecommitdiff
path: root/bot_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'bot_test.go')
-rw-r--r--bot_test.go399
1 files changed, 391 insertions, 8 deletions
diff --git a/bot_test.go b/bot_test.go
index d2956a9..01c3e2c 100644
--- a/bot_test.go
+++ b/bot_test.go
@@ -117,35 +117,35 @@ func TestConsolidateConsecutiveAssistantMessages(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- result := consolidateConsecutiveAssistantMessages(tt.input)
-
+ result := consolidateAssistantMessages(tt.input)
+
if len(result) != len(tt.expected) {
t.Errorf("Expected %d messages, got %d", len(tt.expected), len(result))
t.Logf("Result: %+v", result)
t.Logf("Expected: %+v", tt.expected)
return
}
-
+
for i, expectedMsg := range tt.expected {
if i >= len(result) {
t.Errorf("Result has fewer messages than expected at index %d", i)
continue
}
-
+
actualMsg := result[i]
if actualMsg.Role != expectedMsg.Role {
t.Errorf("Message %d: expected role '%s', got '%s'", i, expectedMsg.Role, actualMsg.Role)
}
-
+
if actualMsg.Content != expectedMsg.Content {
t.Errorf("Message %d: expected content '%s', got '%s'", i, expectedMsg.Content, actualMsg.Content)
}
-
+
if actualMsg.ToolCallID != expectedMsg.ToolCallID {
t.Errorf("Message %d: expected ToolCallID '%s', got '%s'", i, expectedMsg.ToolCallID, actualMsg.ToolCallID)
}
}
-
+
// Additional check: ensure no messages were lost
if !reflect.DeepEqual(result, tt.expected) {
t.Errorf("Result does not match expected:\nResult: %+v\nExpected: %+v", result, tt.expected)
@@ -286,4 +286,387 @@ func TestConvertJSONToMapStringString(t *testing.T) {
}
})
}
-} \ No newline at end of file
+}
+
+func TestParseKnownToTag(t *testing.T) {
+ tests := []struct {
+ name string
+ content string
+ enabled bool
+ tag string
+ wantCleaned string
+ wantKnownTo []string
+ }{
+ {
+ name: "feature disabled returns original",
+ content: "Hello @Alice@",
+ enabled: false,
+ tag: "@",
+ wantCleaned: "Hello @Alice@",
+ wantKnownTo: nil,
+ },
+ {
+ name: "no tag returns original",
+ content: "Hello Alice",
+ enabled: true,
+ tag: "@",
+ wantCleaned: "Hello Alice",
+ wantKnownTo: nil,
+ },
+ {
+ name: "single tag with one char",
+ content: "Hello @Alice@",
+ enabled: true,
+ tag: "@",
+ wantCleaned: "Hello",
+ wantKnownTo: []string{"Alice"},
+ },
+ {
+ name: "single tag with two chars",
+ content: "Secret @Alice,Bob@ message",
+ enabled: true,
+ tag: "@",
+ wantCleaned: "Secret message",
+ wantKnownTo: []string{"Alice", "Bob"},
+ },
+ {
+ name: "tag at beginning",
+ content: "@Alice@ Hello",
+ enabled: true,
+ tag: "@",
+ wantCleaned: "Hello",
+ wantKnownTo: []string{"Alice"},
+ },
+ {
+ name: "tag at end",
+ content: "Hello @Alice@",
+ enabled: true,
+ tag: "@",
+ wantCleaned: "Hello",
+ wantKnownTo: []string{"Alice"},
+ },
+ {
+ name: "multiple tags",
+ content: "First @Alice@ then @Bob@",
+ enabled: true,
+ tag: "@",
+ wantCleaned: "First then",
+ wantKnownTo: []string{"Alice", "Bob"},
+ },
+ {
+ name: "custom tag",
+ content: "Secret @Alice,Bob@ message",
+ enabled: true,
+ tag: "@",
+ wantCleaned: "Secret message",
+ wantKnownTo: []string{"Alice", "Bob"},
+ },
+ {
+ name: "empty list",
+ content: "Secret @@@",
+ enabled: true,
+ tag: "@",
+ wantCleaned: "Secret",
+ wantKnownTo: nil,
+ },
+ {
+ name: "whitespace around commas",
+ content: "@ Alice , Bob , Carl @",
+ enabled: true,
+ tag: "@",
+ wantCleaned: "",
+ wantKnownTo: []string{"Alice", "Bob", "Carl"},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Set up config
+ testCfg := &config.Config{
+ CharSpecificContextEnabled: tt.enabled,
+ CharSpecificContextTag: tt.tag,
+ }
+ cfg = testCfg
+ knownTo := parseKnownToTag(tt.content)
+ if len(knownTo) != len(tt.wantKnownTo) {
+ t.Errorf("parseKnownToTag() knownTo length = %v, want %v", len(knownTo), len(tt.wantKnownTo))
+ t.Logf("got: %v", knownTo)
+ t.Logf("want: %v", tt.wantKnownTo)
+ } else {
+ for i, got := range knownTo {
+ if got != tt.wantKnownTo[i] {
+ t.Errorf("parseKnownToTag() knownTo[%d] = %q, want %q", i, got, tt.wantKnownTo[i])
+ }
+ }
+ }
+ })
+ }
+}
+
+func TestProcessMessageTag(t *testing.T) {
+ tests := []struct {
+ name string
+ msg models.RoleMsg
+ enabled bool
+ tag string
+ wantMsg models.RoleMsg
+ }{
+ {
+ name: "feature disabled returns unchanged",
+ msg: models.RoleMsg{
+ Role: "Alice",
+ Content: "Secret @Bob@",
+ },
+ enabled: false,
+ tag: "@",
+ wantMsg: models.RoleMsg{
+ Role: "Alice",
+ Content: "Secret @Bob@",
+ KnownTo: nil,
+ },
+ },
+ {
+ name: "no tag, no knownTo",
+ msg: models.RoleMsg{
+ Role: "Alice",
+ Content: "Hello everyone",
+ },
+ enabled: true,
+ tag: "@",
+ wantMsg: models.RoleMsg{
+ Role: "Alice",
+ Content: "Hello everyone",
+ KnownTo: nil,
+ },
+ },
+ {
+ name: "tag with Bob, adds Alice automatically",
+ msg: models.RoleMsg{
+ Role: "Alice",
+ Content: "Secret @Bob@",
+ },
+ enabled: true,
+ tag: "@",
+ wantMsg: models.RoleMsg{
+ Role: "Alice",
+ Content: "Secret",
+ KnownTo: []string{"Bob", "Alice"},
+ },
+ },
+ {
+ name: "tag already includes sender",
+ msg: models.RoleMsg{
+ Role: "Alice",
+ Content: "@Alice,Bob@",
+ },
+ enabled: true,
+ tag: "@",
+ wantMsg: models.RoleMsg{
+ Role: "Alice",
+ Content: "",
+ KnownTo: []string{"Alice", "Bob"},
+ },
+ },
+ {
+ name: "knownTo already set (from DB), tag still processed",
+ msg: models.RoleMsg{
+ Role: "Alice",
+ Content: "Secret @Bob@",
+ KnownTo: []string{"Alice"}, // from previous processing
+ },
+ enabled: true,
+ tag: "@",
+ wantMsg: models.RoleMsg{
+ Role: "Alice",
+ Content: "Secret",
+ KnownTo: []string{"Bob", "Alice"},
+ },
+ },
+ {
+ name: "example from real use",
+ msg: models.RoleMsg{
+ Role: "Alice",
+ Content: "I'll start with a simple one! The word is 'banana'. (ooc: @Bob@)",
+ KnownTo: []string{"Alice"}, // from previous processing
+ },
+ enabled: true,
+ tag: "@",
+ wantMsg: models.RoleMsg{
+ Role: "Alice",
+ Content: "I'll start with a simple one! The word is 'banana'. (ooc: @Bob@)",
+ KnownTo: []string{"Bob", "Alice"},
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ testCfg := &config.Config{
+ CharSpecificContextEnabled: tt.enabled,
+ CharSpecificContextTag: tt.tag,
+ }
+ cfg = testCfg
+ got := processMessageTag(&tt.msg)
+ if len(got.KnownTo) != len(tt.wantMsg.KnownTo) {
+ t.Errorf("processMessageTag() KnownTo length = %v, want %v", len(got.KnownTo), len(tt.wantMsg.KnownTo))
+ t.Logf("got: %v", got.KnownTo)
+ t.Logf("want: %v", tt.wantMsg.KnownTo)
+ } else {
+ // order may differ; check membership
+ for _, want := range tt.wantMsg.KnownTo {
+ found := false
+ for _, gotVal := range got.KnownTo {
+ if gotVal == want {
+ found = true
+ break
+ }
+ }
+ if !found {
+ t.Errorf("processMessageTag() missing KnownTo entry %q, got %v", want, got.KnownTo)
+ }
+ }
+ }
+ })
+ }
+}
+
+func TestFilterMessagesForCharacter(t *testing.T) {
+ messages := []models.RoleMsg{
+ {Role: "system", Content: "System message", KnownTo: nil}, // visible to all
+ {Role: "Alice", Content: "Hello everyone", KnownTo: nil}, // visible to all
+ {Role: "Alice", Content: "Secret for Bob", KnownTo: []string{"Alice", "Bob"}},
+ {Role: "Bob", Content: "Reply to Alice", KnownTo: []string{"Alice", "Bob"}},
+ {Role: "Alice", Content: "Private to Carl", KnownTo: []string{"Alice", "Carl"}},
+ {Role: "Carl", Content: "Hi all", KnownTo: nil}, // visible to all
+ }
+
+ tests := []struct {
+ name string
+ enabled bool
+ character string
+ wantIndices []int // indices from original messages that should be included
+ }{
+ {
+ name: "feature disabled returns all",
+ enabled: false,
+ character: "Alice",
+ wantIndices: []int{0, 1, 2, 3, 4, 5},
+ },
+ {
+ name: "character empty returns all",
+ enabled: true,
+ character: "",
+ wantIndices: []int{0, 1, 2, 3, 4, 5},
+ },
+ {
+ name: "Alice sees all including Carl-private",
+ enabled: true,
+ character: "Alice",
+ wantIndices: []int{0, 1, 2, 3, 4, 5},
+ },
+ {
+ name: "Bob sees Alice-Bob secrets and all public",
+ enabled: true,
+ character: "Bob",
+ wantIndices: []int{0, 1, 2, 3, 5},
+ },
+ {
+ name: "Carl sees Alice-Carl secret and public",
+ enabled: true,
+ character: "Carl",
+ wantIndices: []int{0, 1, 4, 5},
+ },
+ {
+ name: "David sees only public messages",
+ enabled: true,
+ character: "David",
+ wantIndices: []int{0, 1, 5},
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ testCfg := &config.Config{
+ CharSpecificContextEnabled: tt.enabled,
+ CharSpecificContextTag: "@",
+ }
+ cfg = testCfg
+
+ got := filterMessagesForCharacter(messages, tt.character)
+
+ if len(got) != len(tt.wantIndices) {
+ t.Errorf("filterMessagesForCharacter() returned %d messages, want %d", len(got), len(tt.wantIndices))
+ t.Logf("got: %v", got)
+ return
+ }
+
+ for i, idx := range tt.wantIndices {
+ if got[i].Content != messages[idx].Content {
+ t.Errorf("filterMessagesForCharacter() message %d content = %q, want %q", i, got[i].Content, messages[idx].Content)
+ }
+ }
+ })
+ }
+}
+
+func TestRoleMsgCopyPreservesKnownTo(t *testing.T) {
+ // Test that the Copy() method preserves the KnownTo field
+ originalMsg := models.RoleMsg{
+ Role: "Alice",
+ Content: "Test message",
+ KnownTo: []string{"Bob", "Charlie"},
+ }
+
+ copiedMsg := originalMsg.Copy()
+
+ if copiedMsg.Role != originalMsg.Role {
+ t.Errorf("Copy() failed to preserve Role: got %q, want %q", copiedMsg.Role, originalMsg.Role)
+ }
+ if copiedMsg.Content != originalMsg.Content {
+ t.Errorf("Copy() failed to preserve Content: got %q, want %q", copiedMsg.Content, originalMsg.Content)
+ }
+ if !reflect.DeepEqual(copiedMsg.KnownTo, originalMsg.KnownTo) {
+ t.Errorf("Copy() failed to preserve KnownTo: got %v, want %v", copiedMsg.KnownTo, originalMsg.KnownTo)
+ }
+ if copiedMsg.ToolCallID != originalMsg.ToolCallID {
+ t.Errorf("Copy() failed to preserve ToolCallID: got %q, want %q", copiedMsg.ToolCallID, originalMsg.ToolCallID)
+ }
+ if copiedMsg.IsContentParts() != originalMsg.IsContentParts() {
+ t.Errorf("Copy() failed to preserve hasContentParts flag")
+ }
+}
+
+func TestKnownToFieldPreservationScenario(t *testing.T) {
+ // Test the specific scenario from the log where KnownTo field was getting lost
+ originalMsg := models.RoleMsg{
+ Role: "Alice",
+ Content: `Alice: "Okay, Bob. The word is... **'Ephemeral'**. (ooc: @Bob@)"`,
+ KnownTo: []string{"Bob"}, // This was detected in the log
+ }
+
+ t.Logf("Original message - Role: %s, Content: %s, KnownTo: %v",
+ originalMsg.Role, originalMsg.Content, originalMsg.KnownTo)
+
+ // Simulate what happens when the message gets copied during processing
+ copiedMsg := originalMsg.Copy()
+
+ t.Logf("Copied message - Role: %s, Content: %s, KnownTo: %v",
+ copiedMsg.Role, copiedMsg.Content, copiedMsg.KnownTo)
+
+ // Check if KnownTo field survived the copy
+ if len(copiedMsg.KnownTo) == 0 {
+ t.Error("ERROR: KnownTo field was lost during copy!")
+ } else {
+ t.Log("SUCCESS: KnownTo field was preserved during copy!")
+ }
+
+ // Verify the content is the same
+ if copiedMsg.Content != originalMsg.Content {
+ t.Errorf("Content was changed during copy: got %s, want %s", copiedMsg.Content, originalMsg.Content)
+ }
+
+ // Verify the KnownTo slice is properly copied
+ if !reflect.DeepEqual(copiedMsg.KnownTo, originalMsg.KnownTo) {
+ t.Errorf("KnownTo was not properly copied: got %v, want %v", copiedMsg.KnownTo, originalMsg.KnownTo)
+ }
+}