decoder_test.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. package requestutils_test
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "net/http/httptest"
  8. "strings"
  9. "testing"
  10. "github.com/go-test/deep"
  11. "github.com/porter-dev/porter/api/server/requestutils"
  12. "github.com/porter-dev/porter/api/server/shared/apierrors"
  13. "github.com/stretchr/testify/assert"
  14. )
  15. type decoderJSONTest struct {
  16. description string
  17. decodeObj interface{}
  18. getBody func() io.ReadCloser
  19. expErr bool
  20. expErrString string
  21. expObj interface{}
  22. }
  23. type decoderTestObj struct {
  24. ID uint `json:"id"`
  25. Name string `json:"name"`
  26. }
  27. const (
  28. jsonFieldErrFmt = "Invalid type for body param %s: expected %s, got %s"
  29. jsonSyntaxErrFmt = "JSON syntax error at character %d"
  30. jsonGenericErr = "Could not parse JSON request"
  31. )
  32. func getSuccessfulJSONBody() io.ReadCloser {
  33. return ioutil.NopCloser(bytes.NewReader([]byte("{\"id\":2,\"name\":\"ok\"}")))
  34. }
  35. func getUnreadableJSONBody() io.ReadCloser {
  36. return ioutil.NopCloser(bytes.NewReader([]byte("{\"bad\":\"json\"")))
  37. }
  38. func getMalformedJSONBody() io.ReadCloser {
  39. return ioutil.NopCloser(bytes.NewReader([]byte("{\"bad\":json}")))
  40. }
  41. func getTypeErrorJSONBody() io.ReadCloser {
  42. return ioutil.NopCloser(bytes.NewReader([]byte("{\"id\":\"string\",\"name\":\"ok\"}")))
  43. }
  44. var decoderJSONTests = []decoderJSONTest{
  45. {
  46. description: "Should throw error on malformed JSON with failing offset chart",
  47. decodeObj: &decoderTestObj{},
  48. getBody: getMalformedJSONBody,
  49. expErr: true,
  50. expErrString: fmt.Sprintf(jsonSyntaxErrFmt, 8),
  51. },
  52. {
  53. description: "Should throw error on un-parsable JSON (curly bracket missing)",
  54. decodeObj: &decoderTestObj{},
  55. getBody: getUnreadableJSONBody,
  56. expErr: true,
  57. expErrString: fmt.Sprintf(jsonGenericErr),
  58. },
  59. {
  60. description: "Should throw descriptive type error",
  61. decodeObj: &decoderTestObj{},
  62. getBody: getTypeErrorJSONBody,
  63. expErr: true,
  64. expErrString: fmt.Sprintf(jsonFieldErrFmt, "id", "uint", "string"),
  65. },
  66. {
  67. description: "Should decode successfully",
  68. decodeObj: &decoderTestObj{},
  69. getBody: getSuccessfulJSONBody,
  70. expErr: false,
  71. expObj: &decoderTestObj{
  72. ID: 2,
  73. Name: "ok",
  74. },
  75. },
  76. }
  77. func TestJSONDecoding(t *testing.T) {
  78. assert := assert.New(t)
  79. decoder := requestutils.NewDefaultDecoder()
  80. for _, test := range decoderJSONTests {
  81. testReq := httptest.NewRequest("POST", "/test/post", test.getBody())
  82. err := decoder.Decode(test.decodeObj, testReq)
  83. assert.Equal(
  84. err != nil,
  85. test.expErr,
  86. "[ %s ]: expected error was %t, got %t",
  87. test.description,
  88. err != nil,
  89. test.expErr,
  90. )
  91. if err != nil && test.expErr {
  92. readableStr := err.Error()
  93. expReadableStr := test.expErrString
  94. assert.Equal(
  95. expReadableStr,
  96. readableStr,
  97. "[ %s ]: readable string not equal",
  98. test.description,
  99. )
  100. // check that external and internal errors are returned as well
  101. assert.Equal(
  102. 400,
  103. err.GetStatusCode(),
  104. "[ %s ]: status code not equal",
  105. test.description,
  106. )
  107. } else if !test.expErr {
  108. if diff := deep.Equal(test.expObj, test.decodeObj); diff != nil {
  109. t.Errorf("request object not equal:")
  110. t.Error(diff)
  111. }
  112. }
  113. }
  114. }
  115. type decoderSchemaTest struct {
  116. description string
  117. decodeObj interface{}
  118. queryStr string
  119. expErr bool
  120. expErrString string
  121. expObj interface{}
  122. }
  123. type decoderSchemaTestObj struct {
  124. ClusterID uint `schema:"cluster_id,required"`
  125. Storage string `schema:"storage"`
  126. }
  127. const (
  128. invalidSchemaTypeErrFmt = "Invalid type for query param %s: expected %s"
  129. emptySchemaErrFmt = "Query param %s cannot be empty"
  130. unknownQueryErrFmt = "Unknown query param %s"
  131. )
  132. var decoderSchemaTests = []decoderSchemaTest{
  133. {
  134. description: "Should throw error with malformed type",
  135. decodeObj: &decoderSchemaTestObj{},
  136. queryStr: "cluster_id=notid",
  137. expErr: true,
  138. expErrString: fmt.Sprintf(invalidSchemaTypeErrFmt, "cluster_id", "uint"),
  139. },
  140. {
  141. description: "Should throw error if param is empty",
  142. decodeObj: &decoderSchemaTestObj{},
  143. queryStr: "",
  144. expErr: true,
  145. expErrString: fmt.Sprintf(emptySchemaErrFmt, "cluster_id"),
  146. },
  147. {
  148. description: "Should throw error if query param is unknown",
  149. decodeObj: &decoderSchemaTestObj{},
  150. queryStr: "unknown=yes&cluster_id=2",
  151. expErr: true,
  152. expErrString: fmt.Sprintf(unknownQueryErrFmt, "unknown"),
  153. },
  154. {
  155. description: "Should throw multiple errors",
  156. decodeObj: &decoderSchemaTestObj{},
  157. queryStr: "unknown=yes&cluster_id=notid",
  158. expErr: true,
  159. expErrString: strings.Join([]string{
  160. fmt.Sprintf(unknownQueryErrFmt, "unknown"),
  161. fmt.Sprintf(invalidSchemaTypeErrFmt, "cluster_id", "uint"),
  162. }, ","),
  163. },
  164. {
  165. description: "Should decode successfully",
  166. decodeObj: &decoderSchemaTestObj{},
  167. queryStr: "cluster_id=2&storage=secret",
  168. expErr: false,
  169. expObj: &decoderSchemaTestObj{
  170. ClusterID: 2,
  171. Storage: "secret",
  172. },
  173. },
  174. }
  175. func TestSchemaDecoding(t *testing.T) {
  176. assert := assert.New(t)
  177. decoder := requestutils.NewDefaultDecoder()
  178. for _, test := range decoderSchemaTests {
  179. testReq := httptest.NewRequest("POST", "/test/post?"+test.queryStr, nil)
  180. err := decoder.Decode(test.decodeObj, testReq)
  181. assert.Equal(
  182. err != nil,
  183. test.expErr,
  184. "[ %s ]: expected error was %t, got %t",
  185. test.description,
  186. err != nil,
  187. test.expErr,
  188. )
  189. if err != nil && test.expErr {
  190. readableStrArr := strings.Split(err.Error(), ",")
  191. expReadableStrArr := strings.Split(test.expErrString, ",")
  192. assert.ElementsMatch(
  193. expReadableStrArr,
  194. readableStrArr,
  195. "[ %s ]: readable string not equal",
  196. test.description,
  197. )
  198. // check that external and internal errors are returned as well
  199. assert.Equal(
  200. 400,
  201. err.GetStatusCode(),
  202. "[ %s ]: status code not equal",
  203. test.description,
  204. )
  205. } else if !test.expErr {
  206. if diff := deep.Equal(test.expObj, test.decodeObj); diff != nil {
  207. t.Errorf("request object not equal:")
  208. t.Error(diff)
  209. }
  210. }
  211. }
  212. }
  213. func TestDecodingNilParams(t *testing.T) {
  214. decoder := requestutils.NewDefaultDecoder()
  215. err := decoder.Decode(nil, nil)
  216. expErr := apierrors.NewErrInternal(fmt.Errorf("decode: request or request.URL cannot be nil"))
  217. // check that error type is of type apierrors.RequestError and that
  218. // message is correct
  219. assert.EqualError(t, err, expErr.Error(), "nil param error not internal server error")
  220. var expErrTarget apierrors.RequestError
  221. assert.ErrorAs(t, err, &expErrTarget)
  222. testReq := httptest.NewRequest("POST", "/test/post", nil)
  223. err = decoder.Decode(nil, testReq)
  224. expErr = apierrors.NewErrInternal(fmt.Errorf("schema: interface must be a pointer to struct"))
  225. // check that error type is of type apierrors.RequestError and that
  226. // message is correct
  227. assert.EqualError(t, err, expErr.Error(), "nil param error not internal server error")
  228. var expErrTarget2 apierrors.RequestError
  229. assert.ErrorAs(t, err, &expErrTarget2)
  230. }