Skip to content

Commit 4ce156a

Browse files
authored
Fix missing signatures (#51)
# Description The WebSocket class was abstracting the `System.net.WebSockets.ClientWebSocket` object by storing it in a `dynamic` variable. This was made to allow mocking the ClientWebSocket class (which is final and thus cannot be extended) via duck typing, using a custom `IClientWebSocket` interface which reproduces parts of the ClientWebSocket API. Problem is: it seems that the Visual Studio C# linker is smart enough to remove unused functions from compiled applications, but it's unable to detect functions used through dynamic variables. This made ClientWebSocket functions stripped from applications using our SDK, making it useless with that protocol. This PR fixes this situation by making our WebSocket protocol implementation use only objects implementing our IClientWebSocket interface, completely removing duck typing. I added a humble object creating and wrapping ClientWebSocket instances to make that class compatible with our custom interface. That humble object explicitly uses ClientWebSocket functions, forcing the C# linker to keep them in the compiled product. Mocking is made by injecting a custom mock objecting implementing that interface. # Other changes * Fix all warnings (99% of them being missing XML documentation) # How to review The actual fix is in the Kuzzle/Protocol/WebSocket.cs file (and its unit test counterpart). All other changes are about fixing warnings.
1 parent aec1d5b commit 4ce156a

25 files changed

+528
-100
lines changed

.travis.yml

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,20 @@ jobs:
3535
name: Dead link check
3636
if: type = pull_request OR type = push AND branch =~ /^master|[0-9]+-(dev|stable)$/ OR type = cron
3737
language: node_js
38-
node_js: 10
39-
38+
node_js: 12
39+
cache:
40+
directories:
41+
- $HOME/.gem/specs
42+
install:
43+
- gem install typhoeus
44+
- npm ci --silent
4045
before_script:
41-
- npm ci
4246
- npm run doc-prepare
4347
- $(npm bin)/kuzdoc iterate-repos:install --repos_path doc/framework/.repos/
4448
- $(npm bin)/kuzdoc framework:link -d /sdk/csharp/2/ -v 2
4549
script:
46-
- gem install typhoeus
47-
- cd doc/framework/ && HYDRA_MAX_CONCURRENCY=20 ruby .ci/dead-links.rb -p src/sdk/csharp/2/
50+
- cd doc/framework/
51+
- HYDRA_MAX_CONCURRENCY=20 ruby .ci/dead-links.rb -p src/sdk/csharp/2/
4852

4953
- stage: Tests
5054
name: Build documentation
@@ -58,7 +62,7 @@ jobs:
5862

5963
- stage: Deployments
6064
name: NuGet
61-
if: tag IS present AND branch = master
65+
if: type = push AND branch =~ /^(master|[0-9]+-stable)$/
6266
language: csharp
6367
solution: sdk-csharp.sln
6468
sudo: required
@@ -88,7 +92,6 @@ jobs:
8892
language: node_js
8993
node_js: 10
9094
env:
91-
- BRANCH=dev
9295
- NODE_ENV=production
9396
- S3_BUCKET=docs-next.kuzzle.io
9497
- CLOUDFRONT_DISTRIBUTION_ID=E2ZCCEK9GRB49U
@@ -102,7 +105,6 @@ jobs:
102105

103106
install:
104107
- pip install awscli --upgrade --user
105-
- npm ci
106108

107109
script:
108110
- npm run doc-prepare
@@ -113,8 +115,6 @@ jobs:
113115
script:
114116
- npm run doc-upload
115117
skip_cleanup: true
116-
on:
117-
all_branches: true
118118

119119
after_deploy:
120120
- npm run doc-cloudfront
@@ -138,7 +138,6 @@ jobs:
138138

139139
install:
140140
- pip install awscli --upgrade --user
141-
- npm ci
142141

143142
script:
144143
- npm run doc-prepare
@@ -149,8 +148,6 @@ jobs:
149148
script:
150149
- npm run doc-upload
151150
skip_cleanup: true
152-
on:
153-
all_branches: true
154151

155152
after_deploy:
156153
- npm run doc-cloudfront

Kuzzle.Tests/Protocol/WebSocketTest.cs

Lines changed: 62 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,78 @@
11
using System;
22
using System.Threading;
33
using System.Threading.Tasks;
4+
using System.Net.WebSockets;
45
using KuzzleSdk.Protocol;
56
using Moq;
67
using Xunit;
78

89
namespace Kuzzle.Tests.Protocol {
9-
public class TestableWebSocket : WebSocket {
10-
internal Mock<IClientWebSocket> MockSocket;
11-
public int StateChangesCount = 0;
12-
public ProtocolState LastStateDispatched = ProtocolState.Closed;
10+
public class MockClientWebSocketAdapter : IClientWebSocket {
11+
public Mock<IClientWebSocket> mockedSocket;
12+
public WebSocketState _state = WebSocketState.Open;
1313

14-
public TestableWebSocket(Uri uri) : base(uri) {
15-
StateChanged += (sender, e) => {
16-
StateChangesCount++;
17-
LastStateDispatched = e;
18-
};
14+
public WebSocketState State {
15+
get { return _state; }
1916
}
2017

21-
internal override dynamic CreateClientSocket() {
22-
MockSocket = new Mock<IClientWebSocket>();
18+
public MockClientWebSocketAdapter () {
19+
mockedSocket = new Mock<IClientWebSocket>();
2320

24-
MockSocket
21+
mockedSocket
2522
.Setup(s => s.ConnectAsync(
2623
It.IsAny<Uri>(), It.IsAny<CancellationToken>()))
2724
.Returns(Task.CompletedTask);
25+
}
26+
27+
public Task ConnectAsync(Uri uri, CancellationToken cancellationToken) {
28+
return mockedSocket.Object.ConnectAsync(uri, cancellationToken);
29+
}
30+
31+
public Task SendAsync(
32+
ArraySegment<byte> buffer,
33+
WebSocketMessageType msgType,
34+
bool endOfMessage,
35+
CancellationToken cancellationToken
36+
) {
37+
return mockedSocket.Object.SendAsync(
38+
buffer,
39+
msgType,
40+
endOfMessage,
41+
cancellationToken);
42+
}
43+
44+
public Task<WebSocketReceiveResult> ReceiveAsync(
45+
ArraySegment<byte> buffer,
46+
CancellationToken cancellationToken
47+
) {
48+
Task.Delay(10000).Wait();
49+
return mockedSocket.Object.ReceiveAsync(buffer, cancellationToken);
50+
}
51+
52+
public void Abort() {
53+
mockedSocket.Object.Abort();
54+
}
55+
}
56+
57+
public class TestableWebSocket : AbstractWebSocket {
58+
public Mock<IClientWebSocket> mockedSocket;
59+
public int StateChangesCount = 0;
60+
public ProtocolState LastStateDispatched = ProtocolState.Closed;
2861

29-
MockSocket.Object.State = System.Net.WebSockets.WebSocketState.Open;
62+
public TestableWebSocket(Uri uri)
63+
: base(typeof(MockClientWebSocketAdapter), uri)
64+
{
65+
StateChanged += (sender, e) => {
66+
StateChangesCount++;
67+
LastStateDispatched = e;
68+
};
69+
}
3070

31-
return MockSocket.Object;
71+
public override async Task ConnectAsync(
72+
CancellationToken cancellationToken
73+
) {
74+
await base.ConnectAsync(cancellationToken);
75+
mockedSocket = ((MockClientWebSocketAdapter)socket).mockedSocket;
3276
}
3377
}
3478

@@ -55,7 +99,7 @@ public void ConstructorRejectsNullUri() {
5599
}
56100

57101
[Fact]
58-
public async void ConnectAsyncTest() {
102+
public async Task ConnectAsyncTest() {
59103
await _ws.ConnectAsync(CancellationToken.None);
60104

61105
Assert.NotNull(_ws.socket);
@@ -64,7 +108,7 @@ public async void ConnectAsyncTest() {
64108
await _ws.ConnectAsync(CancellationToken.None);
65109
await _ws.ConnectAsync(CancellationToken.None);
66110

67-
_ws.MockSocket.Verify(
111+
_ws.mockedSocket.Verify(
68112
s => s.ConnectAsync(uri, CancellationToken.None),
69113
Times.Once);
70114

@@ -74,7 +118,7 @@ public async void ConnectAsyncTest() {
74118
}
75119

76120
[Fact]
77-
public async void DisconnectTest() {
121+
public async Task DisconnectTest() {
78122
await _ws.ConnectAsync(CancellationToken.None);
79123

80124
Assert.Equal(ProtocolState.Open, _ws.State);
@@ -91,7 +135,7 @@ public async void DisconnectTest() {
91135
Assert.Equal(2, _ws.StateChangesCount);
92136
Assert.Equal(ProtocolState.Closed, _ws.LastStateDispatched);
93137

94-
_ws.MockSocket.Verify(s => s.Abort(), Times.Once);
138+
_ws.mockedSocket.Verify(s => s.Abort(), Times.Once);
95139
}
96140

97141
[Fact]

Kuzzle/API/Controllers/AdminController.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
using Newtonsoft.Json.Linq;
33

44
namespace KuzzleSdk.API.Controllers {
5+
/// <summary>
6+
/// Implement the "admin" controller of the Kuzzle API
7+
/// </summary>
58
public class AdminController : BaseController {
69
internal AdminController(IKuzzleApi k) : base(k) { }
710

Kuzzle/API/Controllers/RealtimeController.cs

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,14 @@
1010
using static KuzzleSdk.API.Controllers.RealtimeController;
1111

1212
namespace KuzzleSdk.API.Controllers {
13-
1413
internal interface IRealtimeController {
1514
Task<string> SubscribeAndAddToRecoverer(
1615
string index, string collection, JObject filters,
1716
NotificationHandler handler, SubscribeOptions options = null, bool addToRecoverer = true);
1817
}
1918

2019
/// <summary>
21-
/// Implements the "realtime" Kuzzle API controller
20+
/// Implement the "realtime" Kuzzle API controller
2221
/// </summary>
2322
public sealed class RealtimeController : BaseController, IRealtimeController {
2423
/// <summary>
@@ -112,7 +111,7 @@ internal RealtimeController(IKuzzleApi api) : base(api) {
112111
}
113112

114113
/// <summary>
115-
/// Releases unmanaged resources and performs other cleanup operations
114+
/// Releases unmanaged resources and performs other cleanup operations
116115
/// before the <see cref="T:KuzzleSdk.API.Controllers.RealtimeController"/>
117116
/// is reclaimed by garbage collection.
118117
/// </summary>
@@ -134,8 +133,8 @@ public async Task<int> CountAsync(string roomId) {
134133
}
135134

136135
/// <summary>
137-
/// Sends a real-time message to Kuzzle. The message will be dispatched to
138-
/// all clients with subscriptions matching the index, the collection and
136+
/// Sends a real-time message to Kuzzle. The message will be dispatched to
137+
/// all clients with subscriptions matching the index, the collection and
139138
/// the message content.
140139
/// </summary>
141140
public async Task PublishAsync(
@@ -153,8 +152,8 @@ await api.QueryAsync(new JObject {
153152
}
154153

155154
/// <summary>
156-
/// Subscribes by providing a set of filters: messages, document changes
157-
/// and, optionally, user events matching the provided filters will
155+
/// Subscribes by providing a set of filters: messages, document changes
156+
/// and, optionally, user events matching the provided filters will
158157
/// generate real-time notifications, sent to you in real-time by Kuzzle.
159158
/// </summary>
160159
public async Task<string> SubscribeAsync(
@@ -218,16 +217,32 @@ private async Task<string> SubscribeAndAddToSubscriptionRecoverer(
218217
}
219218

220219
/// <summary>
221-
/// Subscribes by providing a set of filters: messages, document changes
222-
/// and, optionally, user events matching the provided filters will
220+
/// Subscribes by providing a set of filters: messages, document changes
221+
/// and, optionally, user events matching the provided filters will
223222
/// generate real-time notifications, sent to you in real-time by Kuzzle.
224223
/// and add the Subscription to the SubscriptionRecoverer for Offline Mode
225224
/// </summary>
225+
/// <param name="index">Storage index</param>
226+
/// <param name="collection">Storage collection</param>
227+
/// <param name="filters">Realtime filters (Koncorde format)</param>
228+
/// <param name="handler">Callback invoked each time a realtime notification is received</param>
229+
/// <param name="options">Subscription options</param>
226230
/// <param name="addToRecoverer">If set to <c>true</c> add to recoverer.</param>
227231
async Task<string> IRealtimeController.SubscribeAndAddToRecoverer(
228-
string index, string collection, JObject filters,
229-
NotificationHandler handler, SubscribeOptions options, bool addToRecoverer) {
230-
return await SubscribeAndAddToSubscriptionRecoverer(index, collection, filters, handler, options, addToRecoverer);
232+
string index,
233+
string collection,
234+
JObject filters,
235+
NotificationHandler handler,
236+
SubscribeOptions options,
237+
bool addToRecoverer
238+
) {
239+
return await SubscribeAndAddToSubscriptionRecoverer(
240+
index,
241+
collection,
242+
filters,
243+
handler,
244+
options,
245+
addToRecoverer);
231246
}
232247

233248
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
using System;
22
namespace KuzzleSdk.Enums.CollectionController {
3+
/// <summary>
4+
/// Controls the kind of collections to be returned by collection:list
5+
/// </summary>
36
public enum TypeFilter {
7+
/// <summary>
8+
/// Return all collections (stored and real-time)
9+
/// </summary>
410
All,
11+
/// <summary>
12+
/// Return only stored collections
13+
/// </summary>
514
Stored,
15+
/// <summary>
16+
/// Return only real-time collections
17+
/// </summary>
618
Realtime,
719
}
820
}

Kuzzle/EventHandler/AbstractKuzzleEventHandler.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,38 @@
33
using KuzzleSdk.EventHandler.Events;
44

55
namespace KuzzleSdk.EventHandler {
6+
/// <summary>
7+
/// Abstract class describing this SDK event handler
8+
/// </summary>
69
public abstract class AbstractKuzzleEventHandler {
10+
/// <summary>
11+
/// Events occuring on realtime subscriptions
12+
/// </summary>
713
public abstract event EventHandler<SubscriptionEvent> Subscription;
14+
/// <summary>
15+
/// Events occuring on a successful login
16+
/// </summary>
817
public abstract event EventHandler<UserLoggedInEvent> UserLoggedIn;
18+
/// <summary>
19+
/// Events occuring on a successful logout
20+
/// </summary>
921
public abstract event Action UserLoggedOut;
22+
/// <summary>
23+
/// Events occuring whenever the SDK reconnects after a connection loss
24+
/// </summary>
1025
public abstract event Action Reconnected;
26+
/// <summary>
27+
/// Events occuring when queued items during a connection loss have all
28+
/// been replayed
29+
/// </summary>
1130
public abstract event Action QueueRecovered;
31+
/// <summary>
32+
/// Events occuring on an unknown query response received from Kuzzle
33+
/// </summary>
1234
public abstract event EventHandler<Response> UnhandledResponse;
35+
/// <summary>
36+
/// Events occuring when the authentication token has expired
37+
/// </summary>
1338
public abstract event Action TokenExpired;
1439

1540
internal abstract void DispatchSubscription(SubscriptionEvent subscriptionData);

0 commit comments

Comments
 (0)