Skip to content

Commit c22e4c9

Browse files
author
Cem Yılmaz
committed
Implement unit tests and new constructors
1 parent 7a51439 commit c22e4c9

12 files changed

Lines changed: 465 additions & 54 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
string myHostAndPort = "127.0.0.1:27015";
1313
// or
1414
string myHostAndPort = "localhost:27015";
15+
// or
16+
string myHostAndPort = "steam://connect/127.0.0.1:27015";
17+
// or
18+
string myHostAndPort = "steam://connect/localhost:27015";
1519
```
20+
* Implemented new tests for ip, hostname and port validation.
1621
1722
* Added a CHANGELOG. LUL :)
1823

README.md

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,87 @@ SteamQueryNet comes with a single object that gives you access to all API's of t
1616

1717
* Server information (server name, capacity etc).
1818
* Concurrent players.
19-
* Server rules (friendlyfire, roundttime etc). **Warning: currently does not work due to a protocol issue on server source server.**
19+
* Server rules (friendlyfire, roundttime etc). **Warning: currently does not work due to a protocol issue on steam server query API. Use could make use of ServerInfo.tags if the server admins are kind enough to put rules as tags in the field.**
2020

2121
## Creating an instance
22-
To make us of the API's listed above, an instance of `ServerQuery` should be created.
22+
To make use of the API's listed above, an instance of `ServerQuery` should be created.
2323

2424
```csharp
2525
string serverIp = "127.0.0.1";
2626
int serverPort = 27015;
2727
IServerQuery serverQuery = new ServerQuery(serverIp, serverPort);
2828
```
2929

30+
or you can use string resolvers like below:
31+
32+
```csharp
33+
string myHostAndPort = "127.0.0.1:27015";
34+
// or
35+
myHostAndPort = "127.0.0.1,27015";
36+
// or
37+
myHostAndPort = "localhost:27015";
38+
// or
39+
myHostAndPort = "localhost,27015";
40+
// or
41+
myHostAndPort = "steam://connect/127.0.0.1:27015";
42+
// or
43+
myHostAndPort = "steam://connect/localhost:27015";
44+
45+
IServerQuery serverQuery = new ServerQuery(myHostAndPort);
46+
```
47+
48+
Also, it is possible to create `ServerQuery` object without connecting like below:
49+
50+
```csharp
51+
IServerQuery serverQuery = new ServerQuery();
52+
serverQuery.Connect(host, port);
53+
```
54+
55+
*Note*: `Connect` function overloads are similar to `ServerQuery` non-empty constructors.
56+
57+
## Providing Custom UDPClient
58+
59+
You can provide custom UDP clients by implementing `IUdpClient` in `SteamQueryNet.Interfaces` namespace.
60+
61+
See the example below:
62+
```csharp
63+
public class MyAmazingUdpClient : IUdpClient
64+
{
65+
public bool IsConnected { get; }
66+
67+
public void Close()
68+
{
69+
// client implementation
70+
}
71+
72+
public void Connect(IPEndPoint remoteIpEndpoint)
73+
{
74+
// client implementation
75+
}
76+
77+
public void Dispose()
78+
{
79+
// client implementation
80+
}
81+
82+
public Task<UdpReceiveResult> ReceiveAsync()
83+
{
84+
// client implementation
85+
}
86+
87+
public Task<int> SendAsync(byte[] datagram, int bytes)
88+
{
89+
// client implementation
90+
}
91+
}
92+
93+
// Usage
94+
IPEndpoint remoteIpEndpoint = new IPEndPoint(IPAddress.Parse(remoteServerIp), remoteServerPort);
95+
96+
IUdpClient myUdpClient = new MyAmazingUdpClient();
97+
IServerQuery serverQuery = new ServerQuery(myUdpClient, remoteIpEndpoint);
98+
```
99+
30100
once its created functions below returns informations desired,
31101

32102
[ServerInfo](https://github.com/cyilcode/SteamQueryNet/blob/master/SteamQueryNet/SteamQueryNet/Models/ServerInfo.cs)
@@ -36,18 +106,21 @@ ServerInfo serverInfo = serverQuery.GetServerInfo();
36106

37107
[Players](https://github.com/cyilcode/SteamQueryNet/blob/master/SteamQueryNet/SteamQueryNet/Models/Player.cs)
38108
```csharp
39-
// Concurrent Players
40109
List<Player> players = serverQuery.GetPlayers();
41110
```
42111

43112
[Rules](https://github.com/cyilcode/SteamQueryNet/blob/master/SteamQueryNet/SteamQueryNet/Models/Rule.cs)
44113
```csharp
45-
// Rules
46114
List<Rules> rules = serverQuery.GetRules();
47115
```
48116

49-
and thats it.
117+
While **it is not encouraged**, you can chain `Connect` function or Non-empty Constructors to get information in a single line.
118+
119+
```csharp
120+
ServerInfo serverInfo = new ServerQuery()
121+
.Connect(host, port)
122+
.GetServerInfo();
123+
```
50124

51125
# Todos
52-
* Write MOAR TESTS !
53126
* Enable CI
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
255,255,255,255,68,4,0,62,58,40,32,217,160,32,77,114,46,80,111,103,111,77,111,103,111,70,111,103,111,78,111,103,111,32,48,49,58,51,0,0,0,0,0,86,189,203,70,0,70,117,77,101,32,74,70,0,0,0,0,0,63,18,10,69,0,51,48,83,97,110,99,104,101,122,90,0,0,0,0,0,0,30,255,68,0,107,122,122,45,45,0,0,0,0,0,253,167,52,68
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
using SteamQueryNet.Enums;
2+
using SteamQueryNet.Models;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.IO;
6+
using System.Linq;
7+
8+
namespace SteamQueryNet.Tests.Responses
9+
{
10+
internal sealed class ResponseHelper
11+
{
12+
internal const string ServerInfo = "/Responses/ServerInfoValidResponse.txt";
13+
internal const string GetPlayers = "/Responses/GetPlayersValidResponse.txt";
14+
15+
// Decided to use a response from SurfHeaven servers and cache them. Got administrators approval.
16+
private static readonly Dictionary<string, object> _responses = new Dictionary<string, object>()
17+
{
18+
{
19+
ServerInfo, new ServerInfo
20+
{
21+
Bots = 3,
22+
EDF = 177,
23+
Environment = ServerEnvironment.Linux,
24+
Folder = "csgo",
25+
Game = "Counter-Strike: Global Offensive",
26+
GameID = 730,
27+
ID = 730,
28+
Keywords = "!knife,!ws,64tick,SurfHeaven,autobhop,cs20,knife,rank,skins,stats,surf,surfing,surftimer,timer,ws,secure",
29+
Map = "surf_sinister_evil",
30+
MaxPlayers = 40,
31+
Name = " SurfHeaven #5 Top 250/VIP",
32+
Ping = 0,
33+
Players = 4,
34+
Port = 27015,
35+
Protocol = 17,
36+
ServerType = ServerType.Dedicated,
37+
ShipGameInfo = null,
38+
SourceTVPort = 0,
39+
SourceTVServerName = null,
40+
SteamID = 85568392920053114,
41+
VAC = VAC.Secured,
42+
Version = "1.37.3.6",
43+
Visibility = Visibility.Public
44+
}
45+
},
46+
{
47+
GetPlayers, new List<Player>()
48+
{
49+
new Player
50+
{
51+
Duration = 26078.668f,
52+
Index = 0,
53+
Name = ">:( ٠ Mr.PogoMogoFogoNogo 01:3",
54+
Score = 0
55+
},
56+
new Player
57+
{
58+
Duration = 2209.14038f,
59+
Index = 0,
60+
Name = "FuMe JF",
61+
Score = 0
62+
},
63+
new Player
64+
{
65+
Duration = 2040.9375f,
66+
Index = 0,
67+
Name = "30SanchezZ",
68+
Score = 0
69+
},
70+
new Player
71+
{
72+
Duration = 722.6248f,
73+
Index = 0,
74+
Name = "kzz--",
75+
Score = 0
76+
}
77+
}
78+
}
79+
};
80+
81+
public static (byte[], object) GetValidResponse(string responseType)
82+
{
83+
string validResponseString = File.ReadAllText(Environment.CurrentDirectory + responseType);
84+
if (!_responses.TryGetValue(responseType, out object responseObject))
85+
{
86+
throw new ArgumentException($"Invalid response type received: {responseType}." +
87+
$"Consider registering {responseType} into ResponseHelpers dictionary.", nameof(responseType));
88+
}
89+
90+
return (validResponseString.Split(',').Select(x => byte.Parse(x)).ToArray(), responseObject);
91+
}
92+
}
93+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
255,255,255,255,73,17,32,83,117,114,102,72,101,97,118,101,110,32,35,53,32,84,111,112,32,50,53,48,47,86,73,80,0,115,117,114,102,95,115,105,110,105,115,116,101,114,95,101,118,105,108,0,99,115,103,111,0,67,111,117,110,116,101,114,45,83,116,114,105,107,101,58,32,71,108,111,98,97,108,32,79,102,102,101,110,115,105,118,101,0,218,2,4,40,3,100,108,0,1,49,46,51,55,46,51,46,54,0,177,135,105,122,53,0,0,0,0,48,1,33,107,110,105,102,101,44,33,119,115,44,54,52,116,105,99,107,44,83,117,114,102,72,101,97,118,101,110,44,97,117,116,111,98,104,111,112,44,99,115,50,48,44,107,110,105,102,101,44,114,97,110,107,44,115,107,105,110,115,44,115,116,97,116,115,44,115,117,114,102,44,115,117,114,102,105,110,103,44,115,117,114,102,116,105,109,101,114,44,116,105,109,101,114,44,119,115,44,115,101,99,117,114,101,0,218,2,0,0,0,0,0,0
Lines changed: 106 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
using Moq;
2+
3+
using SteamQueryNet.Interfaces;
4+
using SteamQueryNet.Models;
5+
using SteamQueryNet.Tests.Responses;
6+
using SteamQueryNet.Utils;
27
using System;
8+
using System.Collections.Generic;
9+
using System.IO;
10+
using System.Linq;
311
using System.Net;
412
using System.Net.Sockets;
513
using Xunit;
@@ -11,34 +19,35 @@ public class ServerQueryTests
1119
private const string IP_ADDRESS = "127.0.0.1";
1220
private const string HOST_NAME = "localhost";
1321
private const ushort PORT = 27015;
14-
private readonly string IP_AND_PORT_COLON = $"{IP_ADDRESS}:{PORT}";
15-
private readonly string IP_AND_PORT_COMMA = $"{IP_ADDRESS},{PORT}";
16-
private readonly string HOST_NAME_AND_PORT_COLON = $"{HOST_NAME}:{PORT}";
17-
private readonly string HOST_NAME_AND_PORT_COMMA = $"{HOST_NAME},{PORT}";
22+
private readonly IPEndPoint _localIpEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 0);
1823

1924
[Theory]
2025
[InlineData(IP_ADDRESS)]
2126
[InlineData(HOST_NAME)]
2227
public void ShouldInitializeWithProperHost(string host)
2328
{
24-
var client = new Mock<UdpClient>();
25-
client.Setup(x => x.Connect(It.IsAny<IPEndPoint>()));
26-
new ServerQuery(client.Object, It.IsAny<IPEndPoint>()).Connect(host, PORT);
29+
using (var sq = new ServerQuery(new Mock<IUdpClient>().Object, It.IsAny<IPEndPoint>()))
30+
{
31+
sq.Connect(host, PORT);
32+
}
2733
}
2834

29-
// Please don't hate me but i think ClassDataAttribute sucks for many reasons.
30-
[Fact]
31-
public void ShouldInitializeWithProperHostAndPort()
35+
[Theory]
36+
[InlineData("127.0.0.1:27015")]
37+
[InlineData("127.0.0.1,27015")]
38+
[InlineData("localhost:27015")]
39+
[InlineData("localhost,27015")]
40+
[InlineData("steam://connect/localhost:27015")]
41+
[InlineData("steam://connect/127.0.0.1:27015")]
42+
public void ShouldInitializeWithProperHostAndPort(string ipAndHost)
3243
{
33-
new ServerQuery(IP_AND_PORT_COLON);
34-
new ServerQuery(IP_AND_PORT_COMMA);
35-
new ServerQuery(HOST_NAME_AND_PORT_COLON);
36-
new ServerQuery(HOST_NAME_AND_PORT_COMMA);
44+
using (var sq = new ServerQuery(new Mock<IUdpClient>().Object, It.IsAny<IPEndPoint>()))
45+
{
46+
sq.Connect(ipAndHost);
47+
}
3748
}
3849

3950
[Theory]
40-
[InlineData("256.256.256.256")]
41-
[InlineData("invalidHost")]
4251
[InlineData("invalidHost:-1")]
4352
[InlineData("invalidHost,-1")]
4453
[InlineData("invalidHost:65536")]
@@ -47,12 +56,91 @@ public void ShouldInitializeWithProperHostAndPort()
4756
[InlineData("256.256.256.256,-1")]
4857
[InlineData("256.256.256.256:65536")]
4958
[InlineData("256.256.256.256,65536")]
50-
public void ShouldNotInitializeWithAnInvalidHost(string invalidHost)
59+
public void ShouldNotInitializeWithAnInvalidHostAndPort(string invalidHost)
5160
{
5261
Assert.Throws<ArgumentException>(() =>
5362
{
54-
var squery = new ServerQuery(invalidHost, PORT);
63+
using (var sq = new ServerQuery(new Mock<IUdpClient>().Object, It.IsAny<IPEndPoint>()))
64+
{
65+
sq.Connect(invalidHost);
66+
}
5567
});
5668
}
69+
70+
[Fact]
71+
public void GetServerInfo_ShouldPopulateCorrectServerInfo()
72+
{
73+
(byte[] udpPacket, object responseObject) = ResponseHelper.GetValidResponse(ResponseHelper.ServerInfo);
74+
var expectedObject = (ServerInfo)responseObject;
75+
76+
byte[] requestPacket = RequestHelpers.PrepareAS2_INFO_Request();
77+
Mock<IUdpClient> udpClientMock = SetupReceiveResponse(udpPacket);
78+
SetupRequestCompare(requestPacket, udpClientMock);
79+
80+
// Ayylmao it looks ugly as hell but we will improve it later on.
81+
using (var sq = new ServerQuery(udpClientMock.Object, _localIpEndpoint))
82+
{
83+
ServerInfo result = sq.GetServerInfo();
84+
Assert.Equal(expectedObject.Bots, result.Bots);
85+
Assert.Equal(expectedObject.EDF, result.EDF);
86+
Assert.Equal(expectedObject.Environment, result.Environment);
87+
Assert.Equal(expectedObject.Folder, result.Folder);
88+
Assert.Equal(expectedObject.Game, result.Game);
89+
Assert.Equal(expectedObject.GameID, result.GameID);
90+
Assert.Equal(expectedObject.ID, result.ID);
91+
Assert.Equal(expectedObject.Keywords, result.Keywords);
92+
Assert.Equal(expectedObject.Map, result.Map);
93+
Assert.Equal(expectedObject.MaxPlayers, result.MaxPlayers);
94+
Assert.Equal(expectedObject.Name, result.Name);
95+
Assert.Equal(expectedObject.Ping, result.Ping);
96+
Assert.Equal(expectedObject.Players, result.Players);
97+
Assert.Equal(expectedObject.Port, result.Port);
98+
Assert.Equal(expectedObject.Protocol, result.Protocol);
99+
Assert.Equal(expectedObject.ServerType, result.ServerType);
100+
Assert.Equal(expectedObject.ShipGameInfo, result.ShipGameInfo);
101+
Assert.Equal(expectedObject.SourceTVPort, result.SourceTVPort);
102+
Assert.Equal(expectedObject.SourceTVServerName, result.SourceTVServerName);
103+
Assert.Equal(expectedObject.SteamID, result.SteamID);
104+
Assert.Equal(expectedObject.VAC, result.VAC);
105+
Assert.Equal(expectedObject.Version, result.Version);
106+
Assert.Equal(expectedObject.Visibility, result.Visibility);
107+
}
108+
}
109+
110+
[Fact(Skip = "Not Completed Yet")]
111+
public void GetPlayers_ShouldPopulateCorrectPlayers()
112+
{
113+
(byte[] udpPacket, object responseObject) = ResponseHelper.GetValidResponse(ResponseHelper.GetPlayers);
114+
var expectedObject = (List<Player>)responseObject;
115+
116+
byte[] requestPacket = RequestHelpers.PrepareAS2_RENEW_CHALLENGE_Request();
117+
Mock<IUdpClient> udpClientMock = SetupReceiveResponse(udpPacket);
118+
SetupRequestCompare(requestPacket, udpClientMock);
119+
120+
// Ayylmao it looks ugly as hell but we will improve it later on.
121+
using (var sq = new ServerQuery(udpClientMock.Object, _localIpEndpoint))
122+
{
123+
List<Player> result = sq.GetPlayers();
124+
125+
}
126+
}
127+
128+
private void SetupRequestCompare(byte[] requestPacket, Mock<IUdpClient> udpClientMock)
129+
{
130+
udpClientMock
131+
.Setup(x => x.SendAsync(It.IsAny<byte[]>(), requestPacket.Length))
132+
.Callback<byte[], int>((request, length) =>
133+
{
134+
Assert.True(TestValidators.CompareBytes(requestPacket, request));
135+
});
136+
}
137+
138+
private Mock<IUdpClient> SetupReceiveResponse(byte[] udpPacket)
139+
{
140+
Mock<IUdpClient> udpClientMock = new Mock<IUdpClient>();
141+
UdpReceiveResult result = new UdpReceiveResult(udpPacket, _localIpEndpoint);
142+
udpClientMock.Setup(x => x.ReceiveAsync()).ReturnsAsync(result);
143+
return udpClientMock;
144+
}
57145
}
58146
}

0 commit comments

Comments
 (0)