Files
2dph/internal/chats/chats_test.go
T
eSliderandGitHub 5d4b3427a4
Tests / Test (push) Failing after 5s
Tests / Release (semver) (push) Skipped
refactor: chats method shebangs; drop chats index (D14). (#11)
Parsers and commands live in internal/chats. bin/chats/{sync,import,facts,apply}.go
are tagged shebang mains. Brain ingest is not a chats command.
2026-08-13 17:31:03 +01:00

203 lines
5.8 KiB
Go

// System tests for bin/chats.
//
// These are integration tests using real data and real Telegram API (when
// credentials are available). They follow the TDD workflow pattern:
// sync → import → facts → verify.
package chats
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
// TestChatsImport validates JSONL → MD conversion with a synthetic fixture.
func TestChatsImport(t *testing.T) {
dir := t.TempDir()
root := filepath.Join(dir, "var", "chats")
chatDir := filepath.Join(root, "telegram", "test_user_123")
if err := os.MkdirAll(chatDir, 0755); err != nil {
t.Fatal(err)
}
jsonlPath := filepath.Join(chatDir, "messages.jsonl")
f, err := os.Create(jsonlPath)
if err != nil {
t.Fatal(err)
}
defer f.Close()
enc := json.NewEncoder(f)
messages := []Message{
{ID: "tg_1", Timestamp: "2026-01-15T10:30:00Z", From: "Alice", Text: "Hello!", Platform: "telegram"},
{ID: "tg_2", Timestamp: "2026-01-15T10:31:00Z", From: "Bob", Text: "Hi Alice, my phone is +34 612 345 678", Platform: "telegram"},
{ID: "tg_3", Timestamp: "2026-01-15T10:32:00Z", From: "Alice", Text: "Check my LinkedIn: https://linkedin.com/in/alice-test", Platform: "telegram"},
{ID: "tg_4", Timestamp: "2026-01-15T10:33:00Z", From: "Bob", Text: "My email is bob@example.com, working on Project X", Platform: "telegram"},
}
for _, m := range messages {
if err := enc.Encode(m); err != nil {
t.Fatal(err)
}
}
f.Close()
cwd, _ := os.Getwd()
os.Chdir(dir)
t.Cleanup(func() { os.Chdir(cwd) })
t.Setenv("KB_ROOT", dir)
exitCode := RunImport([]string{})
if exitCode != 0 {
t.Fatalf("import exit code %d", exitCode)
}
mdGlob := filepath.Join(root, "md", "telegram", "*", "messages.md")
matches, err := filepath.Glob(mdGlob)
if err != nil {
t.Fatal(err)
}
if len(matches) == 0 {
t.Fatal("no markdown files created by import")
}
mdData, err := os.ReadFile(matches[0])
if err != nil {
t.Fatal(err)
}
content := string(mdData)
if !strings.Contains(content, "Alice") {
t.Error("markdown missing sender name 'Alice'")
}
if !strings.Contains(content, "2026-01-15") {
t.Error("markdown missing date")
}
if !strings.Contains(content, "---") {
t.Error("markdown missing YAML frontmatter")
}
}
// TestChatsFacts validates fact extraction from JSONL fixture.
func TestChatsFacts(t *testing.T) {
dir := t.TempDir()
root := filepath.Join(dir, "var", "chats")
chatDir := filepath.Join(root, "telegram", "test_user_facts")
if err := os.MkdirAll(chatDir, 0755); err != nil {
t.Fatal(err)
}
jsonlPath := filepath.Join(chatDir, "messages.jsonl")
f, err := os.Create(jsonlPath)
if err != nil {
t.Fatal(err)
}
defer f.Close()
enc := json.NewEncoder(f)
messages := []Message{
{ID: "tg_10", Timestamp: "2026-06-01T12:00:00Z", From: "Charlie", Text: "Call me at +1 555 123 4567", Platform: "telegram"},
{ID: "tg_11", Timestamp: "2026-06-01T12:01:00Z", From: "Charlie", Text: "My LinkedIn is linkedin.com/in/charlie-dev", Platform: "telegram"},
{ID: "tg_12", Timestamp: "2026-06-01T12:02:00Z", From: "Charlie", Text: "Email: charlie@dev.com", Platform: "telegram"},
{ID: "tg_13", Timestamp: "2026-06-01T12:03:00Z", From: "Charlie", Text: "I work at Acme Corp on Project Mercury", Platform: "telegram"},
}
for _, m := range messages {
if err := enc.Encode(m); err != nil {
t.Fatal(err)
}
}
f.Close()
facts, _ := extractFacts(jsonlPath, "test_user_facts")
if len(facts) == 0 {
t.Fatal("expected facts, got none")
}
types := make(map[string]int)
for _, f := range facts {
types[f.FactType]++
}
if types["phone"] < 1 {
t.Errorf("expected >=1 phone fact, got %d", types["phone"])
}
if types["email"] < 1 {
t.Errorf("expected >=1 email fact, got %d", types["email"])
}
if types["linkedin"] < 1 {
t.Errorf("expected >=1 linkedin fact, got %d", types["linkedin"])
}
if types["skill"] < 1 {
t.Errorf("expected >=1 skill fact, got %d", types["skill"])
}
}
// TestChatsImportEmptyDir tests that import handles no JSONL gracefully.
func TestChatsImportEmpty(t *testing.T) {
dir := t.TempDir()
cwd, _ := os.Getwd()
os.Chdir(dir)
t.Cleanup(func() { os.Chdir(cwd) })
t.Setenv("KB_ROOT", dir)
exitCode := RunImport([]string{})
if exitCode == 0 {
t.Fatal("expected non-zero exit for empty data dir")
}
}
// TestChatsRoundTrip creates a synthetic JSONL, imports it, then verifies
// the markdown structure is parseable and contains YAML frontmatter.
func TestChatsRoundTrip(t *testing.T) {
dir := t.TempDir()
root := filepath.Join(dir, "var", "chats")
chatDir := filepath.Join(root, "telegram", "rt_user")
if err := os.MkdirAll(chatDir, 0755); err != nil {
t.Fatal(err)
}
jsonlPath := filepath.Join(chatDir, "messages.jsonl")
f, err := os.Create(jsonlPath)
if err != nil {
t.Fatal(err)
}
enc := json.NewEncoder(f)
enc.Encode(Message{ID: "tg_100", Timestamp: "2026-07-01T08:00:00Z", From: "Diana", Text: "Hey", Platform: "telegram"})
enc.Encode(Message{ID: "tg_101", Timestamp: "2026-07-01T08:01:00Z", From: "Diana", Text: "How are you?", Platform: "telegram"})
f.Close()
cwd, _ := os.Getwd()
os.Chdir(dir)
t.Cleanup(func() { os.Chdir(cwd) })
t.Setenv("KB_ROOT", dir)
if code := RunImport([]string{}); code != 0 {
t.Fatalf("import exit %d", code)
}
mdGlob := filepath.Join(root, "md", "telegram", "*", "messages.md")
matches, _ := filepath.Glob(mdGlob)
if len(matches) == 0 {
t.Fatal("no markdown produced")
}
data, err := os.ReadFile(matches[0])
if err != nil {
t.Fatal(err)
}
content := string(data)
if !strings.HasPrefix(content, "---") {
t.Error("markdown should start with YAML frontmatter delimiter")
}
if !strings.Contains(content, "platform: telegram") {
t.Error("markdown should contain platform field")
}
if !strings.Contains(content, "message_count: 2") {
t.Error("markdown should contain correct message count")
}
if !strings.Contains(content, "Diana") {
t.Error("markdown should contain participants")
}
}