pact_broker 2.0.0.beta.6 → 2.0.0.beta.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/README.md +12 -12
  4. data/db/migrations/08_create_latest_pact_view.rb +4 -4
  5. data/db/migrations/14_add_timestamps_to_pact_views.rb +5 -4
  6. data/db/migrations/20_add_pact_version_content_sha_to_all_pacts_view.rb +12 -10
  7. data/db/migrations/28_create_all_pact_publications.rb +6 -5
  8. data/lib/pact_broker/api/decorators/verification_decorator.rb +0 -3
  9. data/lib/pact_broker/api/decorators/verification_summary_decorator.rb +36 -0
  10. data/lib/pact_broker/api/decorators/versions_decorator.rb +1 -1
  11. data/lib/pact_broker/api/resources/latest_verifications_for_consumer_version.rb +5 -5
  12. data/lib/pact_broker/api/resources/versions.rb +1 -1
  13. data/lib/pact_broker/app.rb +2 -0
  14. data/lib/pact_broker/configuration.rb +6 -1
  15. data/lib/pact_broker/domain/order_versions.rb +15 -5
  16. data/lib/pact_broker/domain/verification.rb +1 -1
  17. data/lib/pact_broker/domain/webhook.rb +0 -1
  18. data/lib/pact_broker/domain/webhook_request.rb +6 -4
  19. data/lib/pact_broker/logging.rb +4 -0
  20. data/lib/pact_broker/pacticipants/repository.rb +3 -2
  21. data/lib/pact_broker/pacticipants/service.rb +12 -9
  22. data/lib/pact_broker/pacts/all_pact_publications.rb +2 -2
  23. data/lib/pact_broker/pacts/repository.rb +11 -2
  24. data/lib/pact_broker/pacts/service.rb +4 -0
  25. data/lib/pact_broker/tags/repository.rb +5 -5
  26. data/lib/pact_broker/verifications/repository.rb +4 -3
  27. data/lib/pact_broker/verifications/service.rb +8 -0
  28. data/lib/pact_broker/verifications/summary_for_consumer_version.rb +41 -0
  29. data/lib/pact_broker/version.rb +1 -1
  30. data/lib/pact_broker/versions/parse_semantic_version.rb +14 -4
  31. data/lib/pact_broker/versions/repository.rb +1 -1
  32. data/lib/pact_broker/webhooks/job.rb +46 -0
  33. data/lib/pact_broker/webhooks/service.rb +9 -8
  34. data/lib/pact_broker/webhooks/webhook.rb +1 -1
  35. data/pact_broker.gemspec +4 -3
  36. data/pact_broker_client-pact_broker.json +4 -4
  37. data/script/foo-bar.json +22 -0
  38. data/script/publish-new.sh +7 -0
  39. data/script/publish.sh +2 -2
  40. data/script/recreate-pg-db.sh +10 -0
  41. data/spec/features/get_verifications_for_consumer_version_spec.rb +1 -1
  42. data/spec/fixtures/a_consumer-a_provider-2.json +1 -1
  43. data/spec/fixtures/a_consumer-a_provider-3.json +1 -1
  44. data/spec/fixtures/a_consumer-a_provider-conflict.json +1 -1
  45. data/spec/fixtures/a_consumer-a_provider-merged.json +2 -2
  46. data/spec/fixtures/a_consumer-a_provider.json +1 -1
  47. data/spec/fixtures/consumer-provider.json +1 -1
  48. data/spec/fixtures/renderer_pact.json +1 -1
  49. data/spec/lib/pact_broker/api/decorators/{verifications_decorator_spec.rb → verification_summary_decorator_spec.rb} +16 -13
  50. data/spec/lib/pact_broker/api/resources/latest_verifications_for_consumer_version_spec.rb +5 -5
  51. data/spec/lib/pact_broker/domain/order_versions_spec.rb +30 -10
  52. data/spec/lib/pact_broker/domain/webhook_request_spec.rb +3 -1
  53. data/spec/lib/pact_broker/pacticipants/repository_spec.rb +16 -0
  54. data/spec/lib/pact_broker/pacticipants/service_spec.rb +74 -24
  55. data/spec/lib/pact_broker/verifications/summary_for_consumer_version_spec.rb +72 -0
  56. data/spec/lib/pact_broker/versions/parse_semantic_version_spec.rb +5 -2
  57. data/spec/lib/pact_broker/webhooks/job_spec.rb +67 -0
  58. data/spec/lib/pact_broker/webhooks/service_spec.rb +40 -3
  59. data/spec/support/provider_state_builder.rb +3 -2
  60. data/tasks/db.rake +3 -2
  61. metadata +35 -14
  62. data/lib/pact_broker/api/decorators/verifications_decorator.rb +0 -30
  63. data/lib/pact_broker/pacts/all_pacts.rb +0 -12
  64. data/lib/pact_broker/pacts/latest_pacts.rb +0 -12
@@ -1,3 +1,3 @@
1
1
  curl -v -XPUT \-H "Content-Type: application/json" \
2
- -d@spec/fixtures/a_consumer-a_provider.json \
3
- http://localhost:9292/pacts/provider/A%20Provider/consumer/A%20Consumer/version/1.0.0
2
+ -d@script/foo-bar.json \
3
+ http://127.0.0.1:9292/pacts/provider/Bar/consumer/Foo/version/1.0.0
@@ -0,0 +1,10 @@
1
+ psql postgres -c "drop database pact_broker;"
2
+ psql postgres -c "create database pact_broker;"
3
+ psql postgres -c "GRANT ALL PRIVILEGES ON DATABASE pact_broker to pact_broker;"
4
+ ip=$(ifconfig en0 | sed -n -e '/inet/s/.*inet \([0-9.]*\) netmask .*/\1/p')
5
+ echo ""
6
+ echo "run the following command to set your environment variables:"
7
+ echo "export PACT_BROKER_DATABASE_USERNAME=pact_broker"
8
+ echo "export PACT_BROKER_DATABASE_PASSWORD=pact_broker"
9
+ echo "export PACT_BROKER_DATABASE_NAME=pact_broker"
10
+ echo "export PACT_BROKER_DATABASE_HOST=${ip}"
@@ -27,7 +27,7 @@ describe "Get verifications for consumer version" do
27
27
  end
28
28
 
29
29
  it "returns a list of verifications" do
30
- expect(last_response_body[:_embedded][:'verification-results'].size).to eq 2
30
+ expect(last_response_body[:_embedded][:verificationResults].size).to eq 2
31
31
  end
32
32
  end
33
33
  end
@@ -8,7 +8,7 @@
8
8
  "interactions": [
9
9
  {
10
10
  "description" : "a request for something",
11
- "provider_state": null,
11
+ "providerState": null,
12
12
  "request": {
13
13
  "method": "post",
14
14
  "path" : "/something"
@@ -8,7 +8,7 @@
8
8
  "interactions": [
9
9
  {
10
10
  "description" : "another request for something",
11
- "provider_state": null,
11
+ "providerState": null,
12
12
  "request": {
13
13
  "method": "get",
14
14
  "path" : "/something_else"
@@ -8,7 +8,7 @@
8
8
  "interactions": [
9
9
  {
10
10
  "description" : "a request for something",
11
- "provider_state": null,
11
+ "providerState": null,
12
12
  "request": {
13
13
  "method": "post",
14
14
  "path" : "/something"
@@ -8,7 +8,7 @@
8
8
  "interactions": [
9
9
  {
10
10
  "description" : "a request for something",
11
- "provider_state": null,
11
+ "providerState": null,
12
12
  "request": {
13
13
  "method": "get",
14
14
  "path" : "/something"
@@ -20,7 +20,7 @@
20
20
  },
21
21
  {
22
22
  "description" : "another request for something",
23
- "provider_state": null,
23
+ "providerState": null,
24
24
  "request": {
25
25
  "method": "get",
26
26
  "path" : "/something_else"
@@ -8,7 +8,7 @@
8
8
  "interactions": [
9
9
  {
10
10
  "description" : "a request for something",
11
- "provider_state": null,
11
+ "providerState": null,
12
12
  "request": {
13
13
  "method": "get",
14
14
  "path" : "/something"
@@ -8,7 +8,7 @@
8
8
  "interactions": [
9
9
  {
10
10
  "description" : "a request for something",
11
- "provider_state": null,
11
+ "providerState": null,
12
12
  "request": {
13
13
  "method": "get",
14
14
  "path" : "/something",
@@ -8,7 +8,7 @@
8
8
  "interactions": [
9
9
  {
10
10
  "description": "a request for alligators",
11
- "provider_state": "alligators exist",
11
+ "providerState": "alligators exist",
12
12
  "request": {
13
13
  "method": "get",
14
14
  "path": "/alligators"
@@ -1,17 +1,21 @@
1
- require 'pact_broker/api/decorators/verifications_decorator'
1
+ require 'pact_broker/api/decorators/verification_summary_decorator'
2
2
 
3
3
  module PactBroker
4
4
  module Api
5
5
  module Decorators
6
- describe VerificationsDecorator do
6
+ describe VerificationSummaryDecorator do
7
+ let(:summary) { instance_double("PactBroker::Verification::SummaryForConsumerVersion", verifications: verifications, success: true, provider_summary: provider_summary) }
8
+ let(:provider_summary) {
9
+ instance_double("provider summary", successful: ["Successful provider"], failed: ["Failed provider"], unknown: ["Unknown provider"])
10
+ }
7
11
  let(:verifications) { [verification] }
8
12
  let(:verification) do
9
13
  instance_double("PactBroker::Domain::Verification",
10
14
  success: true, number: 1,
11
15
  provider_version: '4.5.6',
12
16
  build_url: 'http://some-build',
13
- provider_name: 'provider',
14
- consumer_name: 'consumer',
17
+ provider_name: 'Provider',
18
+ consumer_name: 'Consumer',
15
19
  pact_version: pact_version,
16
20
  latest_pact_publication: pact,
17
21
  execution_date: DateTime.now)
@@ -19,14 +23,15 @@ module PactBroker
19
23
  let(:pact_version) do
20
24
  instance_double("PactBroker::Pacts::PactVersion", sha: '1234', name: 'Name')
21
25
  end
26
+
22
27
  let(:pact) { instance_double("PactBroker::Domain::Pact", name: "Some pact", consumer_name: "Foo", provider_name: "Bar", consumer_version_number: "1.2.3") }
23
28
  let(:options) { {base_url: 'http://example.org', consumer_name: "Foo", consumer_version_number: "1.2.3", resource_url: "http://self"} }
24
29
 
25
- subject { JSON.parse VerificationsDecorator.new(verifications).to_json(user_options: options), symbolize_names: true }
30
+ subject { JSON.parse VerificationSummaryDecorator.new(summary).to_json(user_options: options), symbolize_names: true }
26
31
 
27
32
  it "includes a list of verification results" do
28
- expect(subject[:_embedded][:'verification-results']).to be_instance_of(Array)
29
- expect(subject[:_embedded][:'verification-results'].size).to eq 1
33
+ expect(subject[:_embedded][:verificationResults]).to be_instance_of(Array)
34
+ expect(subject[:_embedded][:verificationResults].size).to eq 1
30
35
  end
31
36
 
32
37
  it "includes a title" do
@@ -41,12 +46,10 @@ module PactBroker
41
46
  expect(subject[:success]).to be true
42
47
  end
43
48
 
44
- context "when there are no verifications" do
45
- let(:verifications) { [] }
46
-
47
- it "does not include a flag to indicate the overall success or failure of all the verification results" do
48
- expect(subject).to_not have_key(:success)
49
- end
49
+ it "includes a provider summary" do
50
+ expect(subject[:providerSummary][:successful]).to eq ["Successful provider"]
51
+ expect(subject[:providerSummary][:failed]).to eq ["Failed provider"]
52
+ expect(subject[:providerSummary][:unknown]).to eq ["Unknown provider"]
50
53
  end
51
54
  end
52
55
  end
@@ -24,23 +24,23 @@ module PactBroker
24
24
 
25
25
  context "when the consumer version exists" do
26
26
  let(:decorator) { double(:decorator, to_json: json) }
27
- let(:verifications) { double(:verifications) }
27
+ let(:summary) { double(:summary) }
28
28
  let(:json) { {some: 'json'}.to_json }
29
29
 
30
30
  before do
31
- allow(PactBroker::Api::Decorators::VerificationsDecorator).to receive(:new).and_return(decorator)
32
- allow(PactBroker::Verifications::Service).to receive(:find_latest_verifications_for_consumer_version).and_return(verifications)
31
+ allow(PactBroker::Api::Decorators::VerificationSummaryDecorator).to receive(:new).and_return(decorator)
32
+ allow(PactBroker::Verifications::Service).to receive(:verification_summary_for_consumer_version).and_return(summary)
33
33
  end
34
34
 
35
35
  it { is_expected.to be_a_hal_json_success_response }
36
36
 
37
37
  it "finds the latest verifications for the consumer version" do
38
- expect(PactBroker::Verifications::Service).to receive(:find_latest_verifications_for_consumer_version).with(hash_including(consumer_name: 'Consumer', consumer_version_number: '1.2.3'))
38
+ expect(PactBroker::Verifications::Service).to receive(:verification_summary_for_consumer_version).with(hash_including(consumer_name: 'Consumer', consumer_version_number: '1.2.3'))
39
39
  subject
40
40
  end
41
41
 
42
42
  it "serialises the verifications" do
43
- expect(PactBroker::Api::Decorators::VerificationsDecorator).to receive(:new).with(verifications)
43
+ expect(PactBroker::Api::Decorators::VerificationSummaryDecorator).to receive(:new).with(summary)
44
44
  expect(decorator).to receive(:to_json) do | args |
45
45
  expect(args[:user_options][:consumer_name]).to eq 'Consumer'
46
46
  expect(args[:user_options][:consumer_version_number]).to eq '1.2.3'
@@ -4,20 +4,40 @@ require 'pact_broker/domain/order_versions.rb'
4
4
 
5
5
  describe PactBroker::Domain::OrderVersions do
6
6
 
7
+ context "when order_versions_by_date is false (the default)" do
8
+ before do
9
+ ProviderStateBuilder.new
10
+ .create_condor
11
+ .create_condor_version('1.3.0')
12
+ .create_condor_version('1.5.0')
13
+ .create_condor_version('1.4.0')
14
+ .create_condor_version('1.6.0')
15
+ end
7
16
 
8
- before do
9
- ProviderStateBuilder.new
10
- .create_condor
11
- .create_condor_version('1.3.0')
12
- .create_condor_version('1.5.0')
13
- .create_condor_version('1.4.0')
17
+ let(:ordered_versions) { PactBroker::Domain::Version.order(:order).all.collect(&:number) }
18
+ let(:condor) { PactBroker::Domain::Pacticipant.where(name: 'Condor').single_record }
19
+
20
+ it "orders the versions so they can be loaded from the database in order" do
21
+ expect(ordered_versions).to eq(['1.3.0', '1.4.0', '1.5.0', '1.6.0'])
22
+ end
14
23
  end
15
24
 
16
- let(:ordered_versions) { PactBroker::Domain::Version.order(:order).all.collect(&:number) }
17
- let(:condor) { PactBroker::Domain::Pacticipant.where(name: 'Condor').single_record }
25
+ context "when order_versions_by_date is true (not recommended)" do
26
+ before do
27
+ allow(PactBroker.configuration).to receive(:order_versions_by_date).and_return(true)
28
+ end
29
+ let(:consumer) { ProviderStateBuilder.new.create_consumer.and_return(:consumer) }
30
+ let!(:version_1) { PactBroker::Domain::Version.create(pacticipant_id: consumer.id, number: '2', created_at: DateTime.new(2017)) }
31
+ let!(:version_2) { PactBroker::Domain::Version.create(pacticipant_id: consumer.id, number: '1', created_at: DateTime.new(2017)) }
32
+ let!(:version_3) { PactBroker::Domain::Version.create(pacticipant_id: consumer.id, number: '3', created_at: DateTime.new(2016)) }
33
+ let!(:version_4) { PactBroker::Domain::Version.create(pacticipant_id: consumer.id, number: '4', created_at: DateTime.new(2018)) }
34
+
35
+ let(:ordered_versions) { PactBroker::Domain::Version.order(:order).all.collect(&:number) }
36
+
37
+ it "orders by date, then id" do
38
+ expect(ordered_versions).to eq(['3', '2', '1', '4'])
39
+ end
18
40
 
19
- it "orders the versions so they can be loaded from the database in order" do
20
- expect(ordered_versions).to eq(['1.3.0','1.4.0', '1.5.0'])
21
41
  end
22
42
 
23
43
  end
@@ -64,7 +64,9 @@ module PactBroker
64
64
 
65
65
  it "logs the response" do
66
66
  allow(PactBroker.logger).to receive(:info)
67
- expect(PactBroker.logger).to receive(:info).with(/response.*302.*respbod/)
67
+ allow(PactBroker.logger).to receive(:debug)
68
+ expect(PactBroker.logger).to receive(:info).with(/response.*302/)
69
+ expect(PactBroker.logger).to receive(:debug).with(/respbod/)
68
70
  subject.execute
69
71
  end
70
72
 
@@ -53,6 +53,22 @@ module PactBroker
53
53
 
54
54
  end
55
55
 
56
+ describe "#find_all_pacticipant_versions_in_reverse_order" do
57
+ before do
58
+ ProviderStateBuilder.new
59
+ .create_consumer("Foo")
60
+ .create_consumer_version("1.2.3")
61
+ .create_consumer_version("4.5.6")
62
+ .create_consumer("Bar")
63
+ .create_consumer_version("8.9.0")
64
+ end
65
+
66
+ subject { Repository.new.find_all_pacticipant_versions_in_reverse_order "Foo" }
67
+
68
+ it "returns all the application versions for the given consumer" do
69
+ expect(subject.collect(&:number)).to eq ["4.5.6", "1.2.3"]
70
+ end
71
+ end
56
72
 
57
73
  end
58
74
  end
@@ -90,6 +90,7 @@ module PactBroker
90
90
  end
91
91
 
92
92
  it "logs the names" do
93
+ allow(PactBroker.logger).to receive(:info)
93
94
  expect(PactBroker.logger).to receive(:info).with(/pacticipant_name.*Fred, Mary/)
94
95
  subject.find_potential_duplicate_pacticipants pacticipant_name
95
96
  end
@@ -126,39 +127,88 @@ module PactBroker
126
127
  .create_consumer_version_tag("prod")
127
128
  .create_pact
128
129
  .create_webhook
130
+ .create_verification
129
131
  end
130
132
 
131
- let(:delete_pacticipant) { subject.delete "Consumer" }
133
+ let(:delete_consumer) { subject.delete "Consumer" }
134
+ let(:delete_provider) { subject.delete "Provider" }
132
135
 
133
- it "deletes the pacticipant" do
134
- expect{ delete_pacticipant }.to change{
135
- PactBroker::Domain::Pacticipant.all.count
136
- }.by(-1)
137
- end
136
+ context "deleting a consumer" do
137
+ it "deletes the pacticipant" do
138
+ expect{ delete_consumer }.to change{
139
+ PactBroker::Domain::Pacticipant.all.count
140
+ }.by(-1)
141
+ end
138
142
 
139
- it "deletes the child versions" do
140
- expect{ delete_pacticipant }.to change{
141
- PactBroker::Domain::Version.where(number: "1.2.3").count
142
- }.by(-1)
143
- end
143
+ it "deletes the child versions" do
144
+ expect{ delete_consumer }.to change{
145
+ PactBroker::Domain::Version.where(number: "1.2.3").count
146
+ }.by(-1)
147
+ end
144
148
 
145
- it "deletes the child tags" do
146
- expect{ delete_pacticipant }.to change{
147
- PactBroker::Domain::Tag.where(name: "prod").count
148
- }.by(-1)
149
- end
149
+ it "deletes the child tags" do
150
+ expect{ delete_consumer }.to change{
151
+ PactBroker::Domain::Tag.where(name: "prod").count
152
+ }.by(-1)
153
+ end
150
154
 
151
- it "deletes the webhooks" do
152
- expect{ delete_pacticipant }.to change{
153
- PactBroker::Webhooks::Webhook.count
154
- }.by(-1)
155
+ it "deletes the webhooks" do
156
+ expect{ delete_consumer }.to change{
157
+ PactBroker::Webhooks::Webhook.count
158
+ }.by(-1)
159
+ end
160
+
161
+ it "deletes the child pacts" do
162
+ expect{ delete_consumer }.to change{
163
+ PactBroker::Pacts::PactPublication.count
164
+ }.by(-2)
165
+ end
166
+
167
+ it "deletes the verifications" do
168
+ expect{ delete_consumer }.to change{
169
+ PactBroker::Domain::Verification.count
170
+ }.by(-1)
171
+ end
155
172
  end
156
173
 
157
- it "deletes the child pacts" do
158
- expect{ delete_pacticipant }.to change{
159
- PactBroker::Pacts::PactPublication.count
160
- }.by(-2)
174
+ context "deleting a provider" do
175
+ it "deletes the pacticipant" do
176
+ expect{ delete_provider }.to change{
177
+ PactBroker::Domain::Pacticipant.all.count
178
+ }.by(-1)
179
+ end
180
+
181
+ it "does not delete any versions" do
182
+ expect{ delete_provider }.to change{
183
+ PactBroker::Domain::Version.where(number: "1.2.3").count
184
+ }.by(0)
185
+ end
186
+
187
+ it "deletes the child tags only if there are any" do
188
+ expect{ delete_provider }.to change{
189
+ PactBroker::Domain::Tag.where(name: "prod").count
190
+ }.by(0)
191
+ end
192
+
193
+ it "deletes the webhooks" do
194
+ expect{ delete_provider }.to change{
195
+ PactBroker::Webhooks::Webhook.count
196
+ }.by(-1)
197
+ end
198
+
199
+ it "deletes the child pacts" do
200
+ expect{ delete_provider }.to change{
201
+ PactBroker::Pacts::PactPublication.count
202
+ }.by(-2)
203
+ end
204
+
205
+ it "deletes the verifications" do
206
+ expect{ delete_provider }.to change{
207
+ PactBroker::Domain::Verification.count
208
+ }.by(-1)
209
+ end
161
210
  end
211
+
162
212
  end
163
213
 
164
214
  end
@@ -0,0 +1,72 @@
1
+ require 'pact_broker/verifications/summary_for_consumer_version'
2
+
3
+ module PactBroker
4
+ module Verifications
5
+ describe SummaryForConsumerVersion do
6
+
7
+ let(:verifications) { [verification_1, verification_2] }
8
+ let(:verification_1) do
9
+ instance_double("PactBroker::Domain::Verification",
10
+ success: true,
11
+ provider_name: 'Successful Provider'
12
+ )
13
+ end
14
+ let(:verification_2) do
15
+ instance_double("PactBroker::Domain::Verification",
16
+ success: false,
17
+ provider_name: 'Failed Provider'
18
+ )
19
+ end
20
+
21
+ let(:pact_1) { instance_double("pact", provider_name: 'Successful Provider') }
22
+ let(:pact_2) { instance_double("pact", provider_name: 'Failed Provider') }
23
+ let(:pact_3) { instance_double("pact", provider_name: 'Unknown Provider') }
24
+
25
+ let(:pacts) do
26
+ [pact_1, pact_2, pact_3]
27
+ end
28
+
29
+ subject { SummaryForConsumerVersion.new(verifications, pacts)}
30
+
31
+ describe "#provider_summary" do
32
+ it "returns the successful providers" do
33
+ expect(subject.provider_summary.successful).to eq ["Successful Provider"]
34
+ end
35
+
36
+ it "returns the failed providers" do
37
+ expect(subject.provider_summary.failed).to eq ["Failed Provider"]
38
+ end
39
+
40
+ it "returns the unknown providers" do
41
+ expect(subject.provider_summary.unknown).to eq ["Unknown Provider"]
42
+ end
43
+ end
44
+
45
+ describe "success" do
46
+ context "when all pacts have a successful verification" do
47
+ let(:verifications) { [verification_1] }
48
+ let(:pacts) { [[pact_1]] }
49
+ its(:success) { is_expected.to be true }
50
+ end
51
+
52
+ context "when some pacts have not been verified" do
53
+ let(:verifications) { [] }
54
+ let(:pacts) { [[pact_1]] }
55
+ its(:success) { is_expected.to be false }
56
+ end
57
+
58
+ context "when some pacts have failed verification" do
59
+ let(:verifications) { [verification_2] }
60
+ let(:pacts) { [[pact_2]] }
61
+ its(:success) { is_expected.to be false }
62
+ end
63
+
64
+ context "when there are no verifications" do
65
+ let(:verifications) { [] }
66
+ let(:pacts) { [[pact_2]] }
67
+ its(:success) { is_expected.to be false }
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end