File: exactly_one_of_spec.rb

package info (click to toggle)
ruby-grape 1.6.2-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 2,156 kB
  • sloc: ruby: 25,265; makefile: 7
file content (235 lines) | stat: -rw-r--r-- 6,663 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# frozen_string_literal: true

require 'spec_helper'

describe Grape::Validations::Validators::ExactlyOneOfValidator do
  let_it_be(:app) do
    Class.new(Grape::API) do
      rescue_from Grape::Exceptions::ValidationErrors do |e|
        error!(e.errors.transform_keys! { |key| key.join(',') }, 400)
      end

      params do
        optional :beer
        optional :wine
        optional :grapefruit
        exactly_one_of :beer, :wine, :grapefruit
      end
      post do
      end

      params do
        optional :beer
        optional :wine
        optional :grapefruit
        optional :other
        exactly_one_of :beer, :wine, :grapefruit
      end
      post 'mixed-params' do
      end

      params do
        optional :beer
        optional :wine
        optional :grapefruit
        exactly_one_of :beer, :wine, :grapefruit, message: 'you should choose one'
      end
      post '/custom-message' do
      end

      params do
        requires :item, type: Hash do
          optional :beer
          optional :wine
          optional :grapefruit
          exactly_one_of :beer, :wine, :grapefruit
        end
      end
      post '/nested-hash' do
      end

      params do
        optional :item, type: Hash do
          optional :beer
          optional :wine
          optional :grapefruit
          exactly_one_of :beer, :wine, :grapefruit
        end
      end
      post '/nested-optional-hash' do
      end

      params do
        requires :items, type: Array do
          optional :beer
          optional :wine
          optional :grapefruit
          exactly_one_of :beer, :wine, :grapefruit
        end
      end
      post '/nested-array' do
      end

      params do
        requires :items, type: Array do
          requires :nested_items, type: Array do
            optional :beer, :wine, :grapefruit, type: Grape::API::Boolean
            exactly_one_of :beer, :wine, :grapefruit
          end
        end
      end
      post '/deeply-nested-array' do
      end
    end
  end

  describe '#validate!' do
    subject(:validate) { post path, params }

    context 'when all params are present' do
      let(:path) { '/' }
      let(:params) { { beer: true, wine: true, grapefruit: true } }

      it 'returns a validation error' do
        validate
        expect(last_response.status).to eq 400
        expect(JSON.parse(last_response.body)).to eq(
          'beer,wine,grapefruit' => ['are mutually exclusive']
        )
      end

      context 'mixed with other params' do
        let(:path) { '/mixed-params' }
        let(:params) { { beer: true, wine: true, grapefruit: true, other: true } }

        it 'returns a validation error' do
          validate
          expect(last_response.status).to eq 400
          expect(JSON.parse(last_response.body)).to eq(
            'beer,wine,grapefruit' => ['are mutually exclusive']
          )
        end
      end
    end

    context 'when a subset of params are present' do
      let(:path) { '/' }
      let(:params) { { beer: true, grapefruit: true } }

      it 'returns a validation error' do
        validate
        expect(last_response.status).to eq 400
        expect(JSON.parse(last_response.body)).to eq(
          'beer,grapefruit' => ['are mutually exclusive']
        )
      end
    end

    context 'when custom message is specified' do
      let(:path) { '/custom-message' }
      let(:params) { { beer: true, wine: true } }

      it 'returns a validation error' do
        validate
        expect(last_response.status).to eq 400
        expect(JSON.parse(last_response.body)).to eq(
          'beer,wine' => ['you should choose one']
        )
      end
    end

    context 'when exacly one param is present' do
      let(:path) { '/' }
      let(:params) { { beer: true, somethingelse: true } }

      it 'does not return a validation error' do
        validate
        expect(last_response.status).to eq 201
      end
    end

    context 'when none of the params are present' do
      let(:path) { '/' }
      let(:params) { { somethingelse: true } }

      it 'returns a validation error' do
        validate
        expect(last_response.status).to eq 400
        expect(JSON.parse(last_response.body)).to eq(
          'beer,wine,grapefruit' => ['are missing, exactly one parameter must be provided']
        )
      end
    end

    context 'when params are nested inside required hash' do
      let(:path) { '/nested-hash' }
      let(:params) { { item: { beer: true, wine: true } } }

      it 'returns a validation error with full names of the params' do
        validate
        expect(last_response.status).to eq 400
        expect(JSON.parse(last_response.body)).to eq(
          'item[beer],item[wine]' => ['are mutually exclusive']
        )
      end
    end

    context 'when params are nested inside optional hash' do
      let(:path) { '/nested-optional-hash' }

      context 'when params are passed' do
        let(:params) { { item: { beer: true, wine: true } } }

        it 'returns a validation error with full names of the params' do
          validate
          expect(last_response.status).to eq 400
          expect(JSON.parse(last_response.body)).to eq(
            'item[beer],item[wine]' => ['are mutually exclusive']
          )
        end
      end

      context 'when params are empty' do
        let(:params) { { other: true } }

        it 'does not return a validation error' do
          validate
          expect(last_response.status).to eq 201
        end
      end
    end

    context 'when params are nested inside array' do
      let(:path) { '/nested-array' }
      let(:params) { { items: [{ beer: true, wine: true }, { wine: true, grapefruit: true }] } }

      it 'returns a validation error with full names of the params' do
        validate
        expect(last_response.status).to eq 400
        expect(JSON.parse(last_response.body)).to eq(
          'items[0][beer],items[0][wine]' => [
            'are mutually exclusive'
          ],
          'items[1][wine],items[1][grapefruit]' => [
            'are mutually exclusive'
          ]
        )
      end
    end

    context 'when params are deeply nested' do
      let(:path) { '/deeply-nested-array' }
      let(:params) { { items: [{ nested_items: [{ beer: true, wine: true }] }] } }

      it 'returns a validation error with full names of the params' do
        validate
        expect(last_response.status).to eq 400
        expect(JSON.parse(last_response.body)).to eq(
          'items[0][nested_items][0][beer],items[0][nested_items][0][wine]' => [
            'are mutually exclusive'
          ]
        )
      end
    end
  end
end