pact 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +31 -20
- data/Gemfile.lock +12 -12
- data/README.md +32 -16
- data/Rakefile +3 -3
- data/documentation/README.md +1 -0
- data/documentation/development-workflow.md +22 -0
- data/documentation/faq.md +8 -0
- data/documentation/provider-states.md +2 -0
- data/documentation/troubleshooting.md +4 -0
- data/documentation/verifying-pacts.md +97 -0
- data/lib/pact/app.rb +98 -4
- data/lib/pact/consumer/rspec.rb +3 -2
- data/lib/pact/doc/doc_file.rb +4 -4
- data/lib/pact/doc/generator.rb +3 -3
- data/lib/pact/doc/interaction_view_model.rb +1 -0
- data/lib/pact/doc/markdown/{interactions_renderer.rb → consumer_contract_renderer.rb} +1 -1
- data/lib/pact/doc/markdown/generator.rb +2 -2
- data/lib/pact/matchers/unix_diff_formatter.rb +1 -1
- data/lib/pact/project_root.rb +7 -0
- data/lib/pact/provider.rb +0 -1
- data/lib/pact/provider/context.rb +0 -0
- data/lib/pact/provider/matchers/messages.rb +15 -13
- data/lib/pact/provider/pact_spec_runner.rb +21 -22
- data/lib/pact/provider/rspec.rb +22 -15
- data/lib/pact/provider/rspec/custom_options_file +0 -0
- data/lib/pact/provider/{matchers.rb → rspec/matchers.rb} +2 -1
- data/lib/pact/rspec.rb +20 -0
- data/lib/pact/shared/request.rb +1 -1
- data/lib/pact/tasks/task_helper.rb +18 -15
- data/lib/pact/tasks/verification_task.rb +26 -32
- data/lib/pact/version.rb +1 -1
- data/lib/tasks/pact.rake +5 -11
- data/spec/integration/pact/consumer_configuration_spec.rb +3 -3
- data/spec/lib/pact/app_spec.rb +47 -0
- data/spec/lib/pact/consumer/app_manager_spec.rb +1 -1
- data/spec/lib/pact/consumer/mock_service/interaction_list_spec.rb +3 -3
- data/spec/lib/pact/consumer/mock_service/verification_get_spec.rb +10 -2
- data/spec/lib/pact/consumer/mock_service_interaction_expectation_spec.rb +2 -2
- data/spec/lib/pact/consumer_contract/consumer_contract_spec.rb +1 -1
- data/spec/lib/pact/consumer_contract/interaction_spec.rb +4 -4
- data/spec/lib/pact/consumer_contract/request_spec.rb +23 -23
- data/spec/lib/pact/doc/generator_spec.rb +4 -4
- data/spec/lib/pact/doc/markdown/{interactions_renderer_spec.rb → consumer_contract_renderer_spec.rb} +4 -4
- data/spec/lib/pact/matchers/unix_diff_formatter_spec.rb +8 -8
- data/spec/lib/pact/provider/configuration/configuration_extension_spec.rb +2 -2
- data/spec/lib/pact/provider/matchers/messages_spec.rb +17 -6
- data/spec/lib/pact/provider/rspec/formatter_spec.rb +3 -1
- data/spec/lib/pact/shared/dsl_spec.rb +1 -1
- data/spec/lib/pact/shared/request_spec.rb +8 -0
- data/spec/lib/pact/tasks/task_helper_spec.rb +39 -54
- data/spec/lib/pact/tasks/verification_task_spec.rb +75 -0
- data/spec/pact_specification/compliance-1.0.0.rb +47 -0
- data/spec/spec_helper.rb +2 -6
- data/spec/standalone/consumer_fail_test.rb +1 -0
- data/spec/standalone/consumer_pass_test.rb +1 -0
- data/spec/support/active_support_if_configured.rb +6 -0
- data/spec/support/pact_helper.rb +2 -1
- data/spec/support/shared_examples_for_request.rb +15 -4
- data/spec/support/spec_support.rb +3 -0
- data/spec/support/stubbing_using_allow.rb +1 -0
- data/spec/support/term.json +13 -1
- data/tasks/pact-test.rake +45 -26
- metadata +23 -13
- data/lib/pact/provider/client_project_pact_helper.rb +0 -4
- data/spec/lib/pact/provider/pact_spec_runner_spec.rb +0 -7
- data/spec/lib/pact/verification_task_spec.rb +0 -99
data/spec/lib/pact/doc/markdown/{interactions_renderer_spec.rb → consumer_contract_renderer_spec.rb}
RENAMED
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'pact/doc/markdown/
|
2
|
+
require 'pact/doc/markdown/consumer_contract_renderer'
|
3
3
|
|
4
4
|
module Pact
|
5
5
|
module Doc
|
6
6
|
module Markdown
|
7
|
-
describe
|
7
|
+
describe ConsumerContractRenderer do
|
8
8
|
|
9
|
-
subject {
|
9
|
+
subject { ConsumerContractRenderer.new(consumer_contract) }
|
10
10
|
let(:consumer_contract) { Pact::ConsumerContract.from_uri './spec/support/markdown_pact.json' }
|
11
11
|
|
12
12
|
let(:expected_output) { File.read("./spec/support/generated_markdown.md") }
|
@@ -19,7 +19,7 @@ module Pact
|
|
19
19
|
|
20
20
|
describe ".call" do
|
21
21
|
it "renders an interaction" do
|
22
|
-
expect(
|
22
|
+
expect(ConsumerContractRenderer.call consumer_contract).to eq(expected_output)
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -54,7 +54,7 @@ EOF
|
|
54
54
|
end
|
55
55
|
|
56
56
|
it "generates the right number of lines, even with ActiveSupport loaded" do
|
57
|
-
expect(line_count).to eq
|
57
|
+
expect(line_count).to eq 9
|
58
58
|
end
|
59
59
|
|
60
60
|
end
|
@@ -76,7 +76,7 @@ EOF
|
|
76
76
|
end
|
77
77
|
|
78
78
|
it "generates the right number of lines, even with ActiveSupport loaded" do
|
79
|
-
expect(line_count).to eq
|
79
|
+
expect(line_count).to eq 9
|
80
80
|
end
|
81
81
|
|
82
82
|
end
|
@@ -98,7 +98,7 @@ EOF
|
|
98
98
|
end
|
99
99
|
|
100
100
|
it "generates the right number of lines, even with ActiveSupport loaded" do
|
101
|
-
expect(line_count).to eq
|
101
|
+
expect(line_count).to eq 5
|
102
102
|
end
|
103
103
|
|
104
104
|
end
|
@@ -120,7 +120,7 @@ EOF
|
|
120
120
|
end
|
121
121
|
|
122
122
|
it "generates the right number of lines, even with ActiveSupport loaded" do
|
123
|
-
expect(line_count).to eq
|
123
|
+
expect(line_count).to eq 8
|
124
124
|
end
|
125
125
|
end
|
126
126
|
|
@@ -140,7 +140,7 @@ EOF
|
|
140
140
|
end
|
141
141
|
|
142
142
|
it "generates the right number of lines, even with ActiveSupport loaded" do
|
143
|
-
expect(line_count).to eq
|
143
|
+
expect(line_count).to eq 8
|
144
144
|
end
|
145
145
|
|
146
146
|
end
|
@@ -159,7 +159,7 @@ EOF
|
|
159
159
|
end
|
160
160
|
|
161
161
|
it "generates the right number of lines, even with ActiveSupport loaded" do
|
162
|
-
expect(line_count).to eq
|
162
|
+
expect(line_count).to eq 8
|
163
163
|
end
|
164
164
|
|
165
165
|
end
|
@@ -185,7 +185,7 @@ EOF
|
|
185
185
|
end
|
186
186
|
|
187
187
|
it "generates the right number of lines, even with ActiveSupport loaded" do
|
188
|
-
expect(line_count).to eq
|
188
|
+
expect(line_count).to eq 8
|
189
189
|
end
|
190
190
|
|
191
191
|
end
|
@@ -203,7 +203,7 @@ EOF
|
|
203
203
|
end
|
204
204
|
|
205
205
|
it "generates the right number of lines, even with ActiveSupport loaded" do
|
206
|
-
expect(line_count).to eq
|
206
|
+
expect(line_count).to eq 11
|
207
207
|
end
|
208
208
|
|
209
209
|
end
|
@@ -14,12 +14,12 @@ module Pact
|
|
14
14
|
describe "#color_enabled" do
|
15
15
|
|
16
16
|
it "sets color_enabled to be true by default" do
|
17
|
-
expect(subject.color_enabled).to
|
17
|
+
expect(subject.color_enabled).to be true
|
18
18
|
end
|
19
19
|
|
20
20
|
it "allows configuration of colour_enabled" do
|
21
21
|
subject.color_enabled = false
|
22
|
-
expect(subject.color_enabled).to
|
22
|
+
expect(subject.color_enabled).to be false
|
23
23
|
end
|
24
24
|
|
25
25
|
end
|
@@ -10,9 +10,11 @@ module Pact
|
|
10
10
|
describe "#match_term_failure_message" do
|
11
11
|
|
12
12
|
let(:message) { "line1\nline2"}
|
13
|
+
let(:output_message) { "Actual: actual\n\n#{message}"}
|
14
|
+
let(:output_message_with_resets) { "Actual: actual\n\n#{r}line1\n#{r}line2"}
|
13
15
|
let(:r) { ::Term::ANSIColor.reset }
|
14
|
-
let(:message_with_resets) { "#{r}line1\n#{r}line2"}
|
15
16
|
let(:diff) { double("diff") }
|
17
|
+
let(:actual) { "actual" }
|
16
18
|
let(:color_enabled) { true }
|
17
19
|
let(:ansi_reset_at_start_of_line) { /^#{Regexp.escape ::Term::ANSIColor.reset}/ }
|
18
20
|
let(:message_line_count) { message.split("\n").size }
|
@@ -21,7 +23,7 @@ module Pact
|
|
21
23
|
allow(Pact.configuration.diff_formatter).to receive(:call).and_return(message)
|
22
24
|
end
|
23
25
|
|
24
|
-
subject { match_term_failure_message diff, color_enabled }
|
26
|
+
subject { match_term_failure_message diff, actual, color_enabled }
|
25
27
|
|
26
28
|
it "creates a message using the configured diff_formatter" do
|
27
29
|
expect(Pact.configuration.diff_formatter).to receive(:call).with(diff)
|
@@ -31,17 +33,26 @@ module Pact
|
|
31
33
|
context "when color_enabled is true" do
|
32
34
|
|
33
35
|
it "returns the message with ANSI reset at the start of each line" do
|
34
|
-
expect(subject).to eq(
|
36
|
+
expect(subject).to eq(output_message_with_resets)
|
35
37
|
end
|
36
38
|
|
37
39
|
end
|
38
40
|
|
41
|
+
context "when the actual is not a string" do
|
42
|
+
|
43
|
+
let(:actual) { {the: "actual"} }
|
44
|
+
|
45
|
+
it "includes the actual as json" do
|
46
|
+
expect(subject).to include(actual.to_json)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
39
50
|
context "when color_enabled is false" do
|
40
51
|
|
41
52
|
let(:color_enabled) { false }
|
42
53
|
|
43
54
|
it "returns the message unmodified" do
|
44
|
-
expect(subject).to eq(
|
55
|
+
expect(subject).to eq(output_message)
|
45
56
|
end
|
46
57
|
|
47
58
|
end
|
@@ -88,9 +99,9 @@ module Pact
|
|
88
99
|
|
89
100
|
end
|
90
101
|
|
91
|
-
context "when the expected is a
|
102
|
+
context "when the expected is a regexp" do
|
92
103
|
|
93
|
-
let(:expected) {
|
104
|
+
let(:expected) { /hal/ }
|
94
105
|
let(:expected_message) { "Expected header \"Content-Type\" to match /hal/, but was \"text/plain\"" }
|
95
106
|
|
96
107
|
it "creates a message with the term's matcher" do
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'pact/provider/rspec/formatter'
|
3
3
|
require './spec/support/factories'
|
4
|
+
require './spec/support/spec_support'
|
4
5
|
|
5
6
|
module Pact
|
6
7
|
module Provider
|
@@ -18,7 +19,8 @@ module Pact
|
|
18
19
|
let(:missing_provider_states) { 'missing_provider_states'}
|
19
20
|
|
20
21
|
subject { Formatter.new output }
|
21
|
-
|
22
|
+
|
23
|
+
let(:output_result) { Pact::SpecSupport.remove_ansicolor output.string }
|
22
24
|
|
23
25
|
before do
|
24
26
|
allow(PrintMissingProviderStates).to receive(:call)
|
@@ -72,6 +72,14 @@ module Pact
|
|
72
72
|
expect(subject).to eq("GET /something")
|
73
73
|
end
|
74
74
|
end
|
75
|
+
|
76
|
+
context "with a query" do
|
77
|
+
subject { TestRequest.new("get", "/something", {}, {} , "test=query").method_and_path }
|
78
|
+
|
79
|
+
it "includes the query" do
|
80
|
+
expect(subject).to eq("GET /something?test=query")
|
81
|
+
end
|
82
|
+
end
|
75
83
|
end
|
76
84
|
|
77
85
|
end
|
@@ -1,80 +1,65 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'pact/tasks/task_helper'
|
3
|
+
require 'rake/file_utils'
|
3
4
|
|
4
5
|
module Pact
|
5
6
|
describe TaskHelper do
|
6
|
-
include TaskHelper
|
7
7
|
|
8
|
-
let(:env_description) { "pact description set in ENV"}
|
9
|
-
let(:env_provider_state) { "provider state set in ENV"}
|
10
|
-
let(:env_criteria){ {:description=>/#{env_description}/, :provider_state=>/#{env_provider_state}/} }
|
11
|
-
let(:default_description) { "default description"}
|
12
|
-
let(:default_provider_state) { "default provider state"}
|
13
8
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
end
|
9
|
+
describe ".execute_pact_verify" do
|
10
|
+
let(:ruby_path) { "/path/to/ruby" }
|
11
|
+
let(:pact_uri) { "/pact/uri" }
|
12
|
+
let(:default_pact_helper_path) { "/pact/helper/path.rb" }
|
20
13
|
|
21
|
-
shared_context 'PACT_PROVIDER_STATE is defined' do
|
22
14
|
before do
|
23
|
-
|
24
|
-
|
15
|
+
stub_const("FileUtils::RUBY", ruby_path)
|
16
|
+
allow(Pact::Provider::PactHelperLocater).to receive(:pact_helper_path).and_return(default_pact_helper_path)
|
25
17
|
end
|
26
|
-
end
|
27
|
-
|
28
|
-
shared_context 'default description is defined' do
|
29
|
-
let(:default_description) { "default description"}
|
30
|
-
end
|
31
|
-
|
32
|
-
let(:defaults) { {:description => default_description, :provider_state => default_provider_state} }
|
33
|
-
|
34
|
-
describe "spec_criteria" do
|
35
18
|
|
36
|
-
context "
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
context "when defaults are not passed in" do
|
43
|
-
it "returns the env vars as regexes" do
|
44
|
-
expect(spec_criteria).to eq(env_criteria)
|
45
|
-
end
|
19
|
+
context "with no pact_helper or pact URI" do
|
20
|
+
let(:command) { "#{ruby_path} -S pact verify -h #{default_pact_helper_path}" }
|
21
|
+
it "executes the command" do
|
22
|
+
expect(TaskHelper).to receive(:execute_cmd).with(command)
|
23
|
+
TaskHelper.execute_pact_verify
|
46
24
|
end
|
25
|
+
end
|
47
26
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
27
|
+
context "with a pact URI" do
|
28
|
+
let(:command) { "#{ruby_path} -S pact verify -h #{default_pact_helper_path} -p #{pact_uri}" }
|
29
|
+
it "executes the command" do
|
30
|
+
expect(TaskHelper).to receive(:execute_cmd).with(command)
|
31
|
+
TaskHelper.execute_pact_verify(pact_uri)
|
52
32
|
end
|
53
33
|
end
|
54
34
|
|
55
|
-
context "
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
context "when defaults are not passed in" do
|
62
|
-
it "returns an empty hash" do
|
63
|
-
expect(spec_criteria).to eq({})
|
64
|
-
end
|
35
|
+
context "with a pact URI and a pact_helper" do
|
36
|
+
let(:custom_pact_helper_path) { '/custom/pact_helper.rb' }
|
37
|
+
let(:command) { "#{ruby_path} -S pact verify -h #{custom_pact_helper_path} -p #{pact_uri}" }
|
38
|
+
it "executes the command" do
|
39
|
+
expect(TaskHelper).to receive(:execute_cmd).with(command)
|
40
|
+
TaskHelper.execute_pact_verify(pact_uri, custom_pact_helper_path)
|
65
41
|
end
|
66
42
|
end
|
67
43
|
|
68
|
-
context "
|
69
|
-
|
70
|
-
|
71
|
-
|
44
|
+
context "with a pact_helper with no .rb on the end" do
|
45
|
+
let(:custom_pact_helper_path) { '/custom/pact_helper' }
|
46
|
+
let(:command) { "#{ruby_path} -S pact verify -h #{custom_pact_helper_path}.rb -p #{pact_uri}" }
|
47
|
+
it "executes the command" do
|
48
|
+
expect(TaskHelper).to receive(:execute_cmd).with(command)
|
49
|
+
TaskHelper.execute_pact_verify(pact_uri, custom_pact_helper_path)
|
72
50
|
end
|
51
|
+
end
|
73
52
|
|
74
|
-
|
75
|
-
|
53
|
+
context "with a pact URI and a nil pact_helper" do
|
54
|
+
let(:command) { "#{ruby_path} -S pact verify -h #{default_pact_helper_path} -p #{pact_uri}" }
|
55
|
+
it "executes the command" do
|
56
|
+
expect(TaskHelper).to receive(:execute_cmd).with(command)
|
57
|
+
TaskHelper.execute_pact_verify(pact_uri, nil)
|
76
58
|
end
|
77
59
|
end
|
60
|
+
|
78
61
|
end
|
62
|
+
|
63
|
+
|
79
64
|
end
|
80
65
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pact/tasks/verification_task'
|
3
|
+
|
4
|
+
module Pact
|
5
|
+
describe VerificationTask do
|
6
|
+
before :all do
|
7
|
+
@pact_helper = '/custom/path/pact_helper .rb'
|
8
|
+
@pact_uri = 'http://example.org/pact.json'
|
9
|
+
@task_name = 'pact:verify:pact_rake_spec'
|
10
|
+
@task_name_with_explict_pact_helper = 'pact:verify:pact_rake_spec_with_explict_pact_helper'
|
11
|
+
@consumer = 'some-consumer'
|
12
|
+
@criteria = {:description => /wiffle/}
|
13
|
+
|
14
|
+
VerificationTask.new(:pact_rake_spec_with_explict_pact_helper) do | pact |
|
15
|
+
pact.uri @pact_uri, pact_helper: @pact_helper
|
16
|
+
end
|
17
|
+
|
18
|
+
VerificationTask.new(:pact_rake_spec) do | pact |
|
19
|
+
pact.uri @pact_uri
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
before do
|
24
|
+
allow(Pact::TaskHelper).to receive(:execute_pact_verify).and_return(0)
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:exit_code) {0}
|
28
|
+
|
29
|
+
|
30
|
+
describe '.initialize' do
|
31
|
+
context 'with an explict pact_helper' do
|
32
|
+
it 'creates the tasks' do
|
33
|
+
Rake::Task.tasks.should include_task @task_name
|
34
|
+
end
|
35
|
+
end
|
36
|
+
context 'with no explict pact_helper' do
|
37
|
+
it 'creates the tasks' do
|
38
|
+
Rake::Task.tasks.should include_task @task_name_with_explict_pact_helper
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'execute' do
|
44
|
+
|
45
|
+
context "with no explicit pact_helper" do
|
46
|
+
it 'verifies the pacts using the TaskHelper' do
|
47
|
+
expect(Pact::TaskHelper).to receive(:execute_pact_verify).with(@pact_uri, nil)
|
48
|
+
Rake::Task[@task_name].execute
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "with an explict pact_helper" do
|
53
|
+
let(:verification_config) { [ uri: @pact_uri, pact_helper: @pact_helper] }
|
54
|
+
it 'verifies the pacts using the TaskHelper' do
|
55
|
+
expect(Pact::TaskHelper).to receive(:execute_pact_verify).with(@pact_uri, @pact_helper)
|
56
|
+
Rake::Task[@task_name_with_explict_pact_helper].execute
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'when all specs pass' do
|
61
|
+
|
62
|
+
it 'does not raise an exception' do
|
63
|
+
Rake::Task[@task_name].execute
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
RSpec::Matchers.define :include_task do |expected|
|
72
|
+
match do |actual|
|
73
|
+
actual.any? { |task| task.name == expected }
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pact/consumer/request'
|
3
|
+
require 'pact/consumer_contract/request'
|
4
|
+
|
5
|
+
PACT_SPEC_DIR = "../pact-specification/testcases"
|
6
|
+
REQUEST_TEST_CASE_FOLDERS = Dir.glob("#{PACT_SPEC_DIR}/request/**")
|
7
|
+
REQUEST_TEST_CASE_FILES = Dir.glob("#{PACT_SPEC_DIR}/request/**/*.json")
|
8
|
+
|
9
|
+
TEST_DESCRIPTIONS = {true => "matches", false => "does not match"}
|
10
|
+
|
11
|
+
describe "Pact gem complicance with Pact Specification 1.0.0" do
|
12
|
+
|
13
|
+
directories = Dir.glob("#{PACT_SPEC_DIR}/*")
|
14
|
+
|
15
|
+
directories.each do | dir_name |
|
16
|
+
|
17
|
+
describe File.basename(dir_name) do
|
18
|
+
|
19
|
+
sub_directories = Dir.glob("#{dir_name}/*")
|
20
|
+
|
21
|
+
sub_directories.each do | sub_dir_name |
|
22
|
+
|
23
|
+
context File.basename(sub_dir_name) do
|
24
|
+
testcases = Dir.glob("#{sub_dir_name}/**/*.json")
|
25
|
+
|
26
|
+
testcases.each do | file_name |
|
27
|
+
|
28
|
+
context File.basename(file_name).chomp(".json") do
|
29
|
+
|
30
|
+
file_content = JSON.parse(File.read(file_name))
|
31
|
+
expected = Pact::Request::Expected.from_hash(file_content["expected"])
|
32
|
+
actual = Pact::Consumer::Request::Actual.from_hash(file_content["actual"])
|
33
|
+
expected_result = file_content.fetch("match")
|
34
|
+
comment = file_content["comment"]
|
35
|
+
|
36
|
+
it "#{TEST_DESCRIPTIONS[expected_result]} - #{comment}" do
|
37
|
+
expect(expected.matches?(actual)).to eq expected_result
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|