Skip to content

Commit a09121a

Browse files
authored
Merge pull request #359 from pact-foundation/docs/pact_net_spec_version
Docs/pact net spec version
2 parents 5668331 + d782681 commit a09121a

File tree

9 files changed

+549
-21
lines changed

9 files changed

+549
-21
lines changed

scripts/sync/pact_net.rb

+11-5
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,25 @@
44

55
SOURCE_REPO = 'pact-foundation/pact-net'
66
DESTINATION_DIR = relative_path_to('docs/implementation_guides/net')
7-
TRANSFORM_PATH = -> (path) { File.join(DESTINATION_DIR, path.downcase) }
7+
TRANSFORM_PATH = ->(path) { File.join(DESTINATION_DIR, path.downcase) }
88
INCLUDE = [
9-
->(path) { %w{README.md}.include?(path) }
9+
->(path) { %w[README.md].include?(path) },
10+
->(path) { path.start_with?('docs') && path.end_with?('md') }
1011
]
1112
IGNORE = []
1213

1314
CUSTOM_ACTIONS = [
15+
[:all, lambda { |md_file_contents|
16+
md_file_contents.find_and_replace_underlined_headings
17+
}],
1418
[:all, ->(md_file_contents) { md_file_contents.extract_title } ],
15-
[:all, ->(md_file_contents) { md_file_contents.find_and_replace("```c#", "```csharp") } ],
16-
["README.md", ->(md_file_contents) { md_file_contents.fields[:title] = "README"; md_file_contents.fields[:slug] = "./readme" } ]
19+
[:all, ->(md_file_contents) { md_file_contents.find_and_replace('```c#', '```csharp') }],
20+
['README.md', lambda { |md_file_contents|
21+
md_file_contents.fields[:title] = 'README'
22+
md_file_contents.fields[:slug] = './readme'
23+
}]
1724
]
1825

19-
2026
FileUtils.mkdir_p DESTINATION_DIR
2127

2228
sync(SOURCE_REPO, INCLUDE, IGNORE, TRANSFORM_PATH, CUSTOM_ACTIONS)

scripts/sync/support.rb

+12
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,18 @@ def find_and_replace(find, replace)
8383
@lines = lines.collect{ |line| line.gsub(find, replace) }
8484
end
8585

86+
def find_and_replace_underlined_headings
87+
@lines.each_with_index do |line, index|
88+
if line.strip.start_with?('====')
89+
@lines[index - 1] = "# #{@lines[index - 1].strip}"
90+
@lines.delete_at(index)
91+
elsif line.strip.start_with?('------')
92+
@lines[index - 1] = "## #{@lines[index - 1].strip}"
93+
@lines.delete_at(index)
94+
end
95+
end
96+
end
97+
8698
def remove_lines_including(substring)
8799
@lines = lines.select{ |line| !line.include?(substring) }
88100
end

website/docs/implementation_guides/net.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: Overview
33
---
44

5-
PactNet is currently compliant to Pact Specification Version 2.0.
5+
PactNet is currently compliant up to and including Pact Specification Version 4.0, excluding pact plugins.
66

77
Head to the [README](./net/README) to get started using Pact in .NET \(C\#\).
88

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
---
2+
title: Messaging Pacts
3+
custom_edit_url: https://github.com/pact-foundation/pact-net/edit/master/docs/messaging-pacts.md
4+
---
5+
<!-- This file has been synced from the pact-foundation/pact-net repository. Please do not edit it directly. The URL of the source file can be found in the custom_edit_url value above -->
6+
7+
Messaging pacts work similarly to synchronous request/response pacts in that there is still a consumer
8+
and a producer. The consumer defines the messages it expects to receive (including metadata and the
9+
message contents) and the producer is verified to ensure that the messages it produces meet those
10+
expectations.
11+
12+
It's important to ensure that any given consumer or provider name doesn't have both request/response and
13+
messaging pacts. If you have a HTTP API which also sends and/or receives messages, make sure the two different
14+
types use two different names, for example "Stock Broker API" and "Stock Broker Messaging".
15+
16+
## Sample
17+
18+
See the [sample](https://github.com/pact-foundation/pact-net/blob/master/samples/OrdersApi) for additional detail.
19+
20+
## Consumer Tests
21+
22+
Consumer tests are very similar to request/response pacts. Your consumer specifies which messages it wishes
23+
to receive and PactNet generates a pact file which contains all of the specified interactions.
24+
25+
In code, this is:
26+
27+
```csharp
28+
public class StockEventProcessorTests
29+
{
30+
private readonly IMessagePactBuilderV4 messagePact;
31+
32+
public StockEventProcessorTests(ITestOutputHelper output)
33+
{
34+
IPactV4 v4 = Pact.V4("Stock Event Consumer", "Stock Event Producer", new PactConfig
35+
{
36+
PactDir = "../../../pacts/",
37+
DefaultJsonSettings = new JsonSerializerOptions
38+
{
39+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
40+
},
41+
Outputters = new[]
42+
{
43+
new XUnitOutput(output)
44+
}
45+
});
46+
47+
this.messagePact = v4.WithMessageInteractions();
48+
}
49+
50+
[Fact]
51+
public void ReceiveSomeStockEvents()
52+
{
53+
this.messagePact
54+
.ExpectsToReceive("some stock ticker events")
55+
.Given("A list of events is pushed to the queue")
56+
.WithMetadata("key", "valueKey")
57+
.WithJsonContent(Match.MinType(new
58+
{
59+
Name = Match.Type("AAPL"),
60+
Price = Match.Decimal(1.23m),
61+
Timestamp = Match.Type(14.February(2022).At(13, 14, 15, 678))
62+
}, 1))
63+
.Verify<ICollection<StockEvent>>(events =>
64+
{
65+
events.Should().BeEquivalentTo(new[]
66+
{
67+
new StockEvent
68+
{
69+
Name = "AAPL",
70+
Price = 1.23m,
71+
Timestamp = 14.February(2022).At(13, 14, 15, 678)
72+
}
73+
});
74+
});
75+
}
76+
}
77+
```
78+
79+
After all of your consumer tests have passed a message pact file is written to disk. This file will be used
80+
during the provider verification tests.
81+
82+
## Provider Tests
83+
84+
Provider tests look very similar to request/response pacts, but with one big difference; you must register a
85+
handler for each interaction which generates a sample message.
86+
87+
The key difference for mesaging pacts is that the transport used is not via HTTP. It would be unreasonable
88+
for PactNet to attempt to implement all the different transports that these messages could use - such as
89+
Kafka, RabbitMQ, ZeroMQ, etc - and so internally the messages are simulated during the provider verification
90+
stage. This is done transparently and so you don't need to worry about how this is achieved.
91+
92+
In code, this is:
93+
94+
```csharp
95+
public class StockEventGeneratorTests : IDisposable
96+
{
97+
private readonly PactVerifier verifier;
98+
99+
public StockEventGeneratorTests()
100+
{
101+
this.verifier = new PactVerifier("Stock Event Producer");
102+
}
103+
104+
public void Dispose()
105+
{
106+
// make sure you dispose the verifier to stop the internal messaging server
107+
GC.SuppressFinalize(this);
108+
this.verifier.Dispose();
109+
}
110+
111+
[Fact]
112+
public void EnsureEventApiHonoursPactWithConsumer()
113+
{
114+
string pactPath = Path.Combine("..",
115+
"..",
116+
"..",
117+
"..",
118+
"Consumer.Tests",
119+
"pacts",
120+
"Stock Event Consumer-Stock Event Producer.json");
121+
122+
var defaultSettings = new JsonSerializerOptions
123+
{
124+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
125+
};
126+
127+
this.verifier
128+
.WithMessages(scenarios =>
129+
{
130+
// register the responses to each interaction
131+
// the descriptions must match those in the pact file(s)
132+
scenarios.Add("a single event", () => new StockEvent
133+
{
134+
Name = "AAPL",
135+
Price = 1.23m
136+
})
137+
.Add("some stock ticker events", builder =>
138+
{
139+
builder.WithMetadata(new
140+
{
141+
ContentType = "application/json",
142+
Key = "value"
143+
})
144+
.WithContent(new[]
145+
{
146+
new StockEvent { Name = "AAPL", Price = 1.23m },
147+
new StockEvent { Name = "TSLA", Price = 4.56m }
148+
});
149+
});
150+
}, defaultSettings)
151+
.WithFileSource(new FileInfo(pactPath))
152+
.Verify();
153+
}
154+
}
155+
```

0 commit comments

Comments
 (0)