github.com/minio/console@v1.4.1/api/user_watch_test.go (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2021 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero 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 Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package api
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"fmt"
    23  	"net/http"
    24  	"net/url"
    25  	"testing"
    26  	"time"
    27  
    28  	mc "github.com/minio/mc/cmd"
    29  	"github.com/minio/mc/pkg/probe"
    30  	"github.com/stretchr/testify/assert"
    31  )
    32  
    33  // assigning mock at runtime instead of compile time
    34  var mcWatchMock func(ctx context.Context, options mc.WatchOptions) (*mc.WatchObject, *probe.Error)
    35  
    36  // implements mc.S3Client.Watch()
    37  func (c s3ClientMock) watch(ctx context.Context, options mc.WatchOptions) (*mc.WatchObject, *probe.Error) {
    38  	if options.Prefix == "file/" {
    39  		return mcWatchMock(ctx, options)
    40  	}
    41  	wo := &mc.WatchObject{
    42  		EventInfoChan: make(chan []mc.EventInfo),
    43  		ErrorChan:     make(chan *probe.Error),
    44  		DoneChan:      make(chan struct{}),
    45  	}
    46  	return wo, nil
    47  }
    48  
    49  func TestWatchOnContextDone(t *testing.T) {
    50  	assert := assert.New(t)
    51  	client := s3ClientMock{}
    52  	mockWSConn := mockConn{}
    53  	ctx, cancel := context.WithCancel(context.Background())
    54  	defer cancel()
    55  	testOptions := &watchOptions{}
    56  	testOptions.BucketName = "bucktest"
    57  	testOptions.Prefix = "file2/"
    58  	testOptions.Suffix = ".png"
    59  
    60  	// Test-0: Test closing a done channel
    61  	ctxWithTimeout, cancelFunction := context.WithTimeout(ctx, time.Duration(1)*time.Millisecond)
    62  	defer cancelFunction()
    63  	assert.Equal(startWatch(ctxWithTimeout, mockWSConn, client, testOptions), nil)
    64  }
    65  
    66  func TestWatch(t *testing.T) {
    67  	assert := assert.New(t)
    68  	client := s3ClientMock{}
    69  	mockWSConn := mockConn{}
    70  	ctx, cancel := context.WithCancel(context.Background())
    71  	defer cancel()
    72  	function := "startWatch()"
    73  	testStreamSize := 5
    74  	testReceiver := make(chan []mc.EventInfo, testStreamSize)
    75  	isClosed := false // testReceiver is closed?
    76  	textToReceive := "test message"
    77  	testOptions := &watchOptions{}
    78  	testOptions.BucketName = "bucktest"
    79  	testOptions.Prefix = "file/"
    80  	testOptions.Suffix = ".png"
    81  
    82  	// Test-1: Serve Watch with no errors until Watch finishes sending
    83  	// define mock function behavior
    84  	mcWatchMock = func(_ context.Context, _ mc.WatchOptions) (*mc.WatchObject, *probe.Error) {
    85  		wo := &mc.WatchObject{
    86  			EventInfoChan: make(chan []mc.EventInfo),
    87  			ErrorChan:     make(chan *probe.Error),
    88  			DoneChan:      make(chan struct{}),
    89  		}
    90  		// Only success, start a routine to start reading line by line.
    91  		go func(wo *mc.WatchObject) {
    92  			defer func() {
    93  				close(wo.EventInfoChan)
    94  				close(wo.ErrorChan)
    95  			}()
    96  
    97  			lines := make([]int, testStreamSize)
    98  			// mocking sending 5 lines of info
    99  			for range lines {
   100  				info := []mc.EventInfo{
   101  					{
   102  						UserAgent: textToReceive,
   103  					},
   104  				}
   105  				wo.Events() <- info
   106  			}
   107  		}(wo)
   108  		return wo, nil
   109  	}
   110  	writesCount := 1
   111  	// mock connection WriteMessage() no error
   112  	connWriteMessageMock = func(_ int, data []byte) error {
   113  		// emulate that receiver gets the message written
   114  		var t []mc.EventInfo
   115  		_ = json.Unmarshal(data, &t)
   116  		if writesCount == testStreamSize {
   117  			// for testing we need to close the receiver channel
   118  			if !isClosed {
   119  				close(testReceiver)
   120  				isClosed = true
   121  			}
   122  			return nil
   123  		}
   124  		testReceiver <- t
   125  		writesCount++
   126  		return nil
   127  	}
   128  	if err := startWatch(ctx, mockWSConn, client, testOptions); err != nil {
   129  		t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
   130  	}
   131  	// check that the TestReceiver got the same number of data from Console.
   132  	for i := range testReceiver {
   133  		for _, val := range i {
   134  			assert.Equal(textToReceive, val.UserAgent)
   135  		}
   136  	}
   137  
   138  	// Test-2: if error happens while writing, return error
   139  	connWriteMessageMock = func(_ int, _ []byte) error {
   140  		return fmt.Errorf("error on write")
   141  	}
   142  	if err := startWatch(ctx, mockWSConn, client, testOptions); assert.Error(err) {
   143  		assert.Equal("error on write", err.Error())
   144  	}
   145  
   146  	// Test-3: error happens on Watch, watch should stop
   147  	// and error shall be returned.
   148  	mcWatchMock = func(_ context.Context, _ mc.WatchOptions) (*mc.WatchObject, *probe.Error) {
   149  		wo := &mc.WatchObject{
   150  			EventInfoChan: make(chan []mc.EventInfo),
   151  			ErrorChan:     make(chan *probe.Error),
   152  			DoneChan:      make(chan struct{}),
   153  		}
   154  		// Only success, start a routine to start reading line by line.
   155  		go func(wo *mc.WatchObject) {
   156  			defer func() {
   157  				close(wo.EventInfoChan)
   158  				close(wo.ErrorChan)
   159  			}()
   160  			lines := make([]int, testStreamSize)
   161  			// mocking sending 5 lines of info
   162  			for range lines {
   163  				info := []mc.EventInfo{
   164  					{
   165  						UserAgent: textToReceive,
   166  					},
   167  				}
   168  				wo.Events() <- info
   169  			}
   170  			wo.Errors() <- &probe.Error{Cause: fmt.Errorf("error on watch")}
   171  		}(wo)
   172  		return wo, nil
   173  	}
   174  	connWriteMessageMock = func(_ int, _ []byte) error {
   175  		return nil
   176  	}
   177  	if err := startWatch(ctx, mockWSConn, client, testOptions); assert.Error(err) {
   178  		assert.Equal("error on watch", err.Error())
   179  	}
   180  
   181  	// Test-4: error happens on Watch, watch should stop
   182  	// and error shall be returned.
   183  	mcWatchMock = func(_ context.Context, _ mc.WatchOptions) (*mc.WatchObject, *probe.Error) {
   184  		return nil, &probe.Error{Cause: fmt.Errorf("error on watch")}
   185  	}
   186  	if err := startWatch(ctx, mockWSConn, client, testOptions); assert.Error(err) {
   187  		assert.Equal("error on watch", err.Error())
   188  	}
   189  
   190  	// Test-5: return nil on error on watch
   191  	mcWatchMock = func(_ context.Context, _ mc.WatchOptions) (*mc.WatchObject, *probe.Error) {
   192  		wo := &mc.WatchObject{
   193  			EventInfoChan: make(chan []mc.EventInfo),
   194  			ErrorChan:     make(chan *probe.Error),
   195  			DoneChan:      make(chan struct{}),
   196  		}
   197  		// Only success, start a routine to start reading line by line.
   198  		go func(wo *mc.WatchObject) {
   199  			defer func() {
   200  				close(wo.EventInfoChan)
   201  				close(wo.ErrorChan)
   202  			}()
   203  			lines := make([]int, testStreamSize)
   204  			// mocking sending 5 lines of info
   205  			for range lines {
   206  				info := []mc.EventInfo{
   207  					{
   208  						UserAgent: textToReceive,
   209  					},
   210  				}
   211  				wo.Events() <- info
   212  			}
   213  			wo.Events() <- nil
   214  			wo.Errors() <- nil
   215  		}(wo)
   216  		return wo, nil
   217  	}
   218  	if err := startWatch(ctx, mockWSConn, client, testOptions); err != nil {
   219  		t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
   220  	}
   221  	// check that the TestReceiver got the same number of data from Console.
   222  	for i := range testReceiver {
   223  		for _, val := range i {
   224  			assert.Equal(textToReceive, val.UserAgent)
   225  		}
   226  	}
   227  
   228  	// Test-6: getWatchOptionsFromReq return parameters from path
   229  	u, err := url.Parse("http://localhost/api/v1/watch/bucket1?prefix=&suffix=.jpg&events=put,get")
   230  	if err != nil {
   231  		t.Errorf("Failed on %s:, error occurred: %s", "url.Parse()", err.Error())
   232  	}
   233  	req := &http.Request{
   234  		URL: u,
   235  	}
   236  	opts, err := getWatchOptionsFromReq(req)
   237  	if assert.NoError(err) {
   238  		expectedOptions := watchOptions{
   239  			BucketName: "bucket1",
   240  		}
   241  		expectedOptions.Prefix = ""
   242  		expectedOptions.Suffix = ".jpg"
   243  		expectedOptions.Events = []string{"put", "get"}
   244  		assert.Equal(expectedOptions.BucketName, opts.BucketName)
   245  		assert.Equal(expectedOptions.Prefix, opts.Prefix)
   246  		assert.Equal(expectedOptions.Suffix, opts.Suffix)
   247  		assert.Equal(expectedOptions.Events, opts.Events)
   248  	}
   249  
   250  	// Test-7: getWatchOptionsFromReq return default events if not defined
   251  	u, err = url.Parse("http://localhost/api/v1/watch/bucket1?prefix=&suffix=.jpg&events=")
   252  	if err != nil {
   253  		t.Errorf("Failed on %s:, error occurred: %s", "url.Parse()", err.Error())
   254  	}
   255  	req = &http.Request{
   256  		URL: u,
   257  	}
   258  	opts, err = getWatchOptionsFromReq(req)
   259  	if assert.NoError(err) {
   260  		expectedOptions := watchOptions{
   261  			BucketName: "bucket1",
   262  		}
   263  		expectedOptions.Prefix = ""
   264  		expectedOptions.Suffix = ".jpg"
   265  		expectedOptions.Events = []string{"put", "get", "delete"}
   266  		assert.Equal(expectedOptions.BucketName, opts.BucketName)
   267  		assert.Equal(expectedOptions.Prefix, opts.Prefix)
   268  		assert.Equal(expectedOptions.Suffix, opts.Suffix)
   269  		assert.Equal(expectedOptions.Events, opts.Events)
   270  
   271  	}
   272  
   273  	// Test-8: getWatchOptionsFromReq return default events if not defined
   274  	u, err = url.Parse("http://localhost/api/v1/watch/bucket2?prefix=&suffix=")
   275  	if err != nil {
   276  		t.Errorf("Failed on %s:, error occurred: %s", "url.Parse()", err.Error())
   277  	}
   278  	req = &http.Request{
   279  		URL: u,
   280  	}
   281  	opts, err = getWatchOptionsFromReq(req)
   282  	if assert.NoError(err) {
   283  		expectedOptions := watchOptions{
   284  			BucketName: "bucket2",
   285  		}
   286  		expectedOptions.Events = []string{"put", "get", "delete"}
   287  		assert.Equal(expectedOptions.BucketName, opts.BucketName)
   288  		assert.Equal(expectedOptions.Prefix, opts.Prefix)
   289  		assert.Equal(expectedOptions.Suffix, opts.Suffix)
   290  		assert.Equal(expectedOptions.Events, opts.Events)
   291  	}
   292  
   293  	// Test-9: getWatchOptionsFromReq invalid url
   294  	u, _ = url.Parse("http://localhost/api/v1/wach/bucket2?prefix=&suffix=")
   295  	req = &http.Request{
   296  		URL: u,
   297  	}
   298  	_, err = getWatchOptionsFromReq(req)
   299  	assert.Error(err)
   300  }