github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/communication/nats/connection_mock.go (about)

     1  /*
     2   * Copyright (C) 2017 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package nats
    19  
    20  import (
    21  	"context"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/nats-io/nats.go"
    26  	"github.com/pkg/errors"
    27  )
    28  
    29  // NewConnectionMock constructs new NATS connection
    30  // which delivers published messages to local subscribers
    31  func NewConnectionMock() *ConnectionMock {
    32  	return &ConnectionMock{
    33  		subscriptions: make(map[string][]nats.MsgHandler),
    34  		queue:         make(chan *nats.Msg),
    35  		queueShutdown: make(chan bool),
    36  	}
    37  }
    38  
    39  // StartConnectionMock creates connection and starts it immediately
    40  func StartConnectionMock() *ConnectionMock {
    41  	connection := NewConnectionMock()
    42  	connection.Open()
    43  
    44  	return connection
    45  }
    46  
    47  // ConnectionMock acts as a local connection implementation
    48  type ConnectionMock struct {
    49  	subscriptions map[string][]nats.MsgHandler
    50  	queue         chan *nats.Msg
    51  	queueShutdown chan bool
    52  
    53  	messageLast *nats.Msg
    54  	m           sync.Mutex
    55  	requestLast *nats.Msg
    56  	errorMock   error
    57  }
    58  
    59  // GetLastMessageSubject returns the last message subject
    60  func (conn *ConnectionMock) GetLastMessageSubject() string {
    61  	if conn.messageLast != nil {
    62  		return conn.messageLast.Subject
    63  	}
    64  	return ""
    65  }
    66  
    67  // GetLastMessage returns the last message received
    68  func (conn *ConnectionMock) GetLastMessage() []byte {
    69  	if conn.messageLast != nil {
    70  		return conn.messageLast.Data
    71  	}
    72  	return []byte{}
    73  }
    74  
    75  // GetLastRequest gets last request data
    76  func (conn *ConnectionMock) GetLastRequest() []byte {
    77  	if conn.requestLast != nil {
    78  		return conn.requestLast.Data
    79  	}
    80  	return []byte{}
    81  }
    82  
    83  // MockResponse mocks the response
    84  func (conn *ConnectionMock) MockResponse(subject string, payload []byte) {
    85  	conn.Subscribe(subject, func(message *nats.Msg) {
    86  		conn.Publish(message.Reply, payload)
    87  	})
    88  }
    89  
    90  // MockError mocks the error
    91  func (conn *ConnectionMock) MockError(message string) {
    92  	conn.errorMock = errors.New(message)
    93  }
    94  
    95  // MessageWait waits for a message to arrive
    96  func (conn *ConnectionMock) MessageWait(waitChannel chan interface{}) (interface{}, error) {
    97  	select {
    98  	case message := <-waitChannel:
    99  		return message, nil
   100  	case <-time.After(10 * time.Millisecond):
   101  		return nil, errors.New("Message not received")
   102  	}
   103  }
   104  
   105  // Publish publishes a new message
   106  func (conn *ConnectionMock) Publish(subject string, payload []byte) error {
   107  	if conn.errorMock != nil {
   108  		return conn.errorMock
   109  	}
   110  
   111  	conn.m.Lock()
   112  	defer conn.m.Unlock()
   113  	conn.messageLast = &nats.Msg{
   114  		Subject: subject,
   115  		Data:    payload,
   116  	}
   117  	conn.queue <- conn.messageLast
   118  
   119  	return nil
   120  }
   121  
   122  // Subscribe subscribes to a topic
   123  func (conn *ConnectionMock) Subscribe(subject string, handler nats.MsgHandler) (*nats.Subscription, error) {
   124  	if conn.errorMock != nil {
   125  		return nil, conn.errorMock
   126  	}
   127  
   128  	conn.subscriptionAdd(subject, handler)
   129  
   130  	return &nats.Subscription{}, nil
   131  }
   132  
   133  // Request sends a new request
   134  func (conn *ConnectionMock) Request(subject string, payload []byte, timeout time.Duration) (*nats.Msg, error) {
   135  	if conn.errorMock != nil {
   136  		return nil, conn.errorMock
   137  	}
   138  
   139  	subjectReply := subject + "-reply"
   140  	responseCh := make(chan *nats.Msg)
   141  	conn.Subscribe(subjectReply, func(response *nats.Msg) {
   142  		responseCh <- response
   143  	})
   144  
   145  	conn.requestLast = &nats.Msg{
   146  		Subject: subject,
   147  		Reply:   subjectReply,
   148  		Data:    payload,
   149  	}
   150  	conn.queue <- conn.requestLast
   151  
   152  	select {
   153  	case response := <-responseCh:
   154  		return response, nil
   155  	case <-time.After(timeout):
   156  		return nil, errors.Errorf("request '%s' timeout", subject)
   157  	}
   158  }
   159  
   160  // RequestWithContext Request sends a new request with context
   161  func (conn *ConnectionMock) RequestWithContext(ctx context.Context, subject string, payload []byte) (*nats.Msg, error) {
   162  	if conn.errorMock != nil {
   163  		return nil, conn.errorMock
   164  	}
   165  
   166  	subjectReply := subject + "-reply"
   167  	responseCh := make(chan *nats.Msg)
   168  	conn.Subscribe(subjectReply, func(response *nats.Msg) {
   169  		responseCh <- response
   170  	})
   171  
   172  	conn.requestLast = &nats.Msg{
   173  		Subject: subject,
   174  		Reply:   subjectReply,
   175  		Data:    payload,
   176  	}
   177  	conn.queue <- conn.requestLast
   178  
   179  	select {
   180  	case response := <-responseCh:
   181  		return response, nil
   182  	case <-ctx.Done():
   183  		return nil, errors.Errorf("request '%s' timeout", subject)
   184  	}
   185  }
   186  
   187  // Open starts the connection
   188  func (conn *ConnectionMock) Open() error {
   189  	go conn.queueProcessing()
   190  	return nil
   191  }
   192  
   193  // Close destructs the connection
   194  func (conn *ConnectionMock) Close() {
   195  	conn.queueShutdown <- true
   196  }
   197  
   198  // Check checks the connection
   199  func (conn *ConnectionMock) Check() error {
   200  	return nil
   201  }
   202  
   203  // Servers returns list of currently connected servers
   204  func (conn *ConnectionMock) Servers() []string {
   205  	return []string{"mockhost"}
   206  }
   207  
   208  func (conn *ConnectionMock) subscriptionAdd(subject string, handler nats.MsgHandler) {
   209  	subscriptions, exist := conn.subscriptions[subject]
   210  	if exist {
   211  		subscriptions = append(subscriptions, handler)
   212  	} else {
   213  		conn.subscriptions[subject] = []nats.MsgHandler{handler}
   214  	}
   215  }
   216  
   217  func (conn *ConnectionMock) subscriptionsGet(subject string) (*[]nats.MsgHandler, bool) {
   218  	subscriptions, exist := conn.subscriptions[subject]
   219  	return &subscriptions, exist
   220  }
   221  
   222  func (conn *ConnectionMock) queueProcessing() {
   223  	for {
   224  		select {
   225  		case <-conn.queueShutdown:
   226  			break
   227  
   228  		case message := <-conn.queue:
   229  			if subscriptions, exist := conn.subscriptionsGet(message.Subject); exist {
   230  				for _, handler := range *subscriptions {
   231  					go handler(message)
   232  				}
   233  			}
   234  		}
   235  	}
   236  }