github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/orderer/common/channelparticipation/restapi_test.go (about)

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