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