1# frozen_string_literal: true
2
3RSpec.describe QA::Resource::Base do
4  include QA::Support::Helpers::StubEnv
5
6  let(:resource) { spy('resource', username: 'qa') }
7  let(:location) { 'http://location' }
8  let(:log_regex) { %r{==> Built a MyResource with username 'qa' via #{method} in [\d.\-e]+ seconds+} }
9
10  shared_context 'with fabrication context' do
11    subject do
12      Class.new(described_class) do
13        def self.name
14          'MyResource'
15        end
16      end
17    end
18
19    before do
20      allow(subject).to receive(:current_url).and_return(location)
21      allow(subject).to receive(:new).and_return(resource)
22    end
23  end
24
25  shared_examples 'fabrication method' do |fabrication_method_called, actual_fabrication_method = nil|
26    let(:fabrication_method_used) { actual_fabrication_method || fabrication_method_called }
27
28    it 'yields resource before calling resource method' do
29      expect(resource).to receive(:something!).ordered
30      expect(resource).to receive(fabrication_method_used).ordered.and_return(location)
31
32      subject.public_send(fabrication_method_called, resource: resource, &:something!)
33    end
34  end
35
36  describe '.fabricate!' do
37    context 'when resource does not support fabrication via the API' do
38      before do
39        allow(described_class).to receive(:fabricate_via_api!).and_raise(NotImplementedError)
40      end
41
42      it 'calls .fabricate_via_browser_ui!' do
43        expect(described_class).to receive(:fabricate_via_browser_ui!)
44
45        described_class.fabricate!
46      end
47    end
48
49    context 'when resource supports fabrication via the API' do
50      it 'calls .fabricate_via_browser_ui!' do
51        expect(described_class).to receive(:fabricate_via_api!)
52
53        described_class.fabricate!
54      end
55    end
56  end
57
58  describe '.fabricate_via_api!' do
59    include_context 'with fabrication context'
60
61    it_behaves_like 'fabrication method', :fabricate_via_api!
62
63    it 'instantiates the resource, calls resource method returns the resource' do
64      expect(resource).to receive(:fabricate_via_api!).and_return(location)
65
66      result = subject.fabricate_via_api!(resource: resource, parents: [])
67
68      expect(result).to eq(resource)
69    end
70
71    context "with debug log level" do
72      let(:method) { 'api' }
73
74      before do
75        allow(QA::Runtime::Logger).to receive(:debug)
76      end
77
78      it 'logs the resource and build method' do
79        stub_env('QA_DEBUG', 'true')
80
81        subject.fabricate_via_api!('something', resource: resource, parents: [])
82
83        expect(QA::Runtime::Logger).to have_received(:debug) do |&msg|
84          expect(msg.call).to match_regex(log_regex)
85        end
86      end
87    end
88  end
89
90  describe '.fabricate_via_browser_ui!' do
91    include_context 'with fabrication context'
92
93    it_behaves_like 'fabrication method', :fabricate_via_browser_ui!, :fabricate!
94
95    it 'instantiates the resource and calls resource method' do
96      subject.fabricate_via_browser_ui!('something', resource: resource, parents: [])
97
98      expect(resource).to have_received(:fabricate!).with('something')
99    end
100
101    it 'returns fabrication resource' do
102      result = subject.fabricate_via_browser_ui!('something', resource: resource, parents: [])
103
104      expect(result).to eq(resource)
105    end
106
107    context "with debug log level" do
108      let(:method) { 'browser_ui' }
109
110      before do
111        allow(QA::Runtime::Logger).to receive(:debug)
112      end
113
114      it 'logs the resource and build method' do
115        stub_env('QA_DEBUG', 'true')
116
117        subject.fabricate_via_browser_ui!('something', resource: resource, parents: [])
118
119        expect(QA::Runtime::Logger).to have_received(:debug) do |&msg|
120          expect(msg.call).to match_regex(log_regex)
121        end
122      end
123    end
124  end
125
126  shared_context 'with simple resource' do
127    subject do
128      Class.new(QA::Resource::Base) do
129        attribute :test do
130          'block'
131        end
132
133        attribute :no_block
134
135        def fabricate!
136          'any'
137        end
138
139        def self.current_url
140          'http://stub'
141        end
142      end
143    end
144
145    let(:resource) { subject.new }
146  end
147
148  describe '.attribute' do
149    include_context 'with simple resource'
150
151    context 'when the attribute is populated via a block' do
152      it 'returns value from the block' do
153        result = subject.fabricate!(resource: resource)
154
155        expect(result).to be_a(described_class)
156        expect(result.test).to eq('block')
157      end
158    end
159
160    context 'when the attribute is populated via the api' do
161      let(:api_resource) { { no_block: 'api' } }
162
163      before do
164        allow(resource).to receive(:api_resource).and_return(api_resource)
165      end
166
167      it 'returns value from api' do
168        result = subject.fabricate!(resource: resource)
169
170        expect(result).to be_a(described_class)
171        expect(result.no_block).to eq('api')
172      end
173
174      context 'when the attribute also has a block' do
175        let(:api_resource) { { test: 'api_with_block' } }
176
177        before do
178          allow(QA::Runtime::Logger).to receive(:debug)
179        end
180
181        it 'returns value from api and emits an debug log entry' do
182          result = subject.fabricate!(resource: resource)
183
184          expect(result).to be_a(described_class)
185          expect(result.test).to eq('api_with_block')
186          expect(QA::Runtime::Logger)
187            .to have_received(:debug).with(/api_with_block/)
188        end
189      end
190    end
191
192    context 'when the attribute is populated via direct assignment' do
193      before do
194        resource.test = 'value'
195      end
196
197      it 'returns value from the assignment' do
198        result = subject.fabricate!(resource: resource)
199
200        expect(result).to be_a(described_class)
201        expect(result.test).to eq('value')
202      end
203
204      context 'when the api also has such response' do
205        before do
206          allow(resource).to receive(:api_resource).and_return({ test: 'api' })
207        end
208
209        it 'returns value from the assignment' do
210          result = subject.fabricate!(resource: resource)
211
212          expect(result).to be_a(described_class)
213          expect(result.test).to eq('value')
214        end
215      end
216    end
217
218    context 'when the attribute has no value' do
219      it 'raises an error because no values could be found' do
220        result = subject.fabricate!(resource: resource)
221
222        expect { result.no_block }.to raise_error(
223          described_class::NoValueError, "No value was computed for no_block of #{resource.class.name}."
224        )
225      end
226    end
227
228    context 'when multiple resources have the same attribute name' do
229      let(:base) do
230        Class.new(QA::Resource::Base) do
231          def fabricate!
232            'any'
233          end
234
235          def self.current_url
236            'http://stub'
237          end
238        end
239      end
240
241      let(:first_resource) do
242        Class.new(base) do
243          attribute :test do
244            'first block'
245          end
246        end
247      end
248
249      let(:second_resource) do
250        Class.new(base) do
251          attribute :test do
252            'second block'
253          end
254        end
255      end
256
257      it 'has unique attribute values' do
258        first_result = first_resource.fabricate!(resource: first_resource.new)
259        second_result = second_resource.fabricate!(resource: second_resource.new)
260
261        expect(first_result.test).to eq 'first block'
262        expect(second_result.test).to eq 'second block'
263      end
264    end
265  end
266
267  describe '#web_url' do
268    include_context 'with simple resource'
269
270    it 'sets #web_url to #current_url after fabrication' do
271      subject.fabricate!(resource: resource)
272
273      expect(resource.web_url).to eq(subject.current_url)
274    end
275  end
276
277  describe '#visit!' do
278    include_context 'with simple resource'
279
280    before do
281      allow(resource).to receive(:visit)
282    end
283
284    it 'calls #visit with the underlying #web_url' do
285      allow(resource).to receive(:current_url).and_return(subject.current_url)
286
287      resource.web_url = subject.current_url
288      resource.visit!
289
290      expect(resource).to have_received(:visit).with(subject.current_url)
291    end
292  end
293end
294