209 lines
4.9 KiB
Go
209 lines
4.9 KiB
Go
package prism
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"sync"
|
|
"testing"
|
|
|
|
"git.wisehodl.dev/jay/go-roots-ws"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestAuthManager(t *testing.T) {
|
|
t.Run("challenge triggers callback and send", func(t *testing.T) {
|
|
obs := &mockObserver{}
|
|
p, envoy := newMockEnvoy(t, WithEmbassyObserver(obs))
|
|
|
|
signed := []byte(`{"kind":22242}`)
|
|
var mu sync.Mutex
|
|
var calledWith string
|
|
callback := func(challenge string) ([]byte, error) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
calledWith = challenge
|
|
return signed, nil
|
|
}
|
|
|
|
m := NewAuthManager(envoy, callback)
|
|
t.Cleanup(m.Close)
|
|
|
|
p.connect()
|
|
p.receive([]byte(envelope.EncloseAuthChallenge("abc")))
|
|
|
|
Eventually(t, func() bool {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
return calledWith == "abc"
|
|
},
|
|
"callback not called with challenge")
|
|
|
|
Eventually(t, func() bool {
|
|
select {
|
|
case got := <-p.sent:
|
|
return bytes.Contains(got, signed)
|
|
default:
|
|
return false
|
|
}
|
|
}, "AUTH response envelope not sent")
|
|
|
|
assert.Len(t, EventsOf[ChallengeReceived](obs), 1)
|
|
assert.Len(t, EventsOf[AuthResponseSent](obs), 1)
|
|
})
|
|
|
|
t.Run("callback error recorded", func(t *testing.T) {
|
|
obs := &mockObserver{}
|
|
p, envoy := newMockEnvoy(t, WithEmbassyObserver(obs))
|
|
|
|
callback := func(challenge string) ([]byte, error) {
|
|
return nil, errors.New("signing failed")
|
|
}
|
|
|
|
m := NewAuthManager(envoy, callback)
|
|
t.Cleanup(m.Close)
|
|
|
|
p.connect()
|
|
p.receive([]byte(envelope.EncloseAuthChallenge("abc")))
|
|
|
|
Eventually(t, func() bool {
|
|
return len(EventsOf[AuthResponseFailed](obs)) == 1
|
|
}, "AuthResponseFailed not recorded")
|
|
|
|
Never(t, func() bool {
|
|
select {
|
|
case <-p.sent:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}, "no AUTH envelope should be sent on callback error")
|
|
|
|
assert.Len(t, EventsOf[ChallengeReceived](obs), 1)
|
|
})
|
|
|
|
t.Run("send error recorded", func(t *testing.T) {
|
|
obs := &mockObserver{}
|
|
p, envoy := newMockEnvoy(t, WithEmbassyObserver(obs))
|
|
|
|
callback := func(challenge string) ([]byte, error) {
|
|
return []byte(`{"kind":22242}`), nil
|
|
}
|
|
|
|
m := NewAuthManager(envoy, callback)
|
|
t.Cleanup(m.Close)
|
|
|
|
p.setSendError(errors.New("send failed"))
|
|
p.connect()
|
|
p.receive([]byte(envelope.EncloseAuthChallenge("abc")))
|
|
|
|
Eventually(t, func() bool {
|
|
return len(EventsOf[AuthResponseFailed](obs)) == 1
|
|
}, "AuthResponseFailed not recorded")
|
|
})
|
|
|
|
t.Run("disconnect resets state", func(t *testing.T) {
|
|
obs := &mockObserver{}
|
|
p, envoy := newMockEnvoy(t, WithEmbassyObserver(obs))
|
|
|
|
var mu sync.Mutex
|
|
var calls []string
|
|
callback := func(challenge string) ([]byte, error) {
|
|
mu.Lock()
|
|
calls = append(calls, challenge)
|
|
mu.Unlock()
|
|
return []byte(`{"kind":22242}`), nil
|
|
}
|
|
|
|
m := NewAuthManager(envoy, callback)
|
|
t.Cleanup(m.Close)
|
|
|
|
p.connect()
|
|
p.receive([]byte(envelope.EncloseAuthChallenge("abc")))
|
|
Eventually(t, func() bool {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
return len(calls) == 1 && calls[0] == "abc"
|
|
}, "first callback not fired")
|
|
|
|
p.disconnect()
|
|
// drain the sent channel so p.sent doesn't block the second send
|
|
select {
|
|
case <-p.sent:
|
|
default:
|
|
}
|
|
|
|
p.connect()
|
|
p.receive([]byte(envelope.EncloseAuthChallenge("def")))
|
|
Eventually(t, func() bool {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
return len(calls) == 2 && calls[1] == "def"
|
|
}, "second callback not fired with new challenge")
|
|
})
|
|
|
|
t.Run("replacement challenge", func(t *testing.T) {
|
|
p, envoy := newMockEnvoy(t)
|
|
|
|
var mu sync.Mutex
|
|
var calls []string
|
|
callback := func(challenge string) ([]byte, error) {
|
|
mu.Lock()
|
|
calls = append(calls, challenge)
|
|
mu.Unlock()
|
|
return []byte(`{"kind":22242}`), nil
|
|
}
|
|
|
|
m := NewAuthManager(envoy, callback)
|
|
t.Cleanup(m.Close)
|
|
|
|
p.connect()
|
|
p.receive([]byte(envelope.EncloseAuthChallenge("abc")))
|
|
p.receive([]byte(envelope.EncloseAuthChallenge("def")))
|
|
|
|
Eventually(t, func() bool {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
return len(calls) == 2 && calls[1] == "def"
|
|
}, "callback not called twice with both challenges")
|
|
|
|
assert.GreaterOrEqual(t, len(p.sent), 2)
|
|
})
|
|
|
|
t.Run("malformed AUTH ignored", func(t *testing.T) {
|
|
p, envoy := newMockEnvoy(t)
|
|
|
|
called := false
|
|
callback := func(challenge string) ([]byte, error) {
|
|
called = true
|
|
return []byte(`{"kind":22242}`), nil
|
|
}
|
|
|
|
m := NewAuthManager(envoy, callback)
|
|
t.Cleanup(m.Close)
|
|
|
|
p.connect()
|
|
// EncloseAuthResponse wraps a JSON object as the second element — not a string
|
|
p.receive([]byte(envelope.EncloseAuthResponse([]byte(`{}`))))
|
|
|
|
Never(t, func() bool { return called }, "callback should not be invoked for malformed AUTH")
|
|
})
|
|
|
|
t.Run("close cleans up", func(t *testing.T) {
|
|
p, envoy := newMockEnvoy(t)
|
|
|
|
called := false
|
|
callback := func(challenge string) ([]byte, error) {
|
|
called = true
|
|
return []byte(`{"kind":22242}`), nil
|
|
}
|
|
|
|
m := NewAuthManager(envoy, callback)
|
|
p.connect()
|
|
m.Close()
|
|
|
|
p.receive([]byte(envelope.EncloseAuthChallenge("abc")))
|
|
|
|
Never(t, func() bool { return called }, "callback should not fire after Close")
|
|
})
|
|
}
|