Skip to content

Commit d4a819c

Browse files
Prevent removing of (Required,Default) fields in HTTP PUT. Fixes rs#174
1 parent 5003f4f commit d4a819c

File tree

2 files changed

+263
-19
lines changed

2 files changed

+263
-19
lines changed

rest/method_item_put_test.go

Lines changed: 260 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,8 @@ import (
1414
"github.com/rs/rest-layer/schema/query"
1515
)
1616

17-
func TestPutItem(t *testing.T) {
18-
now := time.Now()
19-
yesterday := now.Add(-24 * time.Hour)
20-
21-
sharedInit := func() *requestTestVars {
17+
func initForBasic(now, yesterday time.Time) func() *requestTestVars {
18+
return func() *requestTestVars {
2219
s1 := mem.NewHandler()
2320
s1.Insert(context.Background(), []*resource.Item{
2421
{ID: "1", ETag: "a", Updated: now, Payload: map[string]interface{}{"id": "1", "foo": "odd", "bar": "baz"}},
@@ -50,6 +47,78 @@ func TestPutItem(t *testing.T) {
5047
Storers: map[string]resource.Storer{"foo": s1, "foo.sub": s2},
5148
}
5249
}
50+
}
51+
52+
func initForDefaultField(now, yesterday time.Time) func() *requestTestVars {
53+
return func() *requestTestVars {
54+
s1 := mem.NewHandler()
55+
s1.Insert(context.Background(), []*resource.Item{
56+
{ID: "1", ETag: "a", Updated: now, Payload: map[string]interface{}{"id": "1", "foo": "odd"}},
57+
{ID: "2", ETag: "b", Updated: now, Payload: map[string]interface{}{"id": "2", "foo": "odd", "bar": "value"}},
58+
})
59+
idx := resource.NewIndex()
60+
idx.Bind("foo", schema.Schema{
61+
Fields: schema.Fields{
62+
"id": {Sortable: true, Filterable: true},
63+
"foo": {Filterable: true},
64+
"bar": {Filterable: true, Default: "default"},
65+
},
66+
}, s1, resource.DefaultConf)
67+
return &requestTestVars{
68+
Index: idx,
69+
Storers: map[string]resource.Storer{"foo": s1},
70+
}
71+
}
72+
}
73+
74+
func initForRequiredField(now, yesterday time.Time) func() *requestTestVars {
75+
return func() *requestTestVars {
76+
s1 := mem.NewHandler()
77+
s1.Insert(context.Background(), []*resource.Item{
78+
{ID: "1", ETag: "a", Updated: now, Payload: map[string]interface{}{"id": "1", "foo": "odd"}},
79+
{ID: "2", ETag: "b", Updated: now, Payload: map[string]interface{}{"id": "2", "foo": "odd", "bar": "original"}},
80+
})
81+
idx := resource.NewIndex()
82+
idx.Bind("foo", schema.Schema{
83+
Fields: schema.Fields{
84+
"id": {Sortable: true, Filterable: true},
85+
"foo": {Filterable: true},
86+
"bar": {Filterable: true, Required: true},
87+
},
88+
}, s1, resource.DefaultConf)
89+
return &requestTestVars{
90+
Index: idx,
91+
Storers: map[string]resource.Storer{"foo": s1},
92+
}
93+
}
94+
}
95+
96+
func initForRequiredDefaultField(now, yesterday time.Time) func() *requestTestVars {
97+
return func() *requestTestVars {
98+
s1 := mem.NewHandler()
99+
s1.Insert(context.Background(), []*resource.Item{
100+
{ID: "1", ETag: "a", Updated: now, Payload: map[string]interface{}{"id": "1", "foo": "odd"}},
101+
{ID: "2", ETag: "b", Updated: now, Payload: map[string]interface{}{"id": "2", "foo": "odd", "bar": "original"}},
102+
})
103+
idx := resource.NewIndex()
104+
idx.Bind("foo", schema.Schema{
105+
Fields: schema.Fields{
106+
"id": {Sortable: true, Filterable: true},
107+
"foo": {Filterable: true},
108+
"bar": {Filterable: true, Required: true, Default: "default"},
109+
},
110+
}, s1, resource.DefaultConf)
111+
return &requestTestVars{
112+
Index: idx,
113+
Storers: map[string]resource.Storer{"foo": s1},
114+
}
115+
}
116+
}
117+
118+
func TestPutItem(t *testing.T) {
119+
now := time.Now()
120+
yesterday := now.Add(-24 * time.Hour)
121+
53122
checkPayload := func(name string, id interface{}, payload map[string]interface{}) requestCheckerFunc {
54123
return func(t *testing.T, vars *requestTestVars) {
55124
var item *resource.Item
@@ -119,7 +188,7 @@ func TestPutItem(t *testing.T) {
119188
ExtraTest: checkPayload("foo", "1", map[string]interface{}{"id": "1", "foo": "bar"}),
120189
},
121190
`pathID:not-found,body:valid`: {
122-
Init: sharedInit,
191+
Init: initForBasic(now, yesterday),
123192
NewRequest: func() (*http.Request, error) {
124193
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
125194
return http.NewRequest("PUT", `/foo/66`, body)
@@ -129,7 +198,7 @@ func TestPutItem(t *testing.T) {
129198
ExtraTest: checkPayload("foo", "66", map[string]interface{}{"id": "66", "foo": "baz"}),
130199
},
131200
`pathID:found,body:invalid-json`: {
132-
Init: sharedInit,
201+
Init: initForBasic(now, yesterday),
133202
NewRequest: func() (*http.Request, error) {
134203
body := bytes.NewReader([]byte(`invalid`))
135204
return http.NewRequest("PUT", "/foo/2", body)
@@ -142,7 +211,7 @@ func TestPutItem(t *testing.T) {
142211
ExtraTest: checkPayload("foo", "2", map[string]interface{}{"id": "2", "foo": "even", "bar": "baz"}),
143212
},
144213
`pathID:found,body:invalid-field`: {
145-
Init: sharedInit,
214+
Init: initForBasic(now, yesterday),
146215
NewRequest: func() (*http.Request, error) {
147216
body := bytes.NewReader([]byte(`{"invalid": true}`))
148217
return http.NewRequest("PUT", "/foo/2", body)
@@ -158,7 +227,7 @@ func TestPutItem(t *testing.T) {
158227
ExtraTest: checkPayload("foo", "2", map[string]interface{}{"id": "2", "foo": "even", "bar": "baz"}),
159228
},
160229
`pathID:found,body:alter-id`: {
161-
Init: sharedInit,
230+
Init: initForBasic(now, yesterday),
162231
NewRequest: func() (*http.Request, error) {
163232
body := bytes.NewReader([]byte(`{"id": "3"}`))
164233
return http.NewRequest("PUT", "/foo/2", body)
@@ -171,7 +240,7 @@ func TestPutItem(t *testing.T) {
171240
ExtraTest: checkPayload("foo", "2", map[string]interface{}{"id": "2", "foo": "even", "bar": "baz"}),
172241
},
173242
`pathID:found,body:valid`: {
174-
Init: sharedInit,
243+
Init: initForBasic(now, yesterday),
175244
NewRequest: func() (*http.Request, error) {
176245
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
177246
return http.NewRequest("PUT", "/foo/2", body)
@@ -181,7 +250,7 @@ func TestPutItem(t *testing.T) {
181250
ExtraTest: checkPayload("foo", "2", map[string]interface{}{"id": "2", "foo": "baz"}),
182251
},
183252
`pathID:found,body:valid,fields:invalid`: {
184-
Init: sharedInit,
253+
Init: initForBasic(now, yesterday),
185254
NewRequest: func() (*http.Request, error) {
186255
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
187256
return http.NewRequest("PUT", "/foo/2?fields=invalid", body)
@@ -197,7 +266,7 @@ func TestPutItem(t *testing.T) {
197266
ExtraTest: checkPayload("foo", "2", map[string]interface{}{"id": "2", "foo": "even", "bar": "baz"}),
198267
},
199268
`pathID:found,body:valid,fields:valid`: {
200-
Init: sharedInit,
269+
Init: initForBasic(now, yesterday),
201270
NewRequest: func() (*http.Request, error) {
202271
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
203272
return http.NewRequest("PUT", "/foo/2?fields=foo", body)
@@ -207,7 +276,7 @@ func TestPutItem(t *testing.T) {
207276
ExtraTest: checkPayload("foo", "2", map[string]interface{}{"id": "2", "foo": "baz"}),
208277
},
209278
`pathID:found,body:valid,header["If-Match"]:not-matching`: {
210-
Init: sharedInit,
279+
Init: initForBasic(now, yesterday),
211280
NewRequest: func() (*http.Request, error) {
212281
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
213282
r, err := http.NewRequest("PUT", "/foo/2", body)
@@ -222,7 +291,7 @@ func TestPutItem(t *testing.T) {
222291
ExtraTest: checkPayload("foo", "2", map[string]interface{}{"id": "2", "foo": "even", "bar": "baz"}),
223292
},
224293
`pathID:found,body:valid,header["If-Match"]:matching`: {
225-
Init: sharedInit,
294+
Init: initForBasic(now, yesterday),
226295
NewRequest: func() (*http.Request, error) {
227296
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
228297
r, err := http.NewRequest("PUT", "/foo/2", body)
@@ -237,7 +306,7 @@ func TestPutItem(t *testing.T) {
237306
ExtraTest: checkPayload("foo", "2", map[string]interface{}{"id": "2", "foo": "baz"}),
238307
},
239308
`pathID:found,body:valid,header["If-Unmodified-Since"]:invalid`: {
240-
Init: sharedInit,
309+
Init: initForBasic(now, yesterday),
241310
NewRequest: func() (*http.Request, error) {
242311
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
243312
r, err := http.NewRequest("PUT", "/foo/1", body)
@@ -252,7 +321,7 @@ func TestPutItem(t *testing.T) {
252321
ExtraTest: checkPayload("foo", "1", map[string]interface{}{"id": "1", "foo": "odd", "bar": "baz"}),
253322
},
254323
`pathID:found,body:valid,header["If-Unmodified-Since"]:not-matching`: {
255-
Init: sharedInit,
324+
Init: initForBasic(now, yesterday),
256325
NewRequest: func() (*http.Request, error) {
257326
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
258327
r, err := http.NewRequest("PUT", "/foo/1", body)
@@ -267,7 +336,7 @@ func TestPutItem(t *testing.T) {
267336
ExtraTest: checkPayload("foo", "1", map[string]interface{}{"id": "1", "foo": "odd", "bar": "baz"}),
268337
},
269338
`parentPathID:found,pathID:found,body:alter-parent-id`: {
270-
Init: sharedInit,
339+
Init: initForBasic(now, yesterday),
271340
NewRequest: func() (*http.Request, error) {
272341
body := bytes.NewReader([]byte(`{"foo": "2"}`))
273342
r, err := http.NewRequest("PUT", "/foo/3/sub/1", body)
@@ -281,7 +350,7 @@ func TestPutItem(t *testing.T) {
281350
ExtraTest: checkPayload("foo.sub", "1", map[string]interface{}{"id": "1", "foo": "2"}),
282351
},
283352
`parentPathID:found,pathID:found,body:no-parent-id`: {
284-
Init: sharedInit,
353+
Init: initForBasic(now, yesterday),
285354
NewRequest: func() (*http.Request, error) {
286355
body := bytes.NewReader([]byte(`{}`))
287356
r, err := http.NewRequest("PUT", "/foo/3/sub/1", body)
@@ -294,6 +363,179 @@ func TestPutItem(t *testing.T) {
294363
ResponseBody: `{"id": "1", "foo": "3"}`,
295364
ExtraTest: checkPayload("foo.sub", "1", map[string]interface{}{"id": "1", "foo": "3"}),
296365
},
366+
367+
`pathID:not-found,body:valid,default:missing`: {
368+
Init: initForDefaultField(now, yesterday),
369+
NewRequest: func() (*http.Request, error) {
370+
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
371+
return http.NewRequest("PUT", "/foo/66", body)
372+
},
373+
ResponseCode: http.StatusCreated,
374+
ResponseBody: `{"id": "66", "foo": "baz", "bar": "default"}`,
375+
ExtraTest: checkPayload("foo", "66", map[string]interface{}{"id": "66", "foo": "baz", "bar": "default"}),
376+
},
377+
`pathID:not-found,body:valid,default:set`: {
378+
Init: initForDefaultField(now, yesterday),
379+
NewRequest: func() (*http.Request, error) {
380+
body := bytes.NewReader([]byte(`{"foo": "baz", "bar": "value"}`))
381+
return http.NewRequest("PUT", "/foo/66", body)
382+
},
383+
ResponseCode: http.StatusCreated,
384+
ResponseBody: `{"id": "66", "foo": "baz", "bar": "value"}`,
385+
ExtraTest: checkPayload("foo", "66", map[string]interface{}{"id": "66", "foo": "baz", "bar": "value"}),
386+
},
387+
`pathID:found,body:valid,default:missing`: {
388+
Init: initForDefaultField(now, yesterday),
389+
NewRequest: func() (*http.Request, error) {
390+
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
391+
return http.NewRequest("PUT", "/foo/1", body)
392+
},
393+
ResponseCode: http.StatusOK,
394+
ResponseBody: `{"id": "1", "foo": "baz"}`,
395+
ExtraTest: checkPayload("foo", "1", map[string]interface{}{"id": "1", "foo": "baz"}),
396+
},
397+
`pathID:found,body:valid,default:set`: {
398+
Init: initForDefaultField(now, yesterday),
399+
NewRequest: func() (*http.Request, error) {
400+
body := bytes.NewReader([]byte(`{"foo": "baz", "bar": "value"}`))
401+
return http.NewRequest("PUT", "/foo/1", body)
402+
},
403+
ResponseCode: http.StatusOK,
404+
ResponseBody: `{"id": "1", "foo": "baz", "bar": "value"}`,
405+
ExtraTest: checkPayload("foo", "1", map[string]interface{}{"id": "1", "foo": "baz", "bar": "value"}),
406+
},
407+
`pathID:found,body:valid,default:delete`: {
408+
Init: initForDefaultField(now, yesterday),
409+
NewRequest: func() (*http.Request, error) {
410+
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
411+
return http.NewRequest("PUT", "/foo/2", body)
412+
},
413+
ResponseCode: http.StatusOK,
414+
ResponseBody: `{"id": "2", "foo": "baz"}`,
415+
ExtraTest: checkPayload("foo", "2", map[string]interface{}{"id": "2", "foo": "baz"}),
416+
},
417+
418+
`pathID:not-found,body:valid,required:missing`: {
419+
Init: initForRequiredField(now, yesterday),
420+
NewRequest: func() (*http.Request, error) {
421+
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
422+
return http.NewRequest("PUT", "/foo/66", body)
423+
},
424+
ResponseCode: http.StatusUnprocessableEntity,
425+
ResponseBody: `{
426+
"code": 422,
427+
"message": "Document contains error(s)",
428+
"issues": {
429+
"bar": ["required"]
430+
}
431+
}`,
432+
},
433+
`pathID:not-found,body:valid,required:set`: {
434+
Init: initForRequiredField(now, yesterday),
435+
NewRequest: func() (*http.Request, error) {
436+
body := bytes.NewReader([]byte(`{"foo": "baz", "bar": "value"}`))
437+
return http.NewRequest("PUT", "/foo/1", body)
438+
},
439+
ResponseCode: http.StatusOK,
440+
ResponseBody: `{"id": "1", "foo": "baz", "bar": "value"}`,
441+
ExtraTest: checkPayload("foo", "1", map[string]interface{}{"id": "1", "foo": "baz", "bar": "value"}),
442+
},
443+
`pathID:found,body:valid,required:missing`: {
444+
Init: initForRequiredField(now, yesterday),
445+
NewRequest: func() (*http.Request, error) {
446+
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
447+
return http.NewRequest("PUT", "/foo/1", body)
448+
},
449+
ResponseCode: http.StatusUnprocessableEntity,
450+
ResponseBody: `{
451+
"code": 422,
452+
"message": "Document contains error(s)",
453+
"issues": {
454+
"bar": ["required"]
455+
}
456+
}`,
457+
},
458+
`pathID:found,body:valid,required:change`: {
459+
Init: initForRequiredField(now, yesterday),
460+
NewRequest: func() (*http.Request, error) {
461+
body := bytes.NewReader([]byte(`{"foo": "baz", "bar": "value1"}`))
462+
return http.NewRequest("PUT", "/foo/2", body)
463+
},
464+
ResponseCode: http.StatusOK,
465+
ResponseBody: `{"id": "2", "foo": "baz", "bar": "value1"}`,
466+
ExtraTest: checkPayload("foo", "2", map[string]interface{}{"id": "2", "foo": "baz", "bar": "value1"}),
467+
},
468+
`pathID:found,body:valid,required:delete`: {
469+
Init: initForRequiredField(now, yesterday),
470+
NewRequest: func() (*http.Request, error) {
471+
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
472+
return http.NewRequest("PUT", "/foo/2", body)
473+
},
474+
ResponseCode: http.StatusUnprocessableEntity,
475+
ResponseBody: `{
476+
"code": 422,
477+
"message": "Document contains error(s)",
478+
"issues": {
479+
"bar": ["required"]
480+
}
481+
}`,
482+
},
483+
484+
`pathID:not-found,body:valid,required-default:missing`: {
485+
Init: initForRequiredDefaultField(now, yesterday),
486+
NewRequest: func() (*http.Request, error) {
487+
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
488+
return http.NewRequest("PUT", "/foo/66", body)
489+
},
490+
ResponseCode: http.StatusCreated,
491+
ResponseBody: `{"id": "66", "foo": "baz", "bar": "default"}`,
492+
ExtraTest: checkPayload("foo", "66", map[string]interface{}{"id": "66", "foo": "baz", "bar": "default"}),
493+
},
494+
`pathID:not-found,body:valid,required-default:set`: {
495+
Init: initForRequiredDefaultField(now, yesterday),
496+
NewRequest: func() (*http.Request, error) {
497+
body := bytes.NewReader([]byte(`{"foo": "baz", "bar": "value"}`))
498+
return http.NewRequest("PUT", "/foo/1", body)
499+
},
500+
ResponseCode: http.StatusOK,
501+
ResponseBody: `{"id": "1", "foo": "baz", "bar": "value"}`,
502+
ExtraTest: checkPayload("foo", "1", map[string]interface{}{"id": "1", "foo": "baz", "bar": "value"}),
503+
},
504+
`pathID:found,body:valid,required-default:missing`: {
505+
Init: initForRequiredDefaultField(now, yesterday),
506+
NewRequest: func() (*http.Request, error) {
507+
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
508+
return http.NewRequest("PUT", "/foo/1", body)
509+
},
510+
ResponseCode: http.StatusUnprocessableEntity,
511+
ResponseBody: `{
512+
"code": 422,
513+
"message": "Document contains error(s)",
514+
"issues": {
515+
"bar": ["required"]
516+
}
517+
}`,
518+
},
519+
`pathID:found,body:valid,required-default:change`: {
520+
Init: initForRequiredDefaultField(now, yesterday),
521+
NewRequest: func() (*http.Request, error) {
522+
body := bytes.NewReader([]byte(`{"foo": "baz", "bar": "value"}`))
523+
return http.NewRequest("PUT", "/foo/2", body)
524+
},
525+
ResponseCode: http.StatusOK,
526+
ResponseBody: `{"id": "2", "foo": "baz", "bar": "value"}`,
527+
ExtraTest: checkPayload("foo", "2", map[string]interface{}{"id": "2", "foo": "baz", "bar": "value"}),
528+
},
529+
`pathID:found,body:valid,required-default:delete`: {
530+
Init: initForRequiredDefaultField(now, yesterday),
531+
NewRequest: func() (*http.Request, error) {
532+
body := bytes.NewReader([]byte(`{"foo": "baz"}`))
533+
return http.NewRequest("PUT", "/foo/2", body)
534+
},
535+
ResponseCode: http.StatusOK,
536+
ResponseBody: `{"id": "2", "foo": "baz", "bar": "default"}`,
537+
ExtraTest: checkPayload("foo", "2", map[string]interface{}{"id": "2", "foo": "baz", "bar": "default"}),
538+
},
297539
}
298540

299541
for n, tc := range tests {

0 commit comments

Comments
 (0)