mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-06-16 23:14:13 +00:00
- add `orama-sni-router` binary to build process - introduce `cmd/sni-router` for TLS-level SNI routing - add documentation for stealth turn deployment architecture
150 lines
4.2 KiB
Go
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")
|
|
}
|
|
}
|