| | @@ -0,0 +1,159 @@ |
| 1 | +package scribe_test
|
| 2 | +
|
| 3 | +import (
|
| 4 | + "encoding/json"
|
| 5 | + "testing"
|
| 6 | + "time"
|
| 7 | +
|
| 8 | + "github.com/conflicthq/scuttlebot/internal/bots/scribe"
|
| 9 | + "github.com/conflicthq/scuttlebot/pkg/protocol"
|
| 10 | +)
|
| 11 | +
|
| 12 | +func validEnvelopeJSON(t *testing.T, msgType, from string) string {
|
| 13 | + t.Helper()
|
| 14 | + env, err := protocol.New(msgType, from, map[string]string{"key": "val"})
|
| 15 | + if err != nil {
|
| 16 | + t.Fatalf("protocol.New: %v", err)
|
| 17 | + }
|
| 18 | + b, err := protocol.Marshal(env)
|
| 19 | + if err != nil {
|
| 20 | + t.Fatalf("protocol.Marshal: %v", err)
|
| 21 | + }
|
| 22 | + return string(b)
|
| 23 | +}
|
| 24 | +
|
| 25 | +func TestStoreAppendAndQuery(t *testing.T) {
|
| 26 | + s := &scribe.MemoryStore{}
|
| 27 | +
|
| 28 | + entries := []scribe.Entry{
|
| 29 | + {At: time.Now(), Channel: "#fleet", Nick: "claude-01", Kind: scribe.EntryKindRaw, Raw: "hello"},
|
| 30 | + {At: time.Now(), Channel: "#fleet", Nick: "gemini-01", Kind: scribe.EntryKindRaw, Raw: "world"},
|
| 31 | + {At: time.Now(), Channel: "#project.test", Nick: "claude-01", Kind: scribe.EntryKindRaw, Raw: "other channel"},
|
| 32 | + }
|
| 33 | + for _, e := range entries {
|
| 34 | + if err := s.Append(e); err != nil {
|
| 35 | + t.Fatalf("Append: %v", err)
|
| 36 | + }
|
| 37 | + }
|
| 38 | +
|
| 39 | + fleet, err := s.Query("#fleet", 0)
|
| 40 | + if err != nil {
|
| 41 | + t.Fatalf("Query: %v", err)
|
| 42 | + }
|
| 43 | + if len(fleet) != 2 {
|
| 44 | + t.Errorf("Query #fleet: got %d entries, want 2", len(fleet))
|
| 45 | + }
|
| 46 | +
|
| 47 | + all, err := s.Query("", 0)
|
| 48 | + if err != nil {
|
| 49 | + t.Fatalf("Query all: %v", err)
|
| 50 | + }
|
| 51 | + if len(all) != 3 {
|
| 52 | + t.Errorf("Query all: got %d entries, want 3", len(all))
|
| 53 | + }
|
| 54 | +}
|
| 55 | +
|
| 56 | +func TestStoreQueryLimit(t *testing.T) {
|
| 57 | + s := &scribe.MemoryStore{}
|
| 58 | + for i := 0; i < 10; i++ {
|
| 59 | + _ = s.Append(scribe.Entry{Channel: "#fleet", Nick: "agent", Kind: scribe.EntryKindRaw})
|
| 60 | + }
|
| 61 | +
|
| 62 | + got, err := s.Query("#fleet", 3)
|
| 63 | + if err != nil {
|
| 64 | + t.Fatalf("Query: %v", err)
|
| 65 | + }
|
| 66 | + if len(got) != 3 {
|
| 67 | + t.Errorf("Query with limit=3: got %d entries", len(got))
|
| 68 | + }
|
| 69 | +}
|
| 70 | +
|
| 71 | +func TestEntryKindFromEnvelope(t *testing.T) {
|
| 72 | + // Test that a valid envelope JSON is detected as EntryKindEnvelope.
|
| 73 | + raw := validEnvelopeJSON(t, protocol.TypeTaskCreate, "claude-01")
|
| 74 | +
|
| 75 | + env, err := protocol.Unmarshal([]byte(raw))
|
| 76 | + if err != nil {
|
| 77 | + t.Fatalf("Unmarshal: %v", err)
|
| 78 | + }
|
| 79 | +
|
| 80 | + entry := scribe.Entry{
|
| 81 | + At: time.Now(),
|
| 82 | + Channel: "#fleet",
|
| 83 | + Nick: "claude-01",
|
| 84 | + Kind: scribe.EntryKindEnvelope,
|
| 85 | + MessageType: env.Type,
|
| 86 | + MessageID: env.ID,
|
| 87 | + Raw: raw,
|
| 88 | + }
|
| 89 | +
|
| 90 | + if entry.MessageType != protocol.TypeTaskCreate {
|
| 91 | + t.Errorf("MessageType: got %q, want %q", entry.MessageType, protocol.TypeTaskCreate)
|
| 92 | + }
|
| 93 | + if entry.MessageID == "" {
|
| 94 | + t.Error("MessageID is empty")
|
| 95 | + }
|
| 96 | + if entry.Kind != scribe.EntryKindEnvelope {
|
| 97 | + t.Errorf("Kind: got %q, want %q", entry.Kind, scribe.EntryKindEnvelope)
|
| 98 | + }
|
| 99 | +}
|
| 100 | +
|
| 101 | +func TestEntryKindRawForMalformed(t *testing.T) {
|
| 102 | + // Non-JSON and invalid envelopes should produce EntryKindRaw entries.
|
| 103 | + cases := []string{
|
| 104 | + "hello from a human",
|
| 105 | + "not json at all",
|
| 106 | + `{"incomplete": true}`, // valid JSON but not a valid envelope
|
| 107 | + }
|
| 108 | +
|
| 109 | + for _, raw := range cases {
|
| 110 | + _, err := protocol.Unmarshal([]byte(raw))
|
| 111 | + if err == nil {
|
| 112 | + // Valid envelope — skip (this case tests malformed only)
|
| 113 | + continue
|
| 114 | + }
|
| 115 | + entry := scribe.Entry{
|
| 116 | + At: time.Now(),
|
| 117 | + Channel: "#fleet",
|
| 118 | + Nick: "agent",
|
| 119 | + Kind: scribe.EntryKindRaw,
|
| 120 | + Raw: raw,
|
| 121 | + }
|
| 122 | + if entry.Kind != scribe.EntryKindRaw {
|
| 123 | + t.Errorf("expected EntryKindRaw for %q", raw)
|
| 124 | + }
|
| 125 | + if entry.MessageType != "" {
|
| 126 | + t.Errorf("MessageType should be empty for raw entry")
|
| 127 | + }
|
| 128 | + }
|
| 129 | +}
|
| 130 | +
|
| 131 | +func TestEntryJSONRoundTrip(t *testing.T) {
|
| 132 | + entry := scribe.Entry{
|
| 133 | + At: time.Now().Truncate(time.Millisecond),
|
| 134 | + Channel: "#project.test",
|
| 135 | + Nick: "claude-01",
|
| 136 | + Kind: scribe.EntryKindEnvelope,
|
| 137 | + MessageType: protocol.TypeAgentHello,
|
| 138 | + MessageID: "01HX123",
|
| 139 | + Raw: `{"v":1}`,
|
| 140 | + }
|
| 141 | +
|
| 142 | + b, err := json.Marshal(entry)
|
| 143 | + if err != nil {
|
| 144 | + t.Fatalf("Marshal: %v", err)
|
| 145 | + }
|
| 146 | +
|
| 147 | + var got scribe.Entry
|
| 148 | + if err := json.Unmarshal(b, &got); err != nil {
|
| 149 | + t.Fatalf("Unmarshal: %v", err)
|
| 150 | + }
|
| 151 | +
|
| 152 | + if got.Channel != entry.Channel {
|
| 153 | + t.Errorf("Channel: got %q, want %q", got.Channel, entry.Channel)
|
| 154 | + }
|
| 155 | + if got.Kind != entry.Kind {
|
| 156 | + t.Errorf("Kind: got %q, want %q", got.Kind, entry.Kind)
|
| 157 | + }
|
| 158 | + if got.MessageType != entry.MessageType {
|
| 159 | + t.Errorf("MessageType: got %q, want %q", got.MessageTyp |