github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/orderer/common/channelparticipation/restapi_test.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package channelparticipation_test 8 9 import ( 10 "bytes" 11 "encoding/json" 12 "fmt" 13 "mime/multipart" 14 "net/http" 15 "net/http/httptest" 16 "os" 17 "path" 18 "testing" 19 20 "github.com/hyperledger/fabric-protos-go/common" 21 "github.com/osdi23p228/fabric/orderer/common/channelparticipation" 22 "github.com/osdi23p228/fabric/orderer/common/channelparticipation/mocks" 23 "github.com/osdi23p228/fabric/orderer/common/localconfig" 24 "github.com/osdi23p228/fabric/orderer/common/types" 25 "github.com/osdi23p228/fabric/protoutil" 26 "github.com/pkg/errors" 27 "github.com/stretchr/testify/assert" 28 "github.com/stretchr/testify/require" 29 ) 30 31 func TestNewHTTPHandler(t *testing.T) { 32 config := localconfig.ChannelParticipation{ 33 Enabled: false, 34 RemoveStorage: false, 35 } 36 h := channelparticipation.NewHTTPHandler(config, &mocks.ChannelManagement{}) 37 assert.NotNilf(t, h, "cannot create handler") 38 } 39 40 func TestHTTPHandler_ServeHTTP_Disabled(t *testing.T) { 41 config := localconfig.ChannelParticipation{Enabled: false, RemoveStorage: false} 42 _, h := setup(config, t) 43 44 resp := httptest.NewRecorder() 45 req := httptest.NewRequest("GET", channelparticipation.URLBaseV1, nil) 46 h.ServeHTTP(resp, req) 47 checkErrorResponse(t, http.StatusServiceUnavailable, "channel participation API is disabled", resp) 48 } 49 50 func TestHTTPHandler_ServeHTTP_InvalidMethods(t *testing.T) { 51 config := localconfig.ChannelParticipation{Enabled: true, RemoveStorage: false} 52 _, h := setup(config, t) 53 invalidMethods := []string{http.MethodConnect, http.MethodHead, http.MethodOptions, http.MethodPatch, http.MethodPut, http.MethodTrace} 54 55 t.Run("on /channels/ch-id", func(t *testing.T) { 56 for _, method := range invalidMethods { 57 resp := httptest.NewRecorder() 58 req := httptest.NewRequest(method, path.Join(channelparticipation.URLBaseV1Channels, "ch-id"), nil) 59 h.ServeHTTP(resp, req) 60 checkErrorResponse(t, http.StatusMethodNotAllowed, fmt.Sprintf("invalid request method: %s", method), resp) 61 assert.Equal(t, "GET, POST, DELETE", resp.Result().Header.Get("Allow"), "%s", method) 62 } 63 }) 64 65 t.Run("on /channels", func(t *testing.T) { 66 invalidMethodsExt := append(invalidMethods, http.MethodDelete, http.MethodPost) 67 for _, method := range invalidMethodsExt { 68 resp := httptest.NewRecorder() 69 req := httptest.NewRequest(method, channelparticipation.URLBaseV1Channels, nil) 70 h.ServeHTTP(resp, req) 71 checkErrorResponse(t, http.StatusMethodNotAllowed, fmt.Sprintf("invalid request method: %s", method), resp) 72 assert.Equal(t, "GET", resp.Result().Header.Get("Allow"), "%s", method) 73 } 74 }) 75 } 76 77 func TestHTTPHandler_ServeHTTP_ListErrors(t *testing.T) { 78 config := localconfig.ChannelParticipation{Enabled: true, RemoveStorage: false} 79 _, h := setup(config, t) 80 81 t.Run("bad base", func(t *testing.T) { 82 resp := httptest.NewRecorder() 83 req := httptest.NewRequest(http.MethodGet, "/oops", nil) 84 h.ServeHTTP(resp, req) 85 assert.Equal(t, http.StatusNotFound, resp.Result().StatusCode) 86 }) 87 88 t.Run("bad resource", func(t *testing.T) { 89 resp := httptest.NewRecorder() 90 req := httptest.NewRequest(http.MethodGet, channelparticipation.URLBaseV1+"oops", nil) 91 h.ServeHTTP(resp, req) 92 assert.Equal(t, http.StatusNotFound, resp.Result().StatusCode) 93 }) 94 95 t.Run("bad channel ID", func(t *testing.T) { 96 resp := httptest.NewRecorder() 97 req := httptest.NewRequest(http.MethodGet, channelparticipation.URLBaseV1Channels+"/no/slash", nil) 98 h.ServeHTTP(resp, req) 99 assert.Equal(t, http.StatusNotFound, resp.Result().StatusCode) 100 }) 101 102 t.Run("illegal character in channel ID", func(t *testing.T) { 103 resp := httptest.NewRecorder() 104 req := httptest.NewRequest(http.MethodGet, channelparticipation.URLBaseV1Channels+"/Oops", nil) 105 h.ServeHTTP(resp, req) 106 checkErrorResponse(t, http.StatusBadRequest, "invalid channel ID: 'Oops' contains illegal characters", resp) 107 }) 108 109 t.Run("bad Accept header", func(t *testing.T) { 110 resp := httptest.NewRecorder() 111 req := httptest.NewRequest(http.MethodGet, channelparticipation.URLBaseV1Channels+"/ok", nil) 112 req.Header.Set("Accept", "text/html") 113 h.ServeHTTP(resp, req) 114 checkErrorResponse(t, http.StatusNotAcceptable, "response Content-Type is application/json only", resp) 115 }) 116 } 117 118 func TestHTTPHandler_ServeHTTP_ListAll(t *testing.T) { 119 config := localconfig.ChannelParticipation{Enabled: true, RemoveStorage: false} 120 fakeManager, h := setup(config, t) 121 122 t.Run("two channels", func(t *testing.T) { 123 list := types.ChannelList{ 124 Channels: []types.ChannelInfoShort{ 125 {Name: "app-channel1", URL: ""}, 126 {Name: "app-channel2", URL: ""}, 127 }, 128 SystemChannel: &types.ChannelInfoShort{Name: "system-channel", URL: ""}, 129 } 130 fakeManager.ChannelListReturns(list) 131 resp := httptest.NewRecorder() 132 req := httptest.NewRequest(http.MethodGet, channelparticipation.URLBaseV1Channels, nil) 133 h.ServeHTTP(resp, req) 134 assert.Equal(t, http.StatusOK, resp.Result().StatusCode) 135 assert.Equal(t, "application/json", resp.Result().Header.Get("Content-Type")) 136 assert.Equal(t, "no-store", resp.Result().Header.Get("Cache-Control")) 137 138 listAll := &types.ChannelList{} 139 err := json.Unmarshal(resp.Body.Bytes(), listAll) 140 require.NoError(t, err, "cannot be unmarshaled") 141 assert.Equal(t, 2, len(listAll.Channels)) 142 assert.Equal(t, list.SystemChannel, listAll.SystemChannel) 143 m := make(map[string]bool) 144 for _, item := range listAll.Channels { 145 m[item.Name] = true 146 assert.Equal(t, channelparticipation.URLBaseV1Channels+"/"+item.Name, item.URL) 147 } 148 assert.True(t, m["app-channel1"]) 149 assert.True(t, m["app-channel2"]) 150 }) 151 152 t.Run("no channels, empty channels", func(t *testing.T) { 153 list := types.ChannelList{ 154 Channels: []types.ChannelInfoShort{}, 155 } 156 fakeManager.ChannelListReturns(list) 157 resp := httptest.NewRecorder() 158 req := httptest.NewRequest(http.MethodGet, channelparticipation.URLBaseV1Channels, nil) 159 h.ServeHTTP(resp, req) 160 assert.Equal(t, http.StatusOK, resp.Result().StatusCode) 161 assert.Equal(t, "application/json", resp.Result().Header.Get("Content-Type")) 162 assert.Equal(t, "no-store", resp.Result().Header.Get("Cache-Control")) 163 164 listAll := &types.ChannelList{} 165 err := json.Unmarshal(resp.Body.Bytes(), listAll) 166 require.NoError(t, err, "cannot be unmarshaled") 167 assert.Equal(t, 0, len(listAll.Channels)) 168 assert.NotNil(t, listAll.Channels) 169 assert.Nil(t, listAll.SystemChannel) 170 }) 171 172 t.Run("no channels, Accept ok", func(t *testing.T) { 173 list := types.ChannelList{} 174 fakeManager.ChannelListReturns(list) 175 176 for _, accept := range []string{"application/json", "application/*", "*/*"} { 177 resp := httptest.NewRecorder() 178 req := httptest.NewRequest(http.MethodGet, channelparticipation.URLBaseV1Channels, nil) 179 req.Header.Set("Accept", accept) 180 h.ServeHTTP(resp, req) 181 assert.Equal(t, http.StatusOK, resp.Result().StatusCode, "Accept: %s", accept) 182 assert.Equal(t, "application/json", resp.Result().Header.Get("Content-Type")) 183 assert.Equal(t, "no-store", resp.Result().Header.Get("Cache-Control")) 184 185 listAll := &types.ChannelList{} 186 err := json.Unmarshal(resp.Body.Bytes(), listAll) 187 require.NoError(t, err, "cannot be unmarshaled") 188 assert.Equal(t, 0, len(listAll.Channels)) 189 assert.Nil(t, listAll.Channels) 190 assert.Nil(t, listAll.SystemChannel) 191 } 192 }) 193 194 t.Run("redirect from base V1 URL", func(t *testing.T) { 195 resp := httptest.NewRecorder() 196 req := httptest.NewRequest(http.MethodGet, channelparticipation.URLBaseV1, nil) 197 h.ServeHTTP(resp, req) 198 assert.Equal(t, http.StatusFound, resp.Result().StatusCode) 199 assert.Equal(t, channelparticipation.URLBaseV1Channels, resp.Result().Header.Get("Location")) 200 }) 201 } 202 203 func TestHTTPHandler_ServeHTTP_ListSingle(t *testing.T) { 204 config := localconfig.ChannelParticipation{Enabled: true, RemoveStorage: false} 205 fakeManager, h := setup(config, t) 206 require.NotNilf(t, h, "cannot create handler") 207 208 t.Run("channel exists", func(t *testing.T) { 209 info := types.ChannelInfo{ 210 Name: "app-channel", 211 URL: channelparticipation.URLBaseV1Channels + "/app-channel", 212 ClusterRelation: "member", 213 Status: "active", 214 Height: 3, 215 } 216 217 fakeManager.ChannelInfoReturns(info, nil) 218 resp := httptest.NewRecorder() 219 req := httptest.NewRequest(http.MethodGet, channelparticipation.URLBaseV1Channels+"/app-channel", nil) 220 h.ServeHTTP(resp, req) 221 assert.Equal(t, http.StatusOK, resp.Result().StatusCode) 222 assert.Equal(t, "application/json", resp.Result().Header.Get("Content-Type")) 223 assert.Equal(t, "no-store", resp.Result().Header.Get("Cache-Control")) 224 225 infoResp := types.ChannelInfo{} 226 err := json.Unmarshal(resp.Body.Bytes(), &infoResp) 227 require.NoError(t, err, "cannot be unmarshaled") 228 assert.Equal(t, info, infoResp) 229 }) 230 231 t.Run("channel does not exists", func(t *testing.T) { 232 fakeManager.ChannelInfoReturns(types.ChannelInfo{}, errors.New("not found")) 233 resp := httptest.NewRecorder() 234 req := httptest.NewRequest(http.MethodGet, channelparticipation.URLBaseV1Channels+"/app-channel", nil) 235 h.ServeHTTP(resp, req) 236 checkErrorResponse(t, http.StatusNotFound, "not found", resp) 237 }) 238 } 239 240 func TestHTTPHandler_ServeHTTP_Join(t *testing.T) { 241 config := localconfig.ChannelParticipation{Enabled: true, RemoveStorage: false} 242 243 t.Run("created ok", func(t *testing.T) { 244 fakeManager, h := setup(config, t) 245 info := types.ChannelInfo{ 246 Name: "app-channel", 247 URL: channelparticipation.URLBaseV1Channels + "/app-channel", 248 ClusterRelation: "member", 249 Status: "active", 250 Height: 1, 251 } 252 fakeManager.JoinChannelReturns(info, nil) 253 254 resp := httptest.NewRecorder() 255 req := genJoinRequestFormData(t, validBlockBytes("ch-id")) 256 h.ServeHTTP(resp, req) 257 assert.Equal(t, http.StatusCreated, resp.Result().StatusCode) 258 assert.Equal(t, "application/json", resp.Result().Header.Get("Content-Type")) 259 260 infoResp := types.ChannelInfo{} 261 err := json.Unmarshal(resp.Body.Bytes(), &infoResp) 262 require.NoError(t, err, "cannot be unmarshaled") 263 assert.Equal(t, info, infoResp) 264 }) 265 266 t.Run("Error: System Channel Exists", func(t *testing.T) { 267 fakeManager, h := setup(config, t) 268 fakeManager.JoinChannelReturns(types.ChannelInfo{}, types.ErrSystemChannelExists) 269 resp := httptest.NewRecorder() 270 req := genJoinRequestFormData(t, validBlockBytes("ch-id")) 271 h.ServeHTTP(resp, req) 272 checkErrorResponse(t, http.StatusMethodNotAllowed, "cannot join: system channel exists", resp) 273 assert.Equal(t, "GET", resp.Result().Header.Get("Allow")) 274 }) 275 276 t.Run("Error: Channel Exists", func(t *testing.T) { 277 fakeManager, h := setup(config, t) 278 fakeManager.JoinChannelReturns(types.ChannelInfo{}, types.ErrChannelAlreadyExists) 279 resp := httptest.NewRecorder() 280 req := genJoinRequestFormData(t, validBlockBytes("ch-id")) 281 h.ServeHTTP(resp, req) 282 checkErrorResponse(t, http.StatusMethodNotAllowed, "cannot join: channel already exists", resp) 283 assert.Equal(t, "GET, DELETE", resp.Result().Header.Get("Allow")) 284 }) 285 286 t.Run("Error: App Channels Exist", func(t *testing.T) { 287 fakeManager, h := setup(config, t) 288 fakeManager.JoinChannelReturns(types.ChannelInfo{}, types.ErrAppChannelsAlreadyExists) 289 resp := httptest.NewRecorder() 290 req := genJoinRequestFormData(t, validBlockBytes("ch-id")) 291 h.ServeHTTP(resp, req) 292 checkErrorResponse(t, http.StatusForbidden, "cannot join: application channels already exist", resp) 293 }) 294 295 t.Run("bad body - not a block", func(t *testing.T) { 296 _, h := setup(config, t) 297 resp := httptest.NewRecorder() 298 req := genJoinRequestFormData(t, []byte{1, 2, 3, 4}) 299 h.ServeHTTP(resp, req) 300 checkErrorResponse(t, http.StatusBadRequest, "cannot unmarshal file part config-block into a block: proto: common.Block: illegal tag 0 (wire type 1)", resp) 301 }) 302 303 t.Run("bad body - invalid join block", func(t *testing.T) { 304 _, h := setup(config, t) 305 resp := httptest.NewRecorder() 306 req := genJoinRequestFormData(t, []byte{}) 307 h.ServeHTTP(resp, req) 308 checkErrorResponse(t, http.StatusBadRequest, "invalid join block: block is not a config block", resp) 309 }) 310 311 t.Run("content type mismatch", func(t *testing.T) { 312 _, h := setup(config, t) 313 resp := httptest.NewRecorder() 314 req := httptest.NewRequest(http.MethodPost, path.Join(channelparticipation.URLBaseV1Channels, "ch-id"), nil) 315 req.Header.Set("Content-Type", "text/plain") 316 h.ServeHTTP(resp, req) 317 checkErrorResponse(t, http.StatusBadRequest, "unsupported Content-Type: [text/plain]", resp) 318 }) 319 320 t.Run("bad channel-id", func(t *testing.T) { 321 _, h := setup(config, t) 322 resp := httptest.NewRecorder() 323 req := httptest.NewRequest(http.MethodPost, path.Join(channelparticipation.URLBaseV1Channels, "ch-ID"), nil) 324 req.Header.Set("Content-Type", "multipart/form-data") 325 h.ServeHTTP(resp, req) 326 checkErrorResponse(t, http.StatusBadRequest, "invalid channel ID: 'ch-ID' contains illegal characters", resp) 327 }) 328 329 t.Run("form-data: bad form - no boundary", func(t *testing.T) { 330 _, h := setup(config, t) 331 resp := httptest.NewRecorder() 332 333 joinBody := new(bytes.Buffer) 334 writer := multipart.NewWriter(joinBody) 335 part, err := writer.CreateFormFile(channelparticipation.FormDataConfigBlockKey, "join-config.block") 336 require.NoError(t, err) 337 part.Write([]byte{}) 338 err = writer.Close() 339 require.NoError(t, err) 340 341 req := httptest.NewRequest(http.MethodPost, path.Join(channelparticipation.URLBaseV1Channels, "ch-id"), joinBody) 342 req.Header.Set("Content-Type", "multipart/form-data") //missing boundary 343 344 h.ServeHTTP(resp, req) 345 checkErrorResponse(t, http.StatusBadRequest, "cannot read form from request body: multipart: boundary is empty", resp) 346 }) 347 348 t.Run("form-data: bad form - no key", func(t *testing.T) { 349 _, h := setup(config, t) 350 resp := httptest.NewRecorder() 351 352 joinBody := new(bytes.Buffer) 353 writer := multipart.NewWriter(joinBody) 354 part, err := writer.CreateFormFile("bad-key", "join-config.block") 355 require.NoError(t, err) 356 part.Write([]byte{}) 357 err = writer.Close() 358 require.NoError(t, err) 359 360 req := httptest.NewRequest(http.MethodPost, path.Join(channelparticipation.URLBaseV1Channels, "ch-id"), joinBody) 361 req.Header.Set("Content-Type", writer.FormDataContentType()) 362 363 h.ServeHTTP(resp, req) 364 checkErrorResponse(t, http.StatusBadRequest, "form does not contains part key: config-block", resp) 365 }) 366 367 t.Run("form-data: bad form - too many parts", func(t *testing.T) { 368 _, h := setup(config, t) 369 resp := httptest.NewRecorder() 370 371 joinBody := new(bytes.Buffer) 372 writer := multipart.NewWriter(joinBody) 373 part, err := writer.CreateFormFile(channelparticipation.FormDataConfigBlockKey, "join-config.block") 374 require.NoError(t, err) 375 part.Write([]byte{}) 376 part, err = writer.CreateFormField("not-wanted") 377 require.NoError(t, err) 378 part.Write([]byte("something")) 379 err = writer.Close() 380 require.NoError(t, err) 381 382 req := httptest.NewRequest(http.MethodPost, path.Join(channelparticipation.URLBaseV1Channels, "ch-id"), joinBody) 383 req.Header.Set("Content-Type", writer.FormDataContentType()) 384 385 h.ServeHTTP(resp, req) 386 checkErrorResponse(t, http.StatusBadRequest, "form contains too many parts", resp) 387 }) 388 } 389 390 func TestHTTPHandler_ServeHTTP_Remove(t *testing.T) { 391 config := localconfig.ChannelParticipation{Enabled: true, RemoveStorage: false} 392 fakeManager, h := setup(config, t) 393 394 type testDef struct { 395 name string 396 channel string 397 query string 398 fakeReturns error 399 expectedCode int 400 expectedErr error 401 } 402 403 testCases := []testDef{ 404 { 405 name: "success - no query", 406 channel: "my-channel", 407 query: "", 408 fakeReturns: nil, 409 expectedCode: http.StatusNoContent, 410 expectedErr: nil, 411 }, 412 { 413 name: "success - query - false", 414 channel: "my-channel", 415 query: channelparticipation.RemoveStorageQueryKey + "=false", 416 fakeReturns: nil, 417 expectedCode: http.StatusNoContent, 418 expectedErr: nil, 419 }, 420 { 421 name: "success - query - true", 422 channel: "my-channel", 423 query: channelparticipation.RemoveStorageQueryKey + "=true", 424 fakeReturns: nil, 425 expectedCode: http.StatusNoContent, 426 expectedErr: nil, 427 }, 428 { 429 name: "bad channel ID", 430 channel: "My-Channel", 431 query: "", 432 fakeReturns: nil, 433 expectedCode: http.StatusBadRequest, 434 expectedErr: errors.New("invalid channel ID: 'My-Channel' contains illegal characters"), 435 }, 436 { 437 name: "channel does not exist", 438 channel: "my-channel", 439 query: "", 440 fakeReturns: types.ErrChannelNotExist, 441 expectedCode: http.StatusNotFound, 442 expectedErr: errors.Wrap(types.ErrChannelNotExist, "cannot remove"), 443 }, 444 { 445 name: "bad query - invalid key", 446 channel: "my-channel", 447 query: "bogus=false", 448 fakeReturns: nil, 449 expectedCode: http.StatusBadRequest, 450 expectedErr: errors.New("cannot remove: invalid query key"), 451 }, 452 { 453 name: "bad query - too many keys", 454 channel: "my-channel", 455 query: "bogus=false&stupid=true", 456 fakeReturns: nil, 457 expectedCode: http.StatusBadRequest, 458 expectedErr: errors.New("cannot remove: too many query keys"), 459 }, 460 { 461 name: "bad query - value", 462 channel: "my-channel", 463 query: channelparticipation.RemoveStorageQueryKey + "=10", 464 fakeReturns: nil, 465 expectedCode: http.StatusBadRequest, 466 expectedErr: errors.New("cannot remove: invalid query parameter: strconv.ParseBool: parsing \"10\": invalid syntax"), 467 }, 468 { 469 name: "bad query - too many parameters", 470 channel: "my-channel", 471 query: channelparticipation.RemoveStorageQueryKey + "=true&" + channelparticipation.RemoveStorageQueryKey + "=false", 472 fakeReturns: nil, 473 expectedCode: http.StatusBadRequest, 474 expectedErr: errors.New("cannot remove: too many query parameters"), 475 }, 476 { 477 name: "some other error", 478 channel: "my-channel", 479 query: "", 480 fakeReturns: os.ErrInvalid, 481 expectedCode: http.StatusBadRequest, 482 expectedErr: errors.Wrap(os.ErrInvalid, "cannot remove"), 483 }, 484 } 485 486 for _, testCase := range testCases { 487 t.Run(testCase.name, func(t *testing.T) { 488 fakeManager.RemoveChannelReturns(testCase.fakeReturns) 489 resp := httptest.NewRecorder() 490 target := path.Join(channelparticipation.URLBaseV1Channels, testCase.channel) + "?" + testCase.query 491 req := httptest.NewRequest(http.MethodDelete, target, nil) 492 h.ServeHTTP(resp, req) 493 494 if testCase.expectedErr == nil { 495 assert.Equal(t, testCase.expectedCode, resp.Result().StatusCode) 496 assert.Equal(t, 0, resp.Body.Len(), "empty body") 497 } else { 498 checkErrorResponse(t, testCase.expectedCode, testCase.expectedErr.Error(), resp) 499 } 500 }) 501 } 502 503 t.Run("Error: System Channel Exists", func(t *testing.T) { 504 fakeManager, h := setup(config, t) 505 fakeManager.RemoveChannelReturns(types.ErrSystemChannelExists) 506 resp := httptest.NewRecorder() 507 req := httptest.NewRequest(http.MethodDelete, path.Join(channelparticipation.URLBaseV1Channels, "my-channel"), nil) 508 h.ServeHTTP(resp, req) 509 checkErrorResponse(t, http.StatusMethodNotAllowed, "cannot remove: system channel exists", resp) 510 assert.Equal(t, "GET", resp.Result().Header.Get("Allow")) 511 }) 512 } 513 514 func setup(config localconfig.ChannelParticipation, t *testing.T) (*mocks.ChannelManagement, *channelparticipation.HTTPHandler) { 515 fakeManager := &mocks.ChannelManagement{} 516 h := channelparticipation.NewHTTPHandler(config, fakeManager) 517 require.NotNilf(t, h, "cannot create handler") 518 return fakeManager, h 519 } 520 521 func checkErrorResponse(t *testing.T, expectedCode int, expectedErrMsg string, resp *httptest.ResponseRecorder) { 522 assert.Equal(t, expectedCode, resp.Result().StatusCode) 523 524 headerArray, headerOK := resp.Result().Header["Content-Type"] 525 assert.True(t, headerOK) 526 require.Len(t, headerArray, 1) 527 assert.Equal(t, "application/json", headerArray[0]) 528 529 decoder := json.NewDecoder(resp.Body) 530 respErr := &types.ErrorResponse{} 531 err := decoder.Decode(respErr) 532 assert.NoError(t, err, "body: %s", resp.Body.String()) 533 assert.Equal(t, expectedErrMsg, respErr.Error) 534 } 535 536 func genJoinRequestFormData(t *testing.T, blockBytes []byte) *http.Request { 537 joinBody := new(bytes.Buffer) 538 writer := multipart.NewWriter(joinBody) 539 part, err := writer.CreateFormFile(channelparticipation.FormDataConfigBlockKey, "join-config.block") 540 require.NoError(t, err) 541 part.Write(blockBytes) 542 err = writer.Close() 543 require.NoError(t, err) 544 545 req := httptest.NewRequest(http.MethodPost, path.Join(channelparticipation.URLBaseV1Channels, "ch-id"), joinBody) 546 req.Header.Set("Content-Type", writer.FormDataContentType()) 547 548 return req 549 } 550 551 func validBlockBytes(channelID string) []byte { 552 blockBytes := protoutil.MarshalOrPanic(blockWithGroups(map[string]*common.ConfigGroup{ 553 "Application": {}, 554 }, channelID)) 555 return blockBytes 556 }