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>
This commit is contained in:
103
internal/store/store_test.go
Normal file
103
internal/store/store_test.go
Normal file
@@ -0,0 +1,103 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user