Skip to content

Commit 2647e2c

Browse files
committed
Increase test coverage around nexted arrays/hashes with optional components
This adds a number of tests around edge cases and different structures that could result in the previous validation incorrectly reporting missing data as a result of an optional element not being present.
1 parent 7637b27 commit 2647e2c

File tree

2 files changed

+173
-18
lines changed

2 files changed

+173
-18
lines changed

lib/grape/validations/single_attribute_iterator.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ def yield_attributes(val, attrs)
1717
# are the parameter parsing stage as they are required to ensure
1818
# the correct indexing is maintained
1919
def skip?(val)
20-
# return false
2120
val == Grape::DSL::Parameters::EmptyOptionalValue
2221
end
2322

spec/grape/validations_spec.rb

Lines changed: 173 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -884,32 +884,188 @@ def validate_param!(attr_name, params)
884884
expect(declared_params).to eq([items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]])
885885
end
886886

887-
it "does not report errors when required array inside missing optional array" do
888-
subject.params do
889-
requires :orders, type: Array do
890-
requires :id, type: Integer
891-
optional :drugs, type: Array do
892-
requires :batches, type: Array do
887+
context <<~DESC do
888+
Issue occurs whenever:
889+
* param structure with at least three levels
890+
* 1st level item is a required Array that has >1 entry with an optional item present and >1 entry with an optional item missing
891+
* 2nd level is an optional Array or Hash
892+
* 3rd level is a required item (can be any type)
893+
* additional levels do not effect the issue from occuring
894+
DESC
895+
896+
it "example based off actual real world use case" do
897+
subject.params do
898+
requires :orders, type: Array do
899+
requires :id, type: Integer
900+
optional :drugs, type: Array do
901+
requires :batches, type: Array do
902+
requires :batch_no, type: String
903+
end
904+
end
905+
end
906+
end
907+
908+
subject.get '/validate_required_arrays_under_optional_arrays' do
909+
'validate_required_arrays_under_optional_arrays works!'
910+
end
911+
912+
data = {
913+
orders: [
914+
{ id: 77, drugs: [{batches: [{batch_no: "A1234567"}]}]},
915+
{ id: 70 }
916+
]
917+
}
918+
919+
get '/validate_required_arrays_under_optional_arrays', data
920+
expect(last_response.body).to eq("validate_required_arrays_under_optional_arrays works!")
921+
expect(last_response.status).to eq(200)
922+
end
923+
924+
it "simplest example using Arry -> Array -> Hash -> String" do
925+
subject.params do
926+
requires :orders, type: Array do
927+
requires :id, type: Integer
928+
optional :drugs, type: Array do
929+
requires :batch_no, type: String
930+
end
931+
end
932+
end
933+
934+
subject.get '/validate_required_arrays_under_optional_arrays' do
935+
'validate_required_arrays_under_optional_arrays works!'
936+
end
937+
938+
data = {
939+
orders: [
940+
{ id: 77, drugs: [{batch_no: "A1234567"}]},
941+
{ id: 70 }
942+
]
943+
}
944+
945+
get '/validate_required_arrays_under_optional_arrays', data
946+
expect(last_response.body).to eq("validate_required_arrays_under_optional_arrays works!")
947+
expect(last_response.status).to eq(200)
948+
end
949+
950+
it "simplest example using Arry -> Hash -> String" do
951+
subject.params do
952+
requires :orders, type: Array do
953+
requires :id, type: Integer
954+
optional :drugs, type: Hash do
893955
requires :batch_no, type: String
894956
end
895957
end
896958
end
959+
960+
subject.get '/validate_required_arrays_under_optional_arrays' do
961+
'validate_required_arrays_under_optional_arrays works!'
962+
end
963+
964+
data = {
965+
orders: [
966+
{ id: 77, drugs: {batch_no: "A1234567"}},
967+
{ id: 70 }
968+
]
969+
}
970+
971+
get '/validate_required_arrays_under_optional_arrays', data
972+
expect(last_response.body).to eq("validate_required_arrays_under_optional_arrays works!")
973+
expect(last_response.status).to eq(200)
897974
end
898975

899-
subject.get '/validate_required_arrays_under_optional_arrays' do
900-
'validate_required_arrays_under_optional_arrays works!'
976+
it "correctly indexes invalida data" do
977+
subject.params do
978+
requires :orders, type: Array do
979+
requires :id, type: Integer
980+
optional :drugs, type: Array do
981+
requires :batch_no, type: String
982+
requires :quantity, type: Integer
983+
end
984+
end
985+
end
986+
987+
subject.get '/correctly_indexes' do
988+
'correctly_indexes works!'
989+
end
990+
991+
data = {
992+
orders: [
993+
{ id: 70 },
994+
{ id: 77, drugs: [{batch_no: "A1234567", quantity: 12}, {batch_no: "B222222"}]}
995+
]
996+
}
997+
998+
get '/correctly_indexes', data
999+
expect(last_response.body).to eq("orders[1][drugs][1][quantity] is missing")
1000+
expect(last_response.status).to eq(400)
9011001
end
9021002

903-
data = {
904-
orders: [
905-
{ id: 77, drugs: [{batches: [{batch_no: "A1234567"}]}]},
906-
{ id: 70 }
907-
]
908-
}
1003+
context "multiple levels of optional and requires settings" do
1004+
before do
1005+
subject.params do
1006+
requires :top, type: Array do
1007+
requires :top_id, type: Integer, allow_blank: false
1008+
optional :middle_1, type: Array do
1009+
requires :middle_1_id, type: Integer, allow_blank: false
1010+
optional :middle_2, type: Array do
1011+
requires :middle_2_id, type: String, allow_blank: false
1012+
optional :bottom, type: Array do
1013+
requires :bottom_id, type: Integer, allow_blank: false
1014+
end
1015+
end
1016+
end
1017+
end
1018+
end
9091019

910-
get '/validate_required_arrays_under_optional_arrays', data
911-
expect(last_response.body).to eq("validate_required_arrays_under_optional_arrays works!")
912-
expect(last_response.status).to eq(200)
1020+
subject.get '/multi_level' do
1021+
'multi_level works!'
1022+
end
1023+
end
1024+
1025+
it "with valid data" do
1026+
data_without_errors = {
1027+
top: [
1028+
{ top_id: 1, middle_1: [
1029+
{middle_1_id: 11}, {middle_1_id: 12, middle_2: [
1030+
{middle_2_id: 121}, {middle_2_id: 122, bottom: [{bottom_id: 1221}]}]}]},
1031+
{ top_id: 2, middle_1: [
1032+
{middle_1_id: 21}, {middle_1_id: 22, middle_2: [
1033+
{middle_2_id: 221}]}]},
1034+
{ top_id: 3, middle_1: [
1035+
{middle_1_id: 31}, {middle_1_id: 32}]},
1036+
{ top_id: 4 }
1037+
]
1038+
}
1039+
1040+
get '/multi_level', data_without_errors
1041+
expect(last_response.body).to eq("multi_level works!")
1042+
expect(last_response.status).to eq(200)
1043+
end
1044+
1045+
it "with invalid data" do
1046+
data = {
1047+
top: [
1048+
{ top_id: 1, middle_1: [
1049+
{middle_1_id: 11}, {middle_1_id: 12, middle_2: [
1050+
{middle_2_id: 121}, {middle_2_id: 122, bottom: [{bottom_id: nil}]}]}]},
1051+
{ top_id: 2, middle_1: [
1052+
{middle_1_id: 21}, {middle_1_id: 22, middle_2: [{middle_2_id: nil}]}]},
1053+
{ top_id: 3, middle_1: [
1054+
{middle_1_id: nil}, {middle_1_id: 32}]},
1055+
{ top_id: nil, missing_top_id: 4 }
1056+
]
1057+
}
1058+
# debugger
1059+
get '/multi_level', data
1060+
expect(last_response.body.split(", ")).to match_array([
1061+
"top[3][top_id] is empty",
1062+
"top[2][middle_1][0][middle_1_id] is empty",
1063+
"top[1][middle_1][1][middle_2][0][middle_2_id] is empty",
1064+
"top[0][middle_1][1][middle_2][1][bottom][0][bottom_id] is empty"
1065+
])
1066+
expect(last_response.status).to eq(400)
1067+
end
1068+
end
9131069
end
9141070
end
9151071

0 commit comments

Comments
 (0)