Files
openfsd/postoffice/post_office_test.go
Reese Norris 57d54d6705 v0.1.0-alpha
Changes:
- Implement bootstrapping library for managing several concurrent internal services
- Refactor concurrency model for connections/logical clients and their associated I/O
- Refactor server context singleton
- Refactor error handling
    - Most errors are now gracefully sent to the FSD client directly encoded as an $ER packet,
      enhancing visibility and debugging
    - Most errors are now rightfully treated as non-fatal
- Refactor package/dependency graph
- Refactor calling conventions/interfaces for many packages
- Refactor database package
- Refactor post office

Features:
- Add VATSIM-esque HTTP/JSON "data feed"
- Add ephemeral in-memory database option
- Add user management REST API
- Add improved web interface
- Add MySQL support (drop SQLite support)
2024-10-07 12:50:39 -07:00

294 lines
5.2 KiB
Go

package postoffice
import (
"errors"
"github.com/renorris/openfsd/protocol"
"github.com/stretchr/testify/assert"
"testing"
"time"
)
type testAddress struct {
name string
mailbox chan string
killbox chan string
rating protocol.NetworkRating
geohash Geohash
}
func (t *testAddress) Name() string {
return t.name
}
func (t *testAddress) SendMail(s string) {
select {
case t.mailbox <- s:
default:
}
}
func (t *testAddress) SendKill(s string) error {
select {
case t.killbox <- s:
return nil
default:
return errors.New("killbox defaulted")
}
}
func (t *testAddress) NetworkRating() protocol.NetworkRating {
return t.rating
}
func (t *testAddress) Geohash() Geohash {
return t.geohash
}
func (t *testAddress) State() AddressState {
return AddressState{}
}
func TestPostOffice(t *testing.T) {
PO := NewPostOffice()
c1 := testAddress{
name: "1",
mailbox: make(chan string, 16),
killbox: make(chan string, 16),
rating: protocol.NetworkRatingOBS,
geohash: Geohash{},
}
c2 := testAddress{
name: "2",
mailbox: make(chan string, 16),
killbox: make(chan string, 16),
rating: protocol.NetworkRatingSUP,
geohash: Geohash{},
}
// Test RegisterAddress
// Register "1"
{
err := PO.RegisterAddress(&c1)
assert.Nil(t, err)
}
// Register "1" twice
{
err := PO.RegisterAddress(&c1)
assert.NotNil(t, err)
assert.ErrorIs(t, KeyInUseError, err)
}
// Register "2"
{
err := PO.RegisterAddress(&c2)
assert.Nil(t, err)
}
// Register "2" twice
{
err := PO.RegisterAddress(&c2)
assert.NotNil(t, err)
assert.ErrorIs(t, KeyInUseError, err)
}
// Test SendMail
{
m := Mail{
mailType: MailTypeDirect,
source: &c1,
recipient: "2",
packet: "hello",
}
PO.SendMail(&m)
assert.Empty(t, c1.mailbox)
assert.NotEmpty(t, c2.mailbox)
msg := <-c2.mailbox
assert.Equal(t, "hello", msg)
assert.Empty(t, c1.mailbox)
assert.Empty(t, c2.mailbox)
}
{
m := Mail{
mailType: MailTypeBroadcast,
source: &c1,
recipient: "2",
packet: "hello",
}
PO.SendMail(&m)
assert.Empty(t, c1.mailbox)
assert.NotEmpty(t, c2.mailbox)
{
msg := <-c2.mailbox
assert.Equal(t, "hello", msg)
}
}
// Test supervisor message
{
m := Mail{
mailType: MailTypeSupervisorBroadcast,
source: &c1,
packet: "hello",
}
PO.SendMail(&m)
assert.Empty(t, c1.mailbox)
assert.NotEmpty(t, c2.mailbox)
{
msg := <-c2.mailbox
assert.Equal(t, "hello", msg)
}
}
// Test ranged broadcast mail
{
// c1 and c2 in range:
c1.geohash = PO.SetLocation(&c1, 45.0, 45.0)
c2.geohash = PO.SetLocation(&c2, 45.0, 45.0)
m := Mail{
mailType: MailTypeGeneralProximityBroadcast,
source: &c1,
packet: "hello",
}
PO.SendMail(&m)
// c2 should receive the broadcast
assert.Empty(t, c1.mailbox)
assert.NotEmpty(t, c2.mailbox)
{
msg := <-c2.mailbox
assert.Equal(t, "hello", msg)
}
// Move c2 far away from c1
c2.geohash = PO.SetLocation(&c2, -45.0, -45.0)
// Try again, nobody should see anything now
PO.SendMail(&m)
assert.Empty(t, c1.mailbox)
assert.Empty(t, c2.mailbox)
// Move back
c2.geohash = PO.SetLocation(&c2, 45.0, 45.0)
// Should see the message again
PO.SendMail(&m)
assert.Empty(t, c1.mailbox)
assert.NotEmpty(t, c2.mailbox)
{
msg := <-c2.mailbox
assert.Equal(t, "hello", msg)
}
// Move c2 to a general proximity neighbor cell
c2.geohash = PO.SetLocation(&c2, 45.7, 47.1)
// c1 geohash: v00
// c2 geohash: v01 (neighbor)
PO.SendMail(&m)
assert.Empty(t, c1.mailbox)
assert.NotEmpty(t, c2.mailbox)
{
msg := <-c2.mailbox
assert.Equal(t, "hello", msg)
}
// Test close proximity broadcast
m2 := Mail{
mailType: MailTypeCloseProximityBroadcast,
source: &c1,
packet: "hello",
}
// Set client 1 and client 2 in the same location
c1.geohash = PO.SetLocation(&c1, 32.713026, -117.176283)
c2.geohash = PO.SetLocation(&c2, 32.713026, -117.176283)
PO.SendMail(&m2)
assert.Empty(t, c1.mailbox)
assert.NotEmpty(t, c2.mailbox)
{
msg := <-c2.mailbox
assert.Equal(t, "hello", msg)
}
// Move client 2 to a neighbor close-proximity cell
c2.geohash = PO.SetLocation(&c2, 32.719491, -117.134442)
// client 2 should still see close-proximity mail
PO.SendMail(&m2)
assert.Empty(t, c1.mailbox)
assert.NotEmpty(t, c2.mailbox)
{
msg := <-c2.mailbox
assert.Equal(t, "hello", msg)
}
// Move c2 two close-proximity cells away
c2.geohash = PO.SetLocation(&c2, 32.715297, -117.091257)
// Nobody should see any mail
PO.SendMail(&m2)
assert.Empty(t, c1.mailbox)
assert.Empty(t, c2.mailbox)
}
// Unavailable browser mailbox should not block SendMail
{
m := Mail{
mailType: MailTypeDirect,
source: &c1,
recipient: "2",
packet: "hello",
}
c2.mailbox = nil
timer := time.NewTimer(100 * time.Millisecond)
done := make(chan interface{})
go func() {
PO.SendMail(&m)
close(done)
}()
select {
case <-timer.C:
assert.Fail(t, "SendMail timeout")
case <-done:
}
}
// Deregister "2"
PO.DeregisterAddress(&c2)
// Deregister "2" twice
PO.DeregisterAddress(&c2)
// Deregister "1"
PO.DeregisterAddress(&c1)
// Deregister "1" twice
PO.DeregisterAddress(&c1)
}