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 }