Files
waste-go/internal/store/store_test.go
Fredrik Johansson cde611b261 Add SQLite message and peer persistence (internal/store)
Each daemon writes to <data-dir>/messages.db on startup. Messages received
or sent are stored immediately; duplicate mids (INSERT OR IGNORE) are safe
to call multiple times. Peer aliases are upserted on peer_connected and
again after hello verification when the real nick is known.

Schema
- messages(mid UNIQUE, room, from_peer, body, sent_at) — mid is the YAW/2
  dedup key added in the proto migration; index on (room, sent_at) for
  efficient per-room queries.
- peers(peer_id PK, alias, last_seen) — cache of every peer ever seen,
  used to resolve hex ids to names when peers are offline.

Wiring
- store.Open called in cmd/daemon/main.go, passed to mesh.New.
- mesh.Mesh holds *store.Store (nil-safe; persistence is optional).
- mesh.SaveMessage called in dispatchPeerMessage (incoming) and ipc
  CmdSendMessage (outgoing) so the local node's own messages are stored.
- mesh.UpdatePeerAlias called after hello verification updates the alias
  with the verified nick rather than the placeholder short-id.

Messages only accumulate from join time forward — no history replay to
late-joining peers; each node's view starts from when it connected.

test-network.sh: added SQLite verification block that queries each node's
DB after the test and prints message + peer counts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 18:04:42 +02:00

104 lines
2.4 KiB
Go

package store
import (
"path/filepath"
"testing"
"time"
"github.com/waste-go/internal/proto"
)
func TestRoundTrip(t *testing.T) {
st, err := Open(filepath.Join(t.TempDir(), "test.db"))
if err != nil {
t.Fatalf("Open: %v", err)
}
defer st.Close()
msg := &proto.ChatMessage{
Mid: "aabbccdd00112233",
From: proto.PeerID("deadbeef"),
Room: "general",
Body: "hello world",
SentAt: time.Now().UTC().Truncate(time.Second),
}
if err := st.SaveMessage(msg); err != nil {
t.Fatalf("SaveMessage: %v", err)
}
// Duplicate mid must be silently ignored.
if err := st.SaveMessage(msg); err != nil {
t.Fatalf("duplicate SaveMessage: %v", err)
}
msgs, err := st.RecentMessages("general", 10)
if err != nil {
t.Fatalf("RecentMessages: %v", err)
}
if len(msgs) != 1 {
t.Fatalf("got %d messages, want 1", len(msgs))
}
got := msgs[0]
if got.Mid != msg.Mid || got.Body != msg.Body || got.Room != msg.Room {
t.Fatalf("message mismatch: %+v", got)
}
}
func TestSavePeerAndKnownPeers(t *testing.T) {
st, err := Open(filepath.Join(t.TempDir(), "test.db"))
if err != nil {
t.Fatalf("Open: %v", err)
}
defer st.Close()
if err := st.SavePeer("aabbccdd", "alice"); err != nil {
t.Fatalf("SavePeer: %v", err)
}
// Upsert with updated alias.
if err := st.SavePeer("aabbccdd", "alice-updated"); err != nil {
t.Fatalf("SavePeer upsert: %v", err)
}
peers, err := st.KnownPeers()
if err != nil {
t.Fatalf("KnownPeers: %v", err)
}
if peers["aabbccdd"] != "alice-updated" {
t.Fatalf("alias = %q, want %q", peers["aabbccdd"], "alice-updated")
}
}
func TestRecentMessagesOrdering(t *testing.T) {
st, err := Open(filepath.Join(t.TempDir(), "test.db"))
if err != nil {
t.Fatalf("Open: %v", err)
}
defer st.Close()
base := time.Now().UTC().Truncate(time.Second)
for i := range 5 {
st.SaveMessage(&proto.ChatMessage{
Mid: string(rune('a'+i)) + "000000000000000",
From: "deadbeef",
Room: "general",
Body: string(rune('a' + i)),
SentAt: base.Add(time.Duration(i) * time.Second),
})
}
msgs, err := st.RecentMessages("general", 10)
if err != nil {
t.Fatalf("RecentMessages: %v", err)
}
if len(msgs) != 5 {
t.Fatalf("got %d messages, want 5", len(msgs))
}
// Must be oldest-first.
for i := 1; i < len(msgs); i++ {
if msgs[i].SentAt.Before(msgs[i-1].SentAt) {
t.Fatalf("messages not in ascending order at index %d", i)
}
}
}