orama/core/pkg/push/dispatcher_test.go
anonpenguin23 0379dc39f1 feat(core): implement sni-router for stealth turn
- add `orama-sni-router` binary to build process
- introduce `cmd/sni-router` for TLS-level SNI routing
- add documentation for stealth turn deployment architecture
2026-05-03 18:20:21 +03:00

150 lines
4.2 KiB
Go

package push
import (
"context"
"errors"
"sync/atomic"
"testing"
"go.uber.org/zap"
)
// fakeProvider records every Send call.
type fakeProvider struct {
name string
sent int32
lastToken string
err error
}
func (f *fakeProvider) Name() string { return f.name }
func (f *fakeProvider) Send(ctx context.Context, msg PushMessage) error {
atomic.AddInt32(&f.sent, 1)
f.lastToken = msg.DeviceToken
return f.err
}
// fakeStore is an in-memory PushDeviceStore.
type fakeStore struct {
devices []PushDevice
err error
}
func (s *fakeStore) Upsert(ctx context.Context, dev PushDevice) error {
if s.err != nil {
return s.err
}
s.devices = append(s.devices, dev)
return nil
}
func (s *fakeStore) Delete(ctx context.Context, ns, id string) error { return nil }
func (s *fakeStore) ListForUser(ctx context.Context, ns, userID string) ([]PushDevice, error) {
if s.err != nil {
return nil, s.err
}
out := []PushDevice{}
for _, d := range s.devices {
if d.Namespace == ns && d.UserID == userID {
out = append(out, d)
}
}
return out, nil
}
func TestSendToUser_no_devices_returns_nil(t *testing.T) {
d := New(&fakeStore{}, zap.NewNop())
if err := d.SendToUser(context.Background(), "ns", "u", PushMessage{Title: "x"}); err != nil {
t.Fatalf("expected nil for no devices, got: %v", err)
}
}
func TestSendToUser_routes_to_correct_provider(t *testing.T) {
store := &fakeStore{devices: []PushDevice{
{Namespace: "ns", UserID: "u", Provider: "ntfy", Token: "ntfy-tok"},
{Namespace: "ns", UserID: "u", Provider: "expo", Token: "expo-tok"},
}}
ntfy := &fakeProvider{name: "ntfy"}
expo := &fakeProvider{name: "expo"}
d := New(store, zap.NewNop())
d.Register(ntfy)
d.Register(expo)
if err := d.SendToUser(context.Background(), "ns", "u", PushMessage{Title: "hi"}); err != nil {
t.Fatalf("SendToUser: %v", err)
}
if atomic.LoadInt32(&ntfy.sent) != 1 || ntfy.lastToken != "ntfy-tok" {
t.Errorf("ntfy provider not called correctly: sent=%d token=%s", ntfy.sent, ntfy.lastToken)
}
if atomic.LoadInt32(&expo.sent) != 1 || expo.lastToken != "expo-tok" {
t.Errorf("expo provider not called correctly: sent=%d token=%s", expo.sent, expo.lastToken)
}
}
func TestSendToUser_unknown_provider_returns_error_continues(t *testing.T) {
store := &fakeStore{devices: []PushDevice{
{Namespace: "ns", UserID: "u", Provider: "ghost", Token: "tok"},
{Namespace: "ns", UserID: "u", Provider: "ntfy", Token: "real"},
}}
ntfy := &fakeProvider{name: "ntfy"}
d := New(store, zap.NewNop())
d.Register(ntfy)
err := d.SendToUser(context.Background(), "ns", "u", PushMessage{})
if err == nil {
t.Fatal("expected error for unknown provider")
}
if !errors.Is(err, ErrUnknownProvider) {
t.Errorf("expected ErrUnknownProvider, got %v", err)
}
// ntfy should still have been called.
if atomic.LoadInt32(&ntfy.sent) != 1 {
t.Error("ntfy should have been called for the second device")
}
}
func TestSendToUser_provider_failure_returned_but_other_devices_still_processed(t *testing.T) {
store := &fakeStore{devices: []PushDevice{
{Namespace: "ns", UserID: "u", Provider: "expo", Token: "tok-1"},
{Namespace: "ns", UserID: "u", Provider: "ntfy", Token: "tok-2"},
}}
expoErr := errors.New("expo down")
expo := &fakeProvider{name: "expo", err: expoErr}
ntfy := &fakeProvider{name: "ntfy"}
d := New(store, zap.NewNop())
d.Register(expo)
d.Register(ntfy)
err := d.SendToUser(context.Background(), "ns", "u", PushMessage{})
if !errors.Is(err, expoErr) {
t.Errorf("expected expo error, got %v", err)
}
if atomic.LoadInt32(&ntfy.sent) != 1 {
t.Error("ntfy should have been called even though expo failed")
}
}
func TestSendToUser_store_error_propagated(t *testing.T) {
storeErr := errors.New("store boom")
d := New(&fakeStore{err: storeErr}, zap.NewNop())
err := d.SendToUser(context.Background(), "ns", "u", PushMessage{})
if err == nil || !errors.Is(err, storeErr) {
t.Errorf("expected store error, got %v", err)
}
}
func TestRegister_replaces_existing_provider(t *testing.T) {
d := New(&fakeStore{}, zap.NewNop())
a := &fakeProvider{name: "ntfy"}
b := &fakeProvider{name: "ntfy"}
d.Register(a)
d.Register(b)
if d.Provider("ntfy") != b {
t.Error("expected second Register to replace the first")
}
}