clone
This commit is contained in:
		
							
								
								
									
										266
									
								
								server/persistence/bolt/bolt.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								server/persistence/bolt/bolt.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,266 @@ | ||||
| package bolt | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"time" | ||||
|  | ||||
| 	sgob "github.com/asdine/storm/codec/gob" | ||||
| 	"github.com/asdine/storm/v3" | ||||
| 	"go.etcd.io/bbolt" | ||||
|  | ||||
| 	"github.com/mochi-co/mqtt/server/persistence" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
|  | ||||
| 	// defaultPath is the default file to use to store the data. | ||||
| 	defaultPath = "mochi.db" | ||||
|  | ||||
| 	// defaultTimeout is the default timeout of the file lock. | ||||
| 	defaultTimeout = 250 * time.Millisecond | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// ErrDBNotOpen indicates the bolt db file is not open for reading. | ||||
| 	ErrDBNotOpen = fmt.Errorf("boltdb not opened") | ||||
| ) | ||||
|  | ||||
| // Store is a backend for writing and reading to bolt persistent storage. | ||||
| type Store struct { | ||||
| 	path string         // the path on which to store the db file. | ||||
| 	opts *bbolt.Options // options for configuring the boltdb instance. | ||||
| 	db   *storm.DB      // the boltdb instance. | ||||
| } | ||||
|  | ||||
| // New returns a configured instance of the boltdb store. | ||||
| func New(path string, opts *bbolt.Options) *Store { | ||||
| 	if path == "" || path == "." { | ||||
| 		path = defaultPath | ||||
| 	} | ||||
|  | ||||
| 	if opts == nil { | ||||
| 		opts = &bbolt.Options{ | ||||
| 			Timeout: defaultTimeout, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &Store{ | ||||
| 		path: path, | ||||
| 		opts: opts, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Open opens the boltdb instance. | ||||
| func (s *Store) Open() error { | ||||
| 	var err error | ||||
| 	s.db, err = storm.Open(s.path, storm.BoltOptions(0600, s.opts), storm.Codec(sgob.Codec)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Close closes the boltdb instance. | ||||
| func (s *Store) Close() { | ||||
| 	s.db.Close() | ||||
| } | ||||
|  | ||||
| // WriteServerInfo writes the server info to the boltdb instance. | ||||
| func (s *Store) WriteServerInfo(v persistence.ServerInfo) error { | ||||
| 	if s.db == nil { | ||||
| 		return ErrDBNotOpen | ||||
| 	} | ||||
|  | ||||
| 	err := s.db.Save(&v) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // WriteSubscription writes a single subscription to the boltdb instance. | ||||
| func (s *Store) WriteSubscription(v persistence.Subscription) error { | ||||
| 	if s.db == nil { | ||||
| 		return ErrDBNotOpen | ||||
| 	} | ||||
|  | ||||
| 	err := s.db.Save(&v) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // WriteInflight writes a single inflight message to the boltdb instance. | ||||
| func (s *Store) WriteInflight(v persistence.Message) error { | ||||
| 	if s.db == nil { | ||||
| 		return ErrDBNotOpen | ||||
| 	} | ||||
|  | ||||
| 	err := s.db.Save(&v) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // WriteRetained writes a single retained message to the boltdb instance. | ||||
| func (s *Store) WriteRetained(v persistence.Message) error { | ||||
| 	if s.db == nil { | ||||
| 		return ErrDBNotOpen | ||||
| 	} | ||||
|  | ||||
| 	err := s.db.Save(&v) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // WriteClient writes a single client to the boltdb instance. | ||||
| func (s *Store) WriteClient(v persistence.Client) error { | ||||
| 	if s.db == nil { | ||||
| 		return ErrDBNotOpen | ||||
| 	} | ||||
|  | ||||
| 	err := s.db.Save(&v) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // DeleteSubscription deletes a subscription from the boltdb instance. | ||||
| func (s *Store) DeleteSubscription(id string) error { | ||||
| 	if s.db == nil { | ||||
| 		return ErrDBNotOpen | ||||
| 	} | ||||
|  | ||||
| 	err := s.db.DeleteStruct(&persistence.Subscription{ | ||||
| 		ID: id, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // DeleteClient deletes a client from the boltdb instance. | ||||
| func (s *Store) DeleteClient(id string) error { | ||||
| 	if s.db == nil { | ||||
| 		return ErrDBNotOpen | ||||
| 	} | ||||
|  | ||||
| 	err := s.db.DeleteStruct(&persistence.Client{ | ||||
| 		ID: id, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // DeleteInflight deletes an inflight message from the boltdb instance. | ||||
| func (s *Store) DeleteInflight(id string) error { | ||||
| 	if s.db == nil { | ||||
| 		return ErrDBNotOpen | ||||
| 	} | ||||
|  | ||||
| 	err := s.db.DeleteStruct(&persistence.Message{ | ||||
| 		ID: id, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // DeleteRetained deletes a retained message from the boltdb instance. | ||||
| func (s *Store) DeleteRetained(id string) error { | ||||
| 	if s.db == nil { | ||||
| 		return ErrDBNotOpen | ||||
| 	} | ||||
|  | ||||
| 	err := s.db.DeleteStruct(&persistence.Message{ | ||||
| 		ID: id, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ReadSubscriptions loads all the subscriptions from the boltdb instance. | ||||
| func (s *Store) ReadSubscriptions() (v []persistence.Subscription, err error) { | ||||
| 	if s.db == nil { | ||||
| 		return v, ErrDBNotOpen | ||||
| 	} | ||||
|  | ||||
| 	err = s.db.Find("T", persistence.KSubscription, &v) | ||||
| 	if err != nil && err != storm.ErrNotFound { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| // ReadClients loads all the clients from the boltdb instance. | ||||
| func (s *Store) ReadClients() (v []persistence.Client, err error) { | ||||
| 	if s.db == nil { | ||||
| 		return v, ErrDBNotOpen | ||||
| 	} | ||||
|  | ||||
| 	err = s.db.Find("T", persistence.KClient, &v) | ||||
| 	if err != nil && err != storm.ErrNotFound { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| // ReadInflight loads all the inflight messages from the boltdb instance. | ||||
| func (s *Store) ReadInflight() (v []persistence.Message, err error) { | ||||
| 	if s.db == nil { | ||||
| 		return v, ErrDBNotOpen | ||||
| 	} | ||||
|  | ||||
| 	err = s.db.Find("T", persistence.KInflight, &v) | ||||
| 	if err != nil && err != storm.ErrNotFound { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| // ReadRetained loads all the retained messages from the boltdb instance. | ||||
| func (s *Store) ReadRetained() (v []persistence.Message, err error) { | ||||
| 	if s.db == nil { | ||||
| 		return v, ErrDBNotOpen | ||||
| 	} | ||||
|  | ||||
| 	err = s.db.Find("T", persistence.KRetained, &v) | ||||
| 	if err != nil && err != storm.ErrNotFound { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
|  | ||||
| //ReadServerInfo loads the server info from the boltdb instance. | ||||
| func (s *Store) ReadServerInfo() (v persistence.ServerInfo, err error) { | ||||
| 	if s.db == nil { | ||||
| 		return v, ErrDBNotOpen | ||||
| 	} | ||||
|  | ||||
| 	err = s.db.One("ID", persistence.KServerInfo, &v) | ||||
| 	if err != nil && err != storm.ErrNotFound { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return v, nil | ||||
| } | ||||
							
								
								
									
										486
									
								
								server/persistence/bolt/bolt_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										486
									
								
								server/persistence/bolt/bolt_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,486 @@ | ||||
| package bolt | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/stretchr/testify/require" | ||||
| 	"go.etcd.io/bbolt" | ||||
|  | ||||
| 	"github.com/mochi-co/mqtt/server/persistence" | ||||
| 	"github.com/mochi-co/mqtt/server/system" | ||||
| ) | ||||
|  | ||||
| const tmpPath = "testbolt.db" | ||||
|  | ||||
| func teardown(s *Store, t *testing.T) { | ||||
| 	s.Close() | ||||
| 	err := os.Remove(tmpPath) | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestSatsifies(t *testing.T) { | ||||
| 	var x persistence.Store | ||||
| 	x = New(tmpPath, &bbolt.Options{ | ||||
| 		Timeout: 500 * time.Millisecond, | ||||
| 	}) | ||||
| 	require.NotNil(t, x) | ||||
| } | ||||
|  | ||||
| func TestNew(t *testing.T) { | ||||
| 	s := New(tmpPath, &bbolt.Options{ | ||||
| 		Timeout: 500 * time.Millisecond, | ||||
| 	}) | ||||
| 	require.NotNil(t, s) | ||||
| 	require.Equal(t, tmpPath, s.path) | ||||
| 	require.Equal(t, 500*time.Millisecond, s.opts.Timeout) | ||||
| } | ||||
|  | ||||
| func TestNewNoPath(t *testing.T) { | ||||
| 	s := New("", nil) | ||||
| 	require.NotNil(t, s) | ||||
| 	require.Equal(t, defaultPath, s.path) | ||||
| } | ||||
|  | ||||
| func TestNewNoOpts(t *testing.T) { | ||||
| 	s := New("", nil) | ||||
| 	require.NotNil(t, s) | ||||
| 	require.Equal(t, defaultTimeout, s.opts.Timeout) | ||||
| } | ||||
|  | ||||
| func TestOpen(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	defer teardown(s, t) | ||||
| 	require.NotNil(t, s.db) | ||||
| } | ||||
|  | ||||
| func TestOpenFailure(t *testing.T) { | ||||
| 	s := New("..", nil) | ||||
| 	err := s.Open() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestWriteAndRetrieveServerInfo(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	defer teardown(s, t) | ||||
|  | ||||
| 	v := system.Info{ | ||||
| 		Version: "test", | ||||
| 		Started: 100, | ||||
| 	} | ||||
| 	err = s.WriteServerInfo(persistence.ServerInfo{ | ||||
| 		Info: v, | ||||
| 		ID:   persistence.KServerInfo, | ||||
| 	}) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	r, err := s.ReadServerInfo() | ||||
| 	require.NoError(t, err) | ||||
| 	require.NotNil(t, r) | ||||
| 	require.Equal(t, v.Version, r.Version) | ||||
| 	require.Equal(t, v.Started, r.Started) | ||||
| } | ||||
|  | ||||
| func TestWriteServerInfoNoDB(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.WriteServerInfo(persistence.ServerInfo{}) | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestWriteServerInfoFail(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	err = os.Remove(tmpPath) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	err = s.WriteServerInfo(persistence.ServerInfo{}) | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestReadServerInfoNoDB(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	_, err := s.ReadServerInfo() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestReadServerInfoFail(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	s.Close() | ||||
| 	_, err = s.ReadServerInfo() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestWriteRetrieveDeleteSubscription(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	defer teardown(s, t) | ||||
|  | ||||
| 	v := persistence.Subscription{ | ||||
| 		ID:     "test:a/b/c", | ||||
| 		Client: "test", | ||||
| 		Filter: "a/b/c", | ||||
| 		QoS:    1, | ||||
| 		T:      persistence.KSubscription, | ||||
| 	} | ||||
| 	err = s.WriteSubscription(v) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	v2 := persistence.Subscription{ | ||||
| 		ID:     "test:d/e/f", | ||||
| 		Client: "test", | ||||
| 		Filter: "d/e/f", | ||||
| 		QoS:    2, | ||||
| 		T:      persistence.KSubscription, | ||||
| 	} | ||||
| 	err = s.WriteSubscription(v2) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	subs, err := s.ReadSubscriptions() | ||||
| 	require.NoError(t, err) | ||||
| 	require.Equal(t, persistence.KSubscription, subs[0].T) | ||||
| 	require.Equal(t, 2, len(subs)) | ||||
|  | ||||
| 	err = s.DeleteSubscription("test:d/e/f") | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	subs, err = s.ReadSubscriptions() | ||||
| 	require.NoError(t, err) | ||||
| 	require.Equal(t, 1, len(subs)) | ||||
| } | ||||
|  | ||||
| func TestWriteSubscriptionNoDB(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.WriteSubscription(persistence.Subscription{}) | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestWriteSubscriptionFail(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	s.Close() | ||||
| 	err = s.WriteSubscription(persistence.Subscription{}) | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestReadSubscriptionNoDB(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	_, err := s.ReadSubscriptions() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestReadSubscriptionFail(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	s.Close() | ||||
| 	_, err = s.ReadSubscriptions() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestWriteRetrieveDeleteInflight(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	defer teardown(s, t) | ||||
|  | ||||
| 	v := persistence.Message{ | ||||
| 		ID:        "client1_if_0", | ||||
| 		T:         persistence.KInflight, | ||||
| 		PacketID:  0, | ||||
| 		TopicName: "a/b/c", | ||||
| 		Payload:   []byte{'h', 'e', 'l', 'l', 'o'}, | ||||
| 		Sent:      100, | ||||
| 		Resends:   0, | ||||
| 	} | ||||
| 	err = s.WriteInflight(v) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	v2 := persistence.Message{ | ||||
| 		ID:        "client1_if_100", | ||||
| 		T:         persistence.KInflight, | ||||
| 		PacketID:  100, | ||||
| 		TopicName: "d/e/f", | ||||
| 		Payload:   []byte{'y', 'e', 's'}, | ||||
| 		Sent:      200, | ||||
| 		Resends:   1, | ||||
| 	} | ||||
| 	err = s.WriteInflight(v2) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	msgs, err := s.ReadInflight() | ||||
| 	require.NoError(t, err) | ||||
| 	require.Equal(t, persistence.KInflight, msgs[0].T) | ||||
| 	require.Equal(t, 2, len(msgs)) | ||||
|  | ||||
| 	err = s.DeleteInflight("client1_if_100") | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	msgs, err = s.ReadInflight() | ||||
| 	require.NoError(t, err) | ||||
| 	require.Equal(t, 1, len(msgs)) | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestWriteInflightNoDB(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.WriteInflight(persistence.Message{}) | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestWriteInflightFail(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	s.Close() | ||||
| 	err = s.WriteInflight(persistence.Message{}) | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestReadInflightNoDB(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	_, err := s.ReadInflight() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestReadInflightFail(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	s.Close() | ||||
| 	_, err = s.ReadInflight() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestWriteRetrieveDeleteRetained(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	defer teardown(s, t) | ||||
|  | ||||
| 	v := persistence.Message{ | ||||
| 		ID: "client1_ret_200", | ||||
| 		T:  persistence.KRetained, | ||||
| 		FixedHeader: persistence.FixedHeader{ | ||||
| 			Retain: true, | ||||
| 		}, | ||||
| 		PacketID:  200, | ||||
| 		TopicName: "a/b/c", | ||||
| 		Payload:   []byte{'h', 'e', 'l', 'l', 'o'}, | ||||
| 		Sent:      100, | ||||
| 		Resends:   0, | ||||
| 	} | ||||
| 	err = s.WriteRetained(v) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	v2 := persistence.Message{ | ||||
| 		ID: "client1_ret_300", | ||||
| 		T:  persistence.KRetained, | ||||
| 		FixedHeader: persistence.FixedHeader{ | ||||
| 			Retain: true, | ||||
| 		}, | ||||
| 		PacketID:  100, | ||||
| 		TopicName: "d/e/f", | ||||
| 		Payload:   []byte{'y', 'e', 's'}, | ||||
| 		Sent:      200, | ||||
| 		Resends:   1, | ||||
| 	} | ||||
| 	err = s.WriteRetained(v2) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	msgs, err := s.ReadRetained() | ||||
| 	require.NoError(t, err) | ||||
| 	require.Equal(t, persistence.KRetained, msgs[0].T) | ||||
| 	require.Equal(t, true, msgs[0].FixedHeader.Retain) | ||||
| 	require.Equal(t, 2, len(msgs)) | ||||
|  | ||||
| 	err = s.DeleteRetained("client1_ret_300") | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	msgs, err = s.ReadRetained() | ||||
| 	require.NoError(t, err) | ||||
| 	require.Equal(t, 1, len(msgs)) | ||||
| } | ||||
|  | ||||
| func TestWriteRetainedNoDB(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.WriteRetained(persistence.Message{}) | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestWriteRetainedFail(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	err = os.Remove(tmpPath) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	err = s.WriteRetained(persistence.Message{}) | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestReadRetainedNoDB(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	_, err := s.ReadRetained() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestReadRetainedFail(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	s.Close() | ||||
| 	_, err = s.ReadRetained() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestWriteRetrieveDeleteClients(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	defer teardown(s, t) | ||||
|  | ||||
| 	v := persistence.Client{ | ||||
| 		ID:       "cl_client1", | ||||
| 		ClientID: "client1", | ||||
| 		T:        persistence.KClient, | ||||
| 		Listener: "tcp1", | ||||
| 		Username: []byte{'m', 'o', 'c', 'h', 'i'}, | ||||
| 		LWT: persistence.LWT{ | ||||
| 			Topic:   "a/b/c", | ||||
| 			Message: []byte{'h', 'e', 'l', 'l', 'o'}, | ||||
| 			Qos:     1, | ||||
| 			Retain:  true, | ||||
| 		}, | ||||
| 	} | ||||
| 	err = s.WriteClient(v) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	clients, err := s.ReadClients() | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	require.Equal(t, []byte{'m', 'o', 'c', 'h', 'i'}, clients[0].Username) | ||||
| 	require.Equal(t, "a/b/c", clients[0].LWT.Topic) | ||||
|  | ||||
| 	v2 := persistence.Client{ | ||||
| 		ID:       "cl_client2", | ||||
| 		ClientID: "client2", | ||||
| 		T:        persistence.KClient, | ||||
| 		Listener: "tcp1", | ||||
| 	} | ||||
| 	err = s.WriteClient(v2) | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	clients, err = s.ReadClients() | ||||
| 	require.NoError(t, err) | ||||
| 	require.Equal(t, persistence.KClient, clients[0].T) | ||||
| 	require.Equal(t, 2, len(clients)) | ||||
|  | ||||
| 	err = s.DeleteClient("cl_client2") | ||||
| 	require.NoError(t, err) | ||||
|  | ||||
| 	clients, err = s.ReadClients() | ||||
| 	require.NoError(t, err) | ||||
| 	require.Equal(t, 1, len(clients)) | ||||
| } | ||||
|  | ||||
| func TestWriteClientNoDB(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.WriteClient(persistence.Client{}) | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestWriteClientFail(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	s.Close() | ||||
| 	err = s.WriteClient(persistence.Client{}) | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestReadClientNoDB(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	_, err := s.ReadClients() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestReadClientFail(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	s.Close() | ||||
| 	_, err = s.ReadClients() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestDeleteSubscriptionNoDB(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.DeleteSubscription("a") | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestDeleteSubscriptionFail(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	s.Close() | ||||
| 	err = s.DeleteSubscription("a") | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestDeleteClientNoDB(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.DeleteClient("a") | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestDeleteClientFail(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	s.Close() | ||||
| 	err = s.DeleteClient("a") | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestDeleteInflightNoDB(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.DeleteInflight("a") | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestDeleteInflightFail(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	s.Close() | ||||
| 	err = s.DeleteInflight("a") | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestDeleteRetainedNoDB(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.DeleteRetained("a") | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestDeleteRetainedFail(t *testing.T) { | ||||
| 	s := New(tmpPath, nil) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	s.Close() | ||||
| 	err = s.DeleteRetained("a") | ||||
| 	require.Error(t, err) | ||||
| } | ||||
							
								
								
									
										291
									
								
								server/persistence/persistence.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								server/persistence/persistence.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,291 @@ | ||||
| package persistence | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
|  | ||||
| 	"github.com/mochi-co/mqtt/server/system" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
|  | ||||
| 	// KSubscription is the key for subscription data. | ||||
| 	KSubscription = "sub" | ||||
|  | ||||
| 	// KServerInfo is the key for server info data. | ||||
| 	KServerInfo = "srv" | ||||
|  | ||||
| 	// KRetained is the key for retained messages data. | ||||
| 	KRetained = "ret" | ||||
|  | ||||
| 	// KInflight is the key for inflight messages data. | ||||
| 	KInflight = "ifm" | ||||
|  | ||||
| 	// KClient is the key for client data. | ||||
| 	KClient = "cl" | ||||
| ) | ||||
|  | ||||
| // Store is an interface which details a persistent storage connector. | ||||
| type Store interface { | ||||
| 	Open() error | ||||
| 	Close() | ||||
| 	WriteSubscription(v Subscription) error | ||||
| 	WriteClient(v Client) error | ||||
| 	WriteInflight(v Message) error | ||||
| 	WriteServerInfo(v ServerInfo) error | ||||
| 	WriteRetained(v Message) error | ||||
|  | ||||
| 	DeleteSubscription(id string) error | ||||
| 	DeleteClient(id string) error | ||||
| 	DeleteInflight(id string) error | ||||
| 	DeleteRetained(id string) error | ||||
|  | ||||
| 	ReadSubscriptions() (v []Subscription, err error) | ||||
| 	ReadInflight() (v []Message, err error) | ||||
| 	ReadRetained() (v []Message, err error) | ||||
| 	ReadClients() (v []Client, err error) | ||||
| 	ReadServerInfo() (v ServerInfo, err error) | ||||
| } | ||||
|  | ||||
| // ServerInfo contains information and statistics about the server. | ||||
| type ServerInfo struct { | ||||
| 	system.Info        // embed the system info struct. | ||||
| 	ID          string // the storage key. | ||||
| } | ||||
|  | ||||
| // Subscription contains the details of a topic filter subscription. | ||||
| type Subscription struct { | ||||
| 	ID     string // the storage key. | ||||
| 	T      string // the type of the stored data. | ||||
| 	Client string // the id of the client who the subscription belongs to. | ||||
| 	Filter string // the topic filter being subscribed to. | ||||
| 	QoS    byte   // the desired QoS byte. | ||||
| } | ||||
|  | ||||
| // Message contains the details of a retained or inflight message. | ||||
| type Message struct { | ||||
| 	Payload     []byte      // the message payload (if retained). | ||||
| 	FixedHeader FixedHeader // the header properties of the message. | ||||
| 	T           string      // the type of the stored data. | ||||
| 	ID          string      // the storage key. | ||||
| 	Client      string      // the id of the client who sent the message (if inflight). | ||||
| 	TopicName   string      // the topic the message was sent to (if retained). | ||||
| 	Sent        int64       // the last time the message was sent (for retries) in unixtime (if inflight). | ||||
| 	Resends     int         // the number of times the message was attempted to be sent (if inflight). | ||||
| 	PacketID    uint16      // the unique id of the packet (if inflight). | ||||
| } | ||||
|  | ||||
| // FixedHeader contains the fixed header properties of a message. | ||||
| type FixedHeader struct { | ||||
| 	Remaining int  // the number of remaining bytes in the payload. | ||||
| 	Type      byte // the type of the packet (PUBLISH, SUBSCRIBE, etc) from bits 7 - 4 (byte 1). | ||||
| 	Qos       byte // indicates the quality of service expected. | ||||
| 	Dup       bool // indicates if the packet was already sent at an earlier time. | ||||
| 	Retain    bool // whether the message should be retained. | ||||
| } | ||||
|  | ||||
| // Client contains client data that can be persistently stored. | ||||
| type Client struct { | ||||
| 	LWT      LWT    // the last-will-and-testament message for the client. | ||||
| 	Username []byte // the username the client authenticated with. | ||||
| 	ID       string // the storage key. | ||||
| 	ClientID string // the id of the client. | ||||
| 	T        string // the type of the stored data. | ||||
| 	Listener string // the last known listener id for the client | ||||
| } | ||||
|  | ||||
| // LWT contains details about a clients LWT payload. | ||||
| type LWT struct { | ||||
| 	Message []byte // the message that shall be sent when the client disconnects. | ||||
| 	Topic   string // the topic the will message shall be sent to. | ||||
| 	Qos     byte   // the quality of service desired. | ||||
| 	Retain  bool   // indicates whether the will message should be retained | ||||
| } | ||||
|  | ||||
| // MockStore is a mock storage backend for testing. | ||||
| type MockStore struct { | ||||
| 	Fail     map[string]bool // issue errors for different methods. | ||||
| 	FailOpen bool            // error on open. | ||||
| 	Closed   bool            // indicate mock store is closed. | ||||
| 	Opened   bool            // indicate mock store is open. | ||||
| } | ||||
|  | ||||
| // Open opens the storage instance. | ||||
| func (s *MockStore) Open() error { | ||||
| 	if s.FailOpen { | ||||
| 		return errors.New("test") | ||||
| 	} | ||||
|  | ||||
| 	s.Opened = true | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Close closes the storage instance. | ||||
| func (s *MockStore) Close() { | ||||
| 	s.Closed = true | ||||
| } | ||||
|  | ||||
| // WriteSubscription writes a single subscription to the storage instance. | ||||
| func (s *MockStore) WriteSubscription(v Subscription) error { | ||||
| 	if _, ok := s.Fail["write_subs"]; ok { | ||||
| 		return errors.New("test") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // WriteClient writes a single client to the storage instance. | ||||
| func (s *MockStore) WriteClient(v Client) error { | ||||
| 	if _, ok := s.Fail["write_clients"]; ok { | ||||
| 		return errors.New("test") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // WriteInFlight writes a single InFlight message to the storage instance. | ||||
| func (s *MockStore) WriteInflight(v Message) error { | ||||
| 	if _, ok := s.Fail["write_inflight"]; ok { | ||||
| 		return errors.New("test") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // WriteRetained writes a single retained message to the storage instance. | ||||
| func (s *MockStore) WriteRetained(v Message) error { | ||||
| 	if _, ok := s.Fail["write_retained"]; ok { | ||||
| 		return errors.New("test") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // WriteServerInfo writes server info to the storage instance. | ||||
| func (s *MockStore) WriteServerInfo(v ServerInfo) error { | ||||
| 	if _, ok := s.Fail["write_info"]; ok { | ||||
| 		return errors.New("test") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // DeleteSubscription deletes a subscription from the persistent store. | ||||
| func (s *MockStore) DeleteSubscription(id string) error { | ||||
| 	if _, ok := s.Fail["delete_subs"]; ok { | ||||
| 		return errors.New("test") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // DeleteClient deletes a client from the persistent store. | ||||
| func (s *MockStore) DeleteClient(id string) error { | ||||
| 	if _, ok := s.Fail["delete_clients"]; ok { | ||||
| 		return errors.New("test") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // DeleteInflight deletes an inflight message from the persistent store. | ||||
| func (s *MockStore) DeleteInflight(id string) error { | ||||
| 	if _, ok := s.Fail["delete_inflight"]; ok { | ||||
| 		return errors.New("test") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // DeleteRetained deletes a retained message from the persistent store. | ||||
| func (s *MockStore) DeleteRetained(id string) error { | ||||
| 	if _, ok := s.Fail["delete_retained"]; ok { | ||||
| 		return errors.New("test") | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // ReadSubscriptions loads the subscriptions from the storage instance. | ||||
| func (s *MockStore) ReadSubscriptions() (v []Subscription, err error) { | ||||
| 	if _, ok := s.Fail["read_subs"]; ok { | ||||
| 		return v, errors.New("test_subs") | ||||
| 	} | ||||
|  | ||||
| 	return []Subscription{ | ||||
| 		{ | ||||
| 			ID:     "test:a/b/c", | ||||
| 			Client: "test", | ||||
| 			Filter: "a/b/c", | ||||
| 			QoS:    1, | ||||
| 			T:      KSubscription, | ||||
| 		}, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // ReadClients loads the clients from the storage instance. | ||||
| func (s *MockStore) ReadClients() (v []Client, err error) { | ||||
| 	if _, ok := s.Fail["read_clients"]; ok { | ||||
| 		return v, errors.New("test_clients") | ||||
| 	} | ||||
|  | ||||
| 	return []Client{ | ||||
| 		{ | ||||
| 			ID:       "cl_client1", | ||||
| 			ClientID: "client1", | ||||
| 			T:        KClient, | ||||
| 			Listener: "tcp1", | ||||
| 		}, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // ReadInflight loads the inflight messages from the storage instance. | ||||
| func (s *MockStore) ReadInflight() (v []Message, err error) { | ||||
| 	if _, ok := s.Fail["read_inflight"]; ok { | ||||
| 		return v, errors.New("test_inflight") | ||||
| 	} | ||||
|  | ||||
| 	return []Message{ | ||||
| 		{ | ||||
| 			ID:        "client1_if_100", | ||||
| 			T:         KInflight, | ||||
| 			Client:    "client1", | ||||
| 			PacketID:  100, | ||||
| 			TopicName: "d/e/f", | ||||
| 			Payload:   []byte{'y', 'e', 's'}, | ||||
| 			Sent:      200, | ||||
| 			Resends:   1, | ||||
| 		}, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // ReadRetained loads the retained messages from the storage instance. | ||||
| func (s *MockStore) ReadRetained() (v []Message, err error) { | ||||
| 	if _, ok := s.Fail["read_retained"]; ok { | ||||
| 		return v, errors.New("test_retained") | ||||
| 	} | ||||
|  | ||||
| 	return []Message{ | ||||
| 		{ | ||||
| 			ID: "client1_ret_200", | ||||
| 			T:  KRetained, | ||||
| 			FixedHeader: FixedHeader{ | ||||
| 				Retain: true, | ||||
| 			}, | ||||
| 			PacketID:  200, | ||||
| 			TopicName: "a/b/c", | ||||
| 			Payload:   []byte{'h', 'e', 'l', 'l', 'o'}, | ||||
| 			Sent:      100, | ||||
| 			Resends:   0, | ||||
| 		}, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| //ReadServerInfo loads the server info from the storage instance. | ||||
| func (s *MockStore) ReadServerInfo() (v ServerInfo, err error) { | ||||
| 	if _, ok := s.Fail["read_info"]; ok { | ||||
| 		return v, errors.New("test_info") | ||||
| 	} | ||||
|  | ||||
| 	return ServerInfo{ | ||||
| 		system.Info{ | ||||
| 			Version: "test", | ||||
| 			Started: 100, | ||||
| 		}, | ||||
| 		KServerInfo, | ||||
| 	}, nil | ||||
| } | ||||
							
								
								
									
										251
									
								
								server/persistence/persistence_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								server/persistence/persistence_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,251 @@ | ||||
| package persistence | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/require" | ||||
| ) | ||||
|  | ||||
| func TestMockStoreOpen(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	err := s.Open() | ||||
| 	require.NoError(t, err) | ||||
| 	require.Equal(t, true, s.Opened) | ||||
| } | ||||
|  | ||||
| func TestMockStoreOpenFail(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	s.FailOpen = true | ||||
| 	err := s.Open() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreClose(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	s.Close() | ||||
| 	require.Equal(t, true, s.Closed) | ||||
| } | ||||
|  | ||||
| func TestMockStoreWriteSubscription(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	err := s.WriteSubscription(Subscription{}) | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreWriteSubscriptionFail(t *testing.T) { | ||||
| 	s := &MockStore{ | ||||
| 		Fail: map[string]bool{ | ||||
| 			"write_subs": true, | ||||
| 		}, | ||||
| 	} | ||||
| 	err := s.WriteSubscription(Subscription{}) | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreWriteClient(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	err := s.WriteClient(Client{}) | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreWriteClientFail(t *testing.T) { | ||||
| 	s := &MockStore{ | ||||
| 		Fail: map[string]bool{ | ||||
| 			"write_clients": true, | ||||
| 		}, | ||||
| 	} | ||||
| 	err := s.WriteClient(Client{}) | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreWriteInflight(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	err := s.WriteInflight(Message{}) | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreWriteInflightFail(t *testing.T) { | ||||
| 	s := &MockStore{ | ||||
| 		Fail: map[string]bool{ | ||||
| 			"write_inflight": true, | ||||
| 		}, | ||||
| 	} | ||||
| 	err := s.WriteInflight(Message{}) | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreWriteRetained(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	err := s.WriteRetained(Message{}) | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreWriteRetainedFail(t *testing.T) { | ||||
| 	s := &MockStore{ | ||||
| 		Fail: map[string]bool{ | ||||
| 			"write_retained": true, | ||||
| 		}, | ||||
| 	} | ||||
| 	err := s.WriteRetained(Message{}) | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreWriteServerInfo(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	err := s.WriteServerInfo(ServerInfo{}) | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreWriteServerInfoFail(t *testing.T) { | ||||
| 	s := &MockStore{ | ||||
| 		Fail: map[string]bool{ | ||||
| 			"write_info": true, | ||||
| 		}, | ||||
| 	} | ||||
| 	err := s.WriteServerInfo(ServerInfo{}) | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreDeleteSubscription(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	err := s.DeleteSubscription("client1:d/e/f") | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreDeleteSubscriptionFail(t *testing.T) { | ||||
| 	s := &MockStore{ | ||||
| 		Fail: map[string]bool{ | ||||
| 			"delete_subs": true, | ||||
| 		}, | ||||
| 	} | ||||
| 	err := s.DeleteSubscription("client1:a/b/c") | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreDeleteClient(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	err := s.DeleteClient("client1") | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreDeleteClientFail(t *testing.T) { | ||||
| 	s := &MockStore{ | ||||
| 		Fail: map[string]bool{ | ||||
| 			"delete_clients": true, | ||||
| 		}, | ||||
| 	} | ||||
| 	err := s.DeleteClient("client1") | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreDeleteInflight(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	err := s.DeleteInflight("client1-if-100") | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreDeleteInflightFail(t *testing.T) { | ||||
| 	s := &MockStore{ | ||||
| 		Fail: map[string]bool{ | ||||
| 			"delete_inflight": true, | ||||
| 		}, | ||||
| 	} | ||||
| 	err := s.DeleteInflight("client1-if-100") | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreDeleteRetained(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	err := s.DeleteRetained("client1-ret-100") | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreDeleteRetainedFail(t *testing.T) { | ||||
| 	s := &MockStore{ | ||||
| 		Fail: map[string]bool{ | ||||
| 			"delete_retained": true, | ||||
| 		}, | ||||
| 	} | ||||
| 	err := s.DeleteRetained("client1-ret-100") | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStorReadServerInfo(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	_, err := s.ReadServerInfo() | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStorReadServerInfoFail(t *testing.T) { | ||||
| 	s := &MockStore{ | ||||
| 		Fail: map[string]bool{ | ||||
| 			"read_info": true, | ||||
| 		}, | ||||
| 	} | ||||
| 	_, err := s.ReadServerInfo() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreReadSubscriptions(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	_, err := s.ReadSubscriptions() | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreReadSubscriptionsFail(t *testing.T) { | ||||
| 	s := &MockStore{ | ||||
| 		Fail: map[string]bool{ | ||||
| 			"read_subs": true, | ||||
| 		}, | ||||
| 	} | ||||
| 	_, err := s.ReadSubscriptions() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreReadClients(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	_, err := s.ReadClients() | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreReadClientsFail(t *testing.T) { | ||||
| 	s := &MockStore{ | ||||
| 		Fail: map[string]bool{ | ||||
| 			"read_clients": true, | ||||
| 		}, | ||||
| 	} | ||||
| 	_, err := s.ReadClients() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreReadInflight(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	_, err := s.ReadInflight() | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreReadInflightFail(t *testing.T) { | ||||
| 	s := &MockStore{ | ||||
| 		Fail: map[string]bool{ | ||||
| 			"read_inflight": true, | ||||
| 		}, | ||||
| 	} | ||||
| 	_, err := s.ReadInflight() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreReadRetained(t *testing.T) { | ||||
| 	s := new(MockStore) | ||||
| 	_, err := s.ReadRetained() | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestMockStoreReadRetainedFail(t *testing.T) { | ||||
| 	s := &MockStore{ | ||||
| 		Fail: map[string]bool{ | ||||
| 			"read_retained": true, | ||||
| 		}, | ||||
| 	} | ||||
| 	_, err := s.ReadRetained() | ||||
| 	require.Error(t, err) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user