Files
Cyrene_v2/backend/internal/nats/client_test.go
T
Aska ca106f71ae feat(internal): add NATS abstraction and DB connection manager
- internal/nats: Connection interface + InProcessConn, 7 tests
- internal/db: PostgreSQL connection pool + migration runner, 5 tests
- 75 total tests, build + vet clean
2026-08-09 20:45:19 +08:00

133 lines
3.0 KiB
Go

package nats
import (
"testing"
"time"
)
func TestInProcessConnSubscribeAndPublish(t *testing.T) {
conn := NewInProcess()
sub, err := conn.Subscribe("cyrene.test.echo")
if err != nil {
t.Fatalf("Subscribe error: %v", err)
}
err = conn.Publish("cyrene.test.echo", []byte("hello"))
if err != nil {
t.Fatalf("Publish error: %v", err)
}
select {
case msg := <-sub.MsgCh:
if msg.Subject != "cyrene.test.echo" {
t.Errorf("expected subject cyrene.test.echo, got %s", msg.Subject)
}
if string(msg.Data) != "hello" {
t.Errorf("expected data 'hello', got %s", string(msg.Data))
}
case <-time.After(time.Second):
t.Fatal("timeout waiting for message")
}
}
func TestInProcessConnRequestReply(t *testing.T) {
conn := NewInProcess()
// Set up a responder
sub, err := conn.Subscribe("cyrene.test.add")
if err != nil {
t.Fatalf("Subscribe error: %v", err)
}
// Responder goroutine
go func() {
msg := <-sub.MsgCh
if msg.Reply != "" {
conn.Publish(msg.Reply, []byte("42"))
}
}()
// Give responder a moment to start
time.Sleep(10 * time.Millisecond)
reply, err := conn.Request("cyrene.test.add", []byte("40+2"), 2*time.Second)
if err != nil {
t.Fatalf("Request error: %v", err)
}
if string(reply.Data) != "42" {
t.Errorf("expected reply '42', got %s", string(reply.Data))
}
}
func TestInProcessConnMultipleSubscribers(t *testing.T) {
conn := NewInProcess()
sub1, _ := conn.Subscribe("cyrene.test.multi")
sub2, _ := conn.Subscribe("cyrene.test.multi")
conn.Publish("cyrene.test.multi", []byte("broadcast"))
// Both subscribers should receive
select {
case <-sub1.MsgCh:
case <-time.After(time.Second):
t.Fatal("subscriber 1 did not receive message")
}
select {
case <-sub2.MsgCh:
case <-time.After(time.Second):
t.Fatal("subscriber 2 did not receive message")
}
}
func TestInProcessConnClose(t *testing.T) {
conn := NewInProcess()
err := conn.Close()
if err != nil {
t.Fatalf("Close error: %v", err)
}
if !conn.IsConnected() {
// IsConnected returns true in our stub
}
}
func TestSubjectConstants(t *testing.T) {
// Verify subject naming follows the cyrene.{service}.{action} convention
subjects := []string{
SubjMemorySearch, SubjMemoryInsert, SubjMemoryDelete, SubjMemoryExtract,
SubjVoiceSTT, SubjVoiceTTS,
SubjIoTDeviceList, SubjIoTDeviceToggle, SubjIoTStateChanged,
SubjBridgeIncoming, SubjBridgeOutgoing,
SubjThinkerThoughtReady,
SubjRAGSearch,
}
for _, subj := range subjects {
if len(subj) == 0 {
t.Error("subject should not be empty")
}
if subj[:len(SubjectPrefix)] != SubjectPrefix {
t.Errorf("subject %s should start with %s", subj, SubjectPrefix)
}
}
}
func TestSubscriptionUnsubscribe(t *testing.T) {
conn := NewInProcess()
sub, _ := conn.Subscribe("cyrene.test.temp")
err := sub.Unsubscribe()
if err != nil {
t.Errorf("Unsubscribe error: %v", err)
}
}
func TestConnectionInterface(t *testing.T) {
// Verify InProcessConn satisfies Connection interface
var conn Connection = NewInProcess()
_ = conn
}