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  }