pact-support 1.4.0 → 1.8.0

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +143 -0
  3. data/lib/pact/configuration.rb +4 -0
  4. data/lib/pact/consumer_contract/consumer_contract.rb +19 -8
  5. data/lib/pact/consumer_contract/http_consumer_contract_parser.rb +37 -0
  6. data/lib/pact/consumer_contract/interaction.rb +57 -56
  7. data/lib/pact/consumer_contract/interaction_parser.rb +23 -0
  8. data/lib/pact/consumer_contract/interaction_v2_parser.rb +34 -0
  9. data/lib/pact/consumer_contract/interaction_v3_parser.rb +73 -0
  10. data/lib/pact/consumer_contract/pact_file.rb +24 -24
  11. data/lib/pact/consumer_contract/provider_state.rb +34 -0
  12. data/lib/pact/consumer_contract/query.rb +0 -2
  13. data/lib/pact/consumer_contract/query_hash.rb +6 -0
  14. data/lib/pact/consumer_contract/query_string.rb +2 -2
  15. data/lib/pact/consumer_contract/request.rb +1 -5
  16. data/lib/pact/consumer_contract/string_with_matching_rules.rb +17 -0
  17. data/lib/pact/matchers/multipart_form_diff_formatter.rb +41 -0
  18. data/lib/pact/matching_rules/merge.rb +43 -28
  19. data/lib/pact/matching_rules/v3/extract.rb +94 -0
  20. data/lib/pact/matching_rules/v3/merge.rb +135 -0
  21. data/lib/pact/matching_rules.rb +19 -6
  22. data/lib/pact/reification.rb +6 -3
  23. data/lib/pact/shared/multipart_form_differ.rb +14 -0
  24. data/lib/pact/specification_version.rb +18 -0
  25. data/lib/pact/support/version.rb +1 -1
  26. data/script/release.sh +1 -1
  27. data/spec/fixtures/multipart-form-diff.txt +9 -0
  28. data/spec/fixtures/not-a-pact.json +3 -0
  29. data/spec/fixtures/pact-http-v2.json +36 -0
  30. data/spec/fixtures/pact-http-v3.json +36 -0
  31. data/spec/integration/matching_rules_extract_and_merge_spec.rb +41 -4
  32. data/spec/lib/pact/consumer_contract/consumer_contract_spec.rb +54 -31
  33. data/spec/lib/pact/consumer_contract/http_consumer_contract_parser_spec.rb +25 -0
  34. data/spec/lib/pact/consumer_contract/interaction_parser_spec.rb +62 -0
  35. data/spec/lib/pact/consumer_contract/interaction_spec.rb +2 -26
  36. data/spec/lib/pact/consumer_contract/interaction_v2_parser_spec.rb +54 -0
  37. data/spec/lib/pact/consumer_contract/interaction_v3_parser_spec.rb +48 -0
  38. data/spec/lib/pact/consumer_contract/pact_file_spec.rb +9 -0
  39. data/spec/lib/pact/consumer_contract/query_hash_spec.rb +23 -0
  40. data/spec/lib/pact/matchers/multipart_form_diff_formatter_spec.rb +36 -0
  41. data/spec/lib/pact/matching_rules/merge_spec.rb +198 -110
  42. data/spec/lib/pact/matching_rules/v3/extract_spec.rb +238 -0
  43. data/spec/lib/pact/matching_rules/v3/merge_spec.rb +462 -0
  44. data/spec/lib/pact/matching_rules_spec.rb +82 -0
  45. data/spec/lib/pact/reification_spec.rb +18 -5
  46. data/spec/lib/pact/shared/multipart_form_differ_spec.rb +39 -0
  47. data/spec/support/factories.rb +5 -0
  48. data/tasks/spec.rake +0 -1
  49. metadata +40 -3
@@ -0,0 +1,36 @@
1
+ {
2
+ "consumer": {
3
+ "name": "consumer"
4
+ },
5
+ "interactions": [
6
+ {
7
+ "description": "a test request",
8
+ "providerState": "the weather is sunny",
9
+ "request": {
10
+ "method": "get",
11
+ "path": "/foo"
12
+ },
13
+ "response": {
14
+ "body": {
15
+ "foo": "bar"
16
+ },
17
+ "matchingRules": {
18
+ "body": {
19
+ "$.foo": {
20
+ "matchers": [{ "match": "type" }]
21
+ }
22
+ }
23
+ },
24
+ "status": 200
25
+ }
26
+ }
27
+ ],
28
+ "provider": {
29
+ "name": "provider"
30
+ },
31
+ "metadata": {
32
+ "pactSpecification": {
33
+ "version": "3.0"
34
+ }
35
+ }
36
+ }
@@ -1,7 +1,9 @@
1
1
  require 'pact/term'
2
2
  require 'pact/something_like'
3
3
  require 'pact/matching_rules/extract'
4
+ require 'pact/matching_rules/v3/extract'
4
5
  require 'pact/matching_rules/merge'
6
+ require 'pact/matching_rules/v3/merge'
5
7
  require 'pact/reification'
6
8
 
7
9
  describe "converting Pact::Term and Pact::SomethingLike to matching rules and back again" do
@@ -10,6 +12,9 @@ describe "converting Pact::Term and Pact::SomethingLike to matching rules and ba
10
12
  let(:matching_rules) { Pact::MatchingRules::Extract.(expected) }
11
13
  let(:recreated_expected) { Pact::MatchingRules::Merge.(example, matching_rules)}
12
14
 
15
+ let(:recreated_expected_v3) { Pact::MatchingRules::V3::Merge.(example, matching_rules_v3) }
16
+ let(:matching_rules_v3) { Pact::MatchingRules::V3::Extract.(expected) }
17
+
13
18
  context "with a Pact::Term" do
14
19
  let(:expected) do
15
20
  {
@@ -21,9 +26,29 @@ describe "converting Pact::Term and Pact::SomethingLike to matching rules and ba
21
26
  }
22
27
  end
23
28
 
24
- it "recreates the same object hierarchy" do
29
+ it "recreates the same object hierarchy with v2 matching" do
30
+ expect(recreated_expected).to eq expected
31
+ end
32
+
33
+ it "recreates the same object hierarchy with v3 matching" do
34
+ expect(recreated_expected_v3).to eq expected
35
+ end
36
+ end
37
+
38
+ context "with a Pact::SomethingLike containing a Pact::ArrayLike" do
39
+ let(:expected) do
40
+ {
41
+ body: Pact::SomethingLike.new(children: Pact::ArrayLike.new("foo", min: 2))
42
+ }
43
+ end
44
+
45
+ it "recreates the same object hierarchy with v2 matching" do
25
46
  expect(recreated_expected).to eq expected
26
47
  end
48
+
49
+ it "recreates the same object hierarchy with v3 matching" do
50
+ expect(recreated_expected_v3).to eq expected
51
+ end
27
52
  end
28
53
 
29
54
  context "with a Pact::SomethingLike" do
@@ -37,9 +62,13 @@ describe "converting Pact::Term and Pact::SomethingLike to matching rules and ba
37
62
  }
38
63
  end
39
64
 
40
- it "recreates the same object hierarchy" do
65
+ it "recreates the same object hierarchy with v2 matching" do
41
66
  expect(recreated_expected).to eq expected
42
67
  end
68
+
69
+ it "recreates the same object hierarchy with v3 matching" do
70
+ expect(recreated_expected_v3).to eq expected
71
+ end
43
72
  end
44
73
 
45
74
  context "with a Pact::SomethingLike containing a Hash" do
@@ -61,9 +90,13 @@ describe "converting Pact::Term and Pact::SomethingLike to matching rules and ba
61
90
  }
62
91
  end
63
92
 
64
- it "recreates the same object hierarchy" do
93
+ it "recreates the same object hierarchy with v2 matching" do
65
94
  expect(recreated_expected).to eq expected
66
95
  end
96
+
97
+ it "recreates the same object hierarchy with v3 matching" do
98
+ expect(recreated_expected_v3).to eq expected
99
+ end
67
100
  end
68
101
 
69
102
  context "with a Pact::SomethingLike containing an Array" do
@@ -83,8 +116,12 @@ describe "converting Pact::Term and Pact::SomethingLike to matching rules and ba
83
116
  }
84
117
  end
85
118
 
86
- it "recreates the same object hierarchy" do
119
+ it "recreates the same object hierarchy with v2 matching" do
87
120
  expect(recreated_expected).to eq expected
88
121
  end
122
+
123
+ it "recreates the same object hierarchy with v3 matching" do
124
+ expect(recreated_expected_v3).to eq expected
125
+ end
89
126
  end
90
127
  end
@@ -3,50 +3,73 @@ require 'pact/consumer_contract'
3
3
 
4
4
  module Pact
5
5
  describe ConsumerContract do
6
+ describe "from_uri" do
7
+ context "when the URL does not point to a valid pact" do
8
+ subject { ConsumerContract.from_uri('spec/fixtures/not-a-pact.json') }
9
+
10
+ it "raises a helpful error" do
11
+ expect { subject }.to raise_error UnrecognizePactFormatError, /Please check that spec/
12
+ end
13
+ end
14
+ end
15
+
16
+ describe "from_hash" do
17
+ context "when the hash is not a valid pact" do
18
+ subject { ConsumerContract.from_hash({'foo' => 'bar'}) }
19
+
20
+ it "raises a helpful error" do
21
+ expect { subject }.to raise_error UnrecognizePactFormatError, 'This document does not use a recognised Pact format: {"foo"=>"bar"}'
22
+ end
23
+ end
24
+ end
6
25
 
7
26
  describe ".from_json" do
27
+
8
28
  let(:loaded_pact) { ConsumerContract.from_json(string) }
9
- context "when the top level object is a ConsumerContract" do
10
- let(:string) { '{"interactions":[{"request": {"path":"/path", "method" : "get"}, "response": {"status" : 200}}], "consumer": {"name" : "Bob"} , "provider": {"name" : "Mary"} }' }
11
29
 
12
- it "should create a Pact" do
13
- expect(loaded_pact).to be_instance_of ConsumerContract
14
- end
30
+ context "with an HTTP contract" do
31
+ context "when the top level object is a ConsumerContract" do
32
+ let(:string) { '{"interactions":[{"request": {"path":"/path", "method" : "get"}, "response": {"status" : 200}}], "consumer": {"name" : "Bob"} , "provider": {"name" : "Mary"} }' }
15
33
 
16
- it "should have interactions" do
17
- expect(loaded_pact.interactions).to be_instance_of Array
18
- end
34
+ it "should create a Pact" do
35
+ expect(loaded_pact).to be_instance_of ConsumerContract
36
+ end
19
37
 
20
- it "should have a consumer" do
21
- expect(loaded_pact.consumer).to be_instance_of Pact::ServiceConsumer
22
- end
38
+ it "should have interactions" do
39
+ expect(loaded_pact.interactions).to be_instance_of Array
40
+ end
23
41
 
24
- it "should have a provider" do
25
- expect(loaded_pact.provider).to be_instance_of Pact::ServiceProvider
26
- end
27
- end
42
+ it "should have a consumer" do
43
+ expect(loaded_pact.consumer).to be_instance_of Pact::ServiceConsumer
44
+ end
28
45
 
29
- context "with old 'producer' key" do
30
- let(:string) { File.read('./spec/support/a_consumer-a_producer.json')}
31
- it "should create a Pact" do
32
- expect(loaded_pact).to be_instance_of ConsumerContract
46
+ it "should have a provider" do
47
+ expect(loaded_pact.provider).to be_instance_of Pact::ServiceProvider
48
+ end
33
49
  end
34
50
 
35
- it "should have interactions" do
36
- expect(loaded_pact.interactions).to be_instance_of Array
37
- end
51
+ context "with old 'producer' key" do
52
+ let(:string) { File.read('./spec/support/a_consumer-a_producer.json')}
53
+ it "should create a Pact" do
54
+ expect(loaded_pact).to be_instance_of ConsumerContract
55
+ end
38
56
 
39
- it "should have a consumer" do
40
- expect(loaded_pact.consumer).to be_instance_of Pact::ServiceConsumer
41
- end
57
+ it "should have interactions" do
58
+ expect(loaded_pact.interactions).to be_instance_of Array
59
+ end
42
60
 
43
- it "should have a provider" do
44
- expect(loaded_pact.provider).to be_instance_of Pact::ServiceProvider
45
- expect(loaded_pact.provider.name).to eq "an old producer"
46
- end
61
+ it "should have a consumer" do
62
+ expect(loaded_pact.consumer).to be_instance_of Pact::ServiceConsumer
63
+ end
47
64
 
48
- it "should have a provider_state" do
49
- expect(loaded_pact.interactions.first.provider_state).to eq 'state one'
65
+ it "should have a provider" do
66
+ expect(loaded_pact.provider).to be_instance_of Pact::ServiceProvider
67
+ expect(loaded_pact.provider.name).to eq "an old producer"
68
+ end
69
+
70
+ it "should have a provider_state" do
71
+ expect(loaded_pact.interactions.first.provider_state).to eq 'state one'
72
+ end
50
73
  end
51
74
  end
52
75
  end
@@ -0,0 +1,25 @@
1
+ require 'pact/consumer_contract/http_consumer_contract_parser'
2
+
3
+ module Pact
4
+ describe HttpConsumerContractParser do
5
+ describe "#call integration test" do
6
+ subject { HttpConsumerContractParser.new.call(pact_hash) }
7
+
8
+ context "with a v2 pact" do
9
+ let(:pact_hash) { load_json_fixture('pact-http-v2.json') }
10
+
11
+ it "correctly parses the pact" do
12
+ expect(subject.interactions.first.response.headers['Content-Type']).to be_a(Pact::Term)
13
+ end
14
+ end
15
+
16
+ context "with a v3 pact" do
17
+ let(:pact_hash) { load_json_fixture('pact-http-v3.json') }
18
+
19
+ it "correctly parses the pact" do
20
+ expect(subject.interactions.first.response.body['foo']).to be_a(Pact::SomethingLike)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,62 @@
1
+ require 'pact/consumer_contract/interaction_parser'
2
+
3
+ module Pact
4
+ describe InteractionParser do
5
+ describe ".call" do
6
+
7
+ let(:request) { {method: 'get', path: 'path'} }
8
+ let(:response) { {} }
9
+
10
+ context "when providerState has been used instead of provider_state" do
11
+
12
+ subject { InteractionParser.call('response' => response, 'request' => request, 'providerState' => 'some state') }
13
+
14
+ it "recognises the provider state" do
15
+ expect(subject.provider_state).to eq 'some state'
16
+ end
17
+ end
18
+
19
+ context "when there are matching rules" do
20
+ let(:hash) { load_json_fixture 'interaction-with-matching-rules.json' }
21
+
22
+ subject { InteractionParser.call(hash, pact_specification_version: Pact::SpecificationVersion.new("2")) }
23
+
24
+ it "merges the rules with the example for the request" do
25
+ expect(subject.request.body['name']).to be_instance_of(Pact::Term)
26
+ end
27
+
28
+ it "merges the rules with the example for the response" do
29
+ expect(subject.response.body['_links']['self']['href']).to be_instance_of(Pact::Term)
30
+ end
31
+ end
32
+
33
+ context "when the request body is a String" do
34
+ let(:hash) { { 'request' => request, 'response' => response } }
35
+ subject { InteractionParser.call(hash, pact_specification_version: Pact::SpecificationVersion.new("3")) }
36
+
37
+ let(:request) { { 'method' => 'get', 'path' => 'path' , 'body' => "<xml></xml>", 'matchingRules' => {"body" => {"foo" => "bar"} } } }
38
+
39
+ it "returns an interaction with an StringWithMatchingRules in the request" do
40
+ expect(subject.request.body).to be_a(Pact::StringWithMatchingRules)
41
+ expect(subject.request.body).to eq "<xml></xml>"
42
+ expect(subject.request.body.matching_rules).to eq "foo" => "bar"
43
+ expect(subject.request.body.pact_specification_version).to eq Pact::SpecificationVersion.new("3")
44
+ end
45
+ end
46
+
47
+ context "when the response body is a String" do
48
+ let(:hash) { { 'request' => request, 'response' => response } }
49
+ subject { InteractionParser.call(hash, pact_specification_version: Pact::SpecificationVersion.new("3")) }
50
+
51
+ let(:response) { { 'status' => '200', 'body' => "<xml></xml>", 'matchingRules' => {"body" => {"foo" => "bar"} } } }
52
+
53
+ it "returns an interaction with an StringWithMatchingRules in the response" do
54
+ expect(subject.response.body).to be_a(Pact::StringWithMatchingRules)
55
+ expect(subject.response.body).to eq "<xml></xml>"
56
+ expect(subject.response.body.matching_rules).to eq "foo" => "bar"
57
+ expect(subject.response.body.pact_specification_version).to eq Pact::SpecificationVersion.new("3")
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -38,12 +38,14 @@ module Pact
38
38
 
39
39
  describe "matches_criteria?" do
40
40
  subject { InteractionFactory.create(:description => 'a request for food') }
41
+
41
42
  context "by description" do
42
43
  context "when the interaction matches" do
43
44
  it "returns true" do
44
45
  expect(subject.matches_criteria?(:description => /request.*food/)).to be true
45
46
  end
46
47
  end
48
+
47
49
  context "when the interaction does not match" do
48
50
  it "returns false" do
49
51
  expect(subject.matches_criteria?(:description => /blah/)).to be false
@@ -52,33 +54,7 @@ module Pact
52
54
  end
53
55
  end
54
56
 
55
- describe "from_hash" do
56
- context "when providerState has been used instead of provider_state" do
57
-
58
- subject { Interaction.from_hash('response' => response, 'request' => request, 'providerState' => 'some state') }
59
-
60
- it "recognises the provider state" do
61
- expect(subject.provider_state).to eq 'some state'
62
- end
63
- end
64
-
65
- context "when there are matching rules" do
66
- let(:hash) { load_json_fixture 'interaction-with-matching-rules.json' }
67
-
68
- subject { Interaction.from_hash hash }
69
-
70
- it "merges the rules with the example for the request" do
71
- expect(subject.request.body['name']).to be_instance_of(Pact::Term)
72
- end
73
-
74
- it "merges the rules with the example for the response" do
75
- expect(subject.response.body['_links']['self']['href']).to be_instance_of(Pact::Term)
76
- end
77
- end
78
- end
79
-
80
57
  describe "request_modifies_resource_without_checking_response_body?" do
81
-
82
58
  let(:interaction) { Interaction.new(request: request, response: response)}
83
59
 
84
60
  subject { interaction.request_modifies_resource_without_checking_response_body?}
@@ -0,0 +1,54 @@
1
+ require 'pact/consumer_contract/interaction_v2_parser'
2
+
3
+ module Pact
4
+ describe InteractionV2Parser do
5
+ describe ".call" do
6
+ let(:interaction_hash) do
7
+ {
8
+ "description" => "description",
9
+ "request" => { "method" => "GET", "path" => "/" },
10
+ "response" => { "status" => 200 },
11
+ "providerState" => "foo"
12
+ }
13
+ end
14
+
15
+ let(:options) do
16
+ {
17
+ pact_specification_version: Pact::SpecificationVersion.new("3.0")
18
+ }
19
+ end
20
+
21
+ subject { InteractionV2Parser.call(interaction_hash, options) }
22
+
23
+ describe "provider_states" do
24
+ it "returns an array of provider states with size 1" do
25
+ expect(subject.provider_states.size).to eq 1
26
+ end
27
+
28
+ it "sets the name of the provider state to the string provided" do
29
+ expect(subject.provider_states.first.name)
30
+ end
31
+
32
+ it "sets the params to an empty hash" do
33
+ expect(subject.provider_states.first.params).to eq({})
34
+ end
35
+
36
+ context "when the providerState is nil" do
37
+ before do
38
+ interaction_hash["providerState"] = nil
39
+ end
40
+
41
+ it "returns an empty list" do
42
+ expect(subject.provider_states).to be_empty
43
+ end
44
+ end
45
+ end
46
+
47
+ describe "provider_state" do
48
+ it "sets the name from the hash" do
49
+ expect(subject.provider_state).to eq "foo"
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,48 @@
1
+ require 'pact/consumer_contract/interaction_v3_parser'
2
+
3
+ module Pact
4
+ describe InteractionV3Parser do
5
+ describe ".call" do
6
+
7
+ let(:interaction_hash) do
8
+ {
9
+ "description" => "description",
10
+ "request" => { "method" => "GET", "path" => "/" },
11
+ "response" => { "status" => 200 },
12
+ "providerStates" => [{
13
+ "name" => "foo",
14
+ "params" => {"a" => "b"}
15
+ }]
16
+ }
17
+ end
18
+
19
+ let(:options) do
20
+ {
21
+ pact_specification_version: Pact::SpecificationVersion.new("3.0")
22
+ }
23
+ end
24
+
25
+ subject { InteractionV3Parser.call(interaction_hash, options) }
26
+
27
+ describe "provider_states" do
28
+ it "parses the array of provider states" do
29
+ expect(subject.provider_states.size).to eq 1
30
+ end
31
+
32
+ it "parses the name of each" do
33
+ expect(subject.provider_states.first.name)
34
+ end
35
+
36
+ it "parses the params of each" do
37
+ expect(subject.provider_states.first.params).to eq "a" => "b"
38
+ end
39
+ end
40
+
41
+ describe "provider_state" do
42
+ it "sets the provider_state string to the name of the first providerState for backwards compatibility while we implement v3" do
43
+ expect(subject.provider_state).to eq "foo"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -25,6 +25,15 @@ module Pact
25
25
  end
26
26
  end
27
27
 
28
+ describe 'with backslashes to a local path' do
29
+ let(:windows_path) { 'spec\support\a_consumer-a_provider.json' }
30
+ let(:unix_path) { 'spec/support/a_consumer-a_provider.json' }
31
+
32
+ it 'transforms them to forward slashes' do
33
+ expect(PactFile.read(windows_path)).to eq PactFile.read(unix_path)
34
+ end
35
+ end
36
+
28
37
  context 'without basic authentication' do
29
38
  before do
30
39
  stub_request(:get, uri_without_userinfo).to_return(body: pact_content)
@@ -72,6 +72,15 @@ module Pact
72
72
  expect(subject.difference(other).keys).to contain_exactly(:"param[a]", :"param[a][aa]", :"param[a][bb]")
73
73
  end
74
74
  end
75
+
76
+ context "when there is an ArrayLike" do
77
+ let(:query) { { param: Pact.each_like("1") } }
78
+ let(:other) { QueryString.new('param=1&param=2') }
79
+
80
+ it 'returns an empty diff' do
81
+ expect(subject.difference(other)).to be_empty
82
+ end
83
+ end
75
84
  end
76
85
  end
77
86
 
@@ -82,6 +91,20 @@ module Pact
82
91
  end
83
92
 
84
93
  describe "#to_json" do
94
+ context "when the query contains an ArrayLike" do
95
+ let(:query) { { foo: Pact.each_like("1"), bar: "2" } }
96
+ let(:expected_json) do
97
+ {
98
+ foo: Pact.each_like("1"),
99
+ bar: ["2"]
100
+ }.to_json
101
+ end
102
+
103
+ it "serialises the ArrayLike without wrapping an array around it" do
104
+ expect(subject.to_json).to eq expected_json
105
+ end
106
+ end
107
+
85
108
  context "when the query contains a Pact::Term" do
86
109
  let(:term) { Pact::Term.new(generate: "thing", matcher: /th/) }
87
110
  let(:query) { { param: term } }
@@ -0,0 +1,36 @@
1
+ require 'pact/matchers/multipart_form_diff_formatter'
2
+
3
+ module Pact
4
+ module Matchers
5
+ describe MultipartFormDiffFormatter do
6
+ describe ".call" do
7
+ subject { MultipartFormDiffFormatter.call(diff, options)}
8
+
9
+ let(:diff) do
10
+ {
11
+ headers: header_diff,
12
+ body: body_diff
13
+ }
14
+ end
15
+
16
+ let(:header_diff) do
17
+ {
18
+ "Content-Type" => Difference.new("foo", "bar", "Wrong header")
19
+ }
20
+ end
21
+
22
+ let(:body_diff) do
23
+ Difference.new("foo", "bar", "A message")
24
+ end
25
+
26
+ let(:options) { {} }
27
+
28
+ let(:expected_output) { File.read("spec/fixtures/multipart-form-diff.txt")}
29
+
30
+ it "formats the diff" do
31
+ expect(subject).to eq expected_output
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end