steam-condenser 1.1.0 → 1.2.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 (62) hide show
  1. data/Gemfile.lock +30 -0
  2. data/LICENSE +1 -1
  3. data/README.md +4 -3
  4. data/lib/steam-condenser/version.rb +2 -2
  5. data/lib/steam/community/alien_swarm/alien_swarm_mission.rb +24 -22
  6. data/lib/steam/community/alien_swarm/alien_swarm_stats.rb +66 -65
  7. data/lib/steam/community/alien_swarm/alien_swarm_weapon.rb +6 -6
  8. data/lib/steam/community/app_news.rb +2 -2
  9. data/lib/steam/community/css/css_map.rb +4 -4
  10. data/lib/steam/community/css/css_stats.rb +43 -43
  11. data/lib/steam/community/css/css_weapon.rb +5 -5
  12. data/lib/steam/community/defense_grid/defense_grid_stats.rb +36 -35
  13. data/lib/steam/community/dods/dods_class.rb +14 -14
  14. data/lib/steam/community/dods/dods_stats.rb +5 -4
  15. data/lib/steam/community/dods/dods_weapon.rb +6 -6
  16. data/lib/steam/community/game_achievement.rb +38 -31
  17. data/lib/steam/community/game_inventory.rb +6 -6
  18. data/lib/steam/community/game_leaderboard.rb +34 -32
  19. data/lib/steam/community/game_leaderboard_entry.rb +6 -6
  20. data/lib/steam/community/game_stats.rb +39 -65
  21. data/lib/steam/community/game_weapon.rb +2 -2
  22. data/lib/steam/community/l4d/abstract_l4d_stats.rb +54 -49
  23. data/lib/steam/community/l4d/abstract_l4d_weapon.rb +7 -6
  24. data/lib/steam/community/l4d/l4d2_map.rb +10 -10
  25. data/lib/steam/community/l4d/l4d2_stats.rb +33 -33
  26. data/lib/steam/community/l4d/l4d2_weapon.rb +8 -7
  27. data/lib/steam/community/l4d/l4d_explosive.rb +5 -4
  28. data/lib/steam/community/l4d/l4d_map.rb +8 -7
  29. data/lib/steam/community/l4d/l4d_stats.rb +7 -7
  30. data/lib/steam/community/l4d/l4d_weapon.rb +5 -4
  31. data/lib/steam/community/portal2/portal2_stats.rb +3 -3
  32. data/lib/steam/community/steam_game.rb +106 -16
  33. data/lib/steam/community/steam_group.rb +51 -40
  34. data/lib/steam/community/steam_id.rb +119 -87
  35. data/lib/steam/community/tf2/tf2_class.rb +14 -14
  36. data/lib/steam/community/tf2/tf2_class_factory.rb +2 -2
  37. data/lib/steam/community/tf2/tf2_engineer.rb +5 -7
  38. data/lib/steam/community/tf2/tf2_golden_wrench.rb +1 -1
  39. data/lib/steam/community/tf2/tf2_medic.rb +4 -6
  40. data/lib/steam/community/tf2/tf2_sniper.rb +3 -5
  41. data/lib/steam/community/tf2/tf2_spy.rb +10 -6
  42. data/lib/steam/community/tf2/tf2_stats.rb +15 -7
  43. data/lib/steam/community/web_api.rb +15 -1
  44. data/lib/steam/community/xml_data.rb +17 -0
  45. data/lib/steam/servers/game_server.rb +4 -4
  46. data/lib/steam/servers/master_server.rb +2 -2
  47. data/lib/steam/servers/source_server.rb +0 -2
  48. data/lib/steam/sockets/goldsrc_socket.rb +2 -2
  49. data/lib/steam/steam_player.rb +2 -2
  50. data/steam-condenser.gemspec +3 -2
  51. data/test/helper.rb +10 -2
  52. data/test/steam/communtiy/test_steam_group.rb +4 -4
  53. data/test/steam/communtiy/test_steam_id.rb +28 -2
  54. data/test/steam/communtiy/test_web_api.rb +2 -2
  55. data/test/steam/packets/test_steam_packet.rb +37 -0
  56. data/test/steam/servers/test_game_server.rb +296 -308
  57. data/test/steam/servers/test_goldsrc_server.rb +59 -59
  58. data/test/steam/servers/test_master_server.rb +131 -131
  59. data/test/steam/servers/test_server.rb +72 -72
  60. data/test/steam/servers/test_source_server.rb +126 -140
  61. data/test/steam/sockets/test_master_server_socket.rb +1 -0
  62. metadata +39 -19
@@ -8,9 +8,7 @@ require 'steam/community/tf2/tf2_class'
8
8
  # Represents the stats for the Team Fortress 2 Sniper class for a specific user
9
9
  #
10
10
  # @author Sebastian Staudt
11
- class TF2Sniper
12
-
13
- include TF2Class
11
+ class TF2Sniper < TF2Class
14
12
 
15
13
  # Returns the maximum number enemies killed with a headshot by the player in
16
14
  # a single life as a Sniper
@@ -20,11 +18,11 @@ class TF2Sniper
20
18
 
21
19
  # Creates a new instance of the Sniper class based on the given XML data
22
20
  #
23
- # @param [REXML::Element] class_data The XML data for this Sniper
21
+ # @param [Hash<String, Object>] class_data The XML data for this Sniper
24
22
  def initialize(class_data)
25
23
  super class_data
26
24
 
27
- @max_headshots = class_data.elements['iheadshots'].text.to_i
25
+ @max_headshots = class_data['iheadshots'].to_i
28
26
  end
29
27
 
30
28
  end
@@ -8,9 +8,7 @@ require 'steam/community/tf2/tf2_class'
8
8
  # Represents the stats for the Team Fortress 2 Spy class for a specific user
9
9
  #
10
10
  # @author Sebastian Staudt
11
- class TF2Spy
12
-
13
- include TF2Class
11
+ class TF2Spy < TF2Class
14
12
 
15
13
  # Returns the maximum number enemies killed with a backstab by the player in
16
14
  # a single life as a Spy
@@ -18,6 +16,11 @@ class TF2Spy
18
16
  # @return [Fixnum] Maximum number of buildings built
19
17
  attr_reader :max_backstabs
20
18
 
19
+ # Returns the head shots by the player in a single life as a Spy
20
+ #
21
+ # @return [Fixnum] Maximum number of head shots
22
+ attr_reader :max_head_shots
23
+
21
24
  # Returns the maximum health leeched from enemies by the player in a single
22
25
  # life as a Spy
23
26
  #
@@ -26,12 +29,13 @@ class TF2Spy
26
29
 
27
30
  # Creates a new instance of the Spy class based on the given XML data
28
31
  #
29
- # @param [REXML::Element] class_data The XML data for this Spy
32
+ # @param [Hash<String, Object>] class_data The XML data for this Spy
30
33
  def initialize(class_data)
31
34
  super class_data
32
35
 
33
- @max_backstabs = class_data.elements['ibackstabs'].text.to_i
34
- @max_health_leeched = class_data.elements['ihealthpointsleached'].text.to_i
36
+ @max_backstabs = class_data['ibackstabs'].to_i
37
+ @max_head_shots = class_data['iheadshots'].to_i
38
+ @max_health_leeched = class_data['ihealthpointsleached'].to_i
35
39
  end
36
40
 
37
41
  end
@@ -1,7 +1,7 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under
2
2
  # the terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2011, Sebastian Staudt
4
+ # Copyright (c) 2008-2012, Sebastian Staudt
5
5
 
6
6
  require 'steam/community/game_stats'
7
7
  require 'steam/community/tf2/tf2_beta_inventory'
@@ -19,6 +19,11 @@ class TF2Stats < GameStats
19
19
  # @return [Fixnum] This player's accumulated points
20
20
  attr_reader :accumulated_points
21
21
 
22
+ # Returns the accumulated number of seconds this player has spent playing as a TF2 class
23
+ #
24
+ # @return [Fixnum] total seconds played as a TF2 class
25
+ attr_reader :total_playtime
26
+
22
27
  # Creates a `TF2Stats` instance by calling the super constructor with the
23
28
  # game name `'tf2'`
24
29
  #
@@ -28,8 +33,11 @@ class TF2Stats < GameStats
28
33
  def initialize(steam_id, beta = false)
29
34
  super steam_id, (beta ? '520' : 'tf2')
30
35
 
31
- if public? && !@xml_data.elements['stats/accumulatedPoints'].nil?
32
- @accumulated_points = @xml_data.elements['stats/accumulatedPoints'].text.to_i
36
+ if public? && !@xml_data['stats']['accumulatedPoints'].nil?
37
+ @accumulated_points = @xml_data['stats']['accumulatedPoints'].to_i
38
+ end
39
+ if public? && !@xml_data['stats']['secondsPlayedAllClassesLifetime'].nil?
40
+ @total_playtime = @xml_data['stats']['secondsPlayedAllClassesLifetime']
33
41
  end
34
42
  end
35
43
 
@@ -44,8 +52,8 @@ class TF2Stats < GameStats
44
52
 
45
53
  if @class_stats.nil?
46
54
  @class_stats = Hash.new
47
- @xml_data.elements.each('stats/classData') do |class_data|
48
- @class_stats[class_data.elements['className'].text] = TF2ClassFactory.tf2_class(class_data)
55
+ @xml_data['stats']['classData'].each do |class_data|
56
+ @class_stats[class_data['className']] = TF2ClassFactory.tf2_class(class_data)
49
57
  end
50
58
  end
51
59
 
@@ -58,8 +66,8 @@ class TF2Stats < GameStats
58
66
  # @return [TF2Inventory] This player's TF2 backpack
59
67
  def inventory
60
68
  if @inventory.nil?
61
- inventory_class = (game_friendly_name == 'tf2') ? TF2Inventory : TF2BetaInventory
62
- @inventory = inventory_class.new(steam_id64) if @inventory.nil?
69
+ inventory_class = (game.short_name == 'tf2') ? TF2Inventory : TF2BetaInventory
70
+ @inventory = inventory_class.new(user.steam_id64) if @inventory.nil?
63
71
  end
64
72
 
65
73
  @inventory
@@ -16,6 +16,8 @@ require 'errors/web_api_error'
16
16
  # @author Sebastian
17
17
  module WebApi
18
18
 
19
+ @@api_key = nil
20
+
19
21
  # Returns the Steam Web API key currently used by Steam Condenser
20
22
  #
21
23
  # @return [String] The currently active Steam Web API key
@@ -37,6 +39,18 @@ module WebApi
37
39
  @@api_key = api_key
38
40
  end
39
41
 
42
+ # Returns a raw list of interfaces and their methods that are available in
43
+ # Steam's Web API
44
+ #
45
+ # This can be used for reference when accessing interfaces and methods that
46
+ # have not yet been implemented by Steam Condenser.
47
+ #
48
+ # @return [Array<Hash>] The list of interfaces and methods
49
+ def self.interfaces
50
+ data = json 'ISteamWebAPIUtil', 'GetSupportedAPIList'
51
+ MultiJson.load(data, { :symbolize_keys => true })[:apilist][:interfaces]
52
+ end
53
+
40
54
  # Fetches JSON data from Steam Web API using the specified interface, method
41
55
  # and version. Additional parameters are supplied via HTTP GET.
42
56
  # Data is returned as a JSON-encoded string.
@@ -67,7 +81,7 @@ module WebApi
67
81
  # @return [Hash<Symbol, Object>] The JSON data replied to the request
68
82
  def self.json!(interface, method, version = 1, params = nil)
69
83
  data = json(interface, method, version, params)
70
- result = MultiJson.decode(data, { :symbolize_keys => true })[:result]
84
+ result = MultiJson.load(data, { :symbolize_keys => true })[:result]
71
85
 
72
86
  status = result[:status]
73
87
  if status != 1
@@ -0,0 +1,17 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2011, Sebastian Staudt
5
+
6
+ require 'open-uri'
7
+
8
+ require 'multi_xml'
9
+
10
+ module XMLData
11
+
12
+ def parse(url)
13
+ data = open(url, { :proxy => true })
14
+ @xml_data = MultiXml.parse(data).values.first
15
+ end
16
+
17
+ end
@@ -1,7 +1,7 @@
1
1
  # This code is free software; you can redistribute it and/or modify it under
2
2
  # the terms of the new BSD License.
3
3
  #
4
- # Copyright (c) 2008-2011, Sebastian Staudt
4
+ # Copyright (c) 2008-2012, Sebastian Staudt
5
5
 
6
6
  require 'errors/steam_condenser_error'
7
7
  require 'errors/timeout_error'
@@ -203,7 +203,7 @@ module GameServer
203
203
  def handle_response_for_request(request_type, repeat_on_failure = true)
204
204
  case request_type
205
205
  when :challenge then
206
- request_packet = A2S_SERVERQUERY_GETCHALLENGE_Packet.new
206
+ request_packet = A2S_PLAYER_Packet.new
207
207
  expected_response = S2C_CHALLENGE_Packet
208
208
  when :info then
209
209
  request_packet = A2S_INFO_Packet.new
@@ -346,8 +346,8 @@ module GameServer
346
346
  def to_s
347
347
  return_string = ''
348
348
 
349
- return_string << "Ping: #{@ping}\n"
350
- return_string << "Challenge number: #{@challenge_number}\n"
349
+ return_string << "Ping: #@ping\n"
350
+ return_string << "Challenge number: #@challenge_number\n"
351
351
 
352
352
  unless @info_hash.nil?
353
353
  return_string << "Info:\n"
@@ -28,7 +28,7 @@ class MasterServer
28
28
  # The master server address to query for GoldSrc game servers
29
29
  GOLDSRC_MASTER_SERVER = 'hl1master.steampowered.com', 27010
30
30
 
31
- # The master server address to query for GoldSrc game servers
31
+ # The master server address to query for Source game servers
32
32
  SOURCE_MASTER_SERVER = 'hl2master.steampowered.com', 27011
33
33
 
34
34
  # The region code for the US east coast
@@ -103,7 +103,7 @@ class MasterServer
103
103
  # * `\map\[mapname]`: Request only servers running a specific map
104
104
  # * `\linux\1`: Request only linux servers
105
105
  # * `\emtpy\1`: Request only **non**-empty servers
106
- # * `\full\`: Request only servers **not** full
106
+ # * `\full\1`: Request only servers **not** full
107
107
  # * `\proxy\1`: Request only spectator proxy servers
108
108
  #
109
109
  # @note Receiving all servers from the master server is taking quite some
@@ -68,8 +68,6 @@ class SourceServer
68
68
  @rcon_socket.reply
69
69
  reply = @rcon_socket.reply
70
70
 
71
- raise RCONNoAuthError if reply.request_id == -1
72
-
73
71
  @rcon_authenticated = reply.request_id == @rcon_request_id
74
72
  end
75
73
 
@@ -90,8 +90,8 @@ class GoldSrcSocket
90
90
  def rcon_exec(password, command)
91
91
  rcon_challenge if @rcon_challenge.nil? || @is_hltv
92
92
 
93
- rcon_send "rcon #{@rcon_challenge} #{password} #{command}"
94
- rcon_send "rcon #{@rcon_challenge} #{password}"
93
+ rcon_send "rcon #@rcon_challenge #{password} #{command}"
94
+ rcon_send "rcon #@rcon_challenge #{password}"
95
95
  if @is_hltv
96
96
  begin
97
97
  response = reply.response
@@ -134,9 +134,9 @@ class SteamPlayer
134
134
  # @return [String] A string representing this player
135
135
  def to_s
136
136
  if @extended
137
- "\##{@real_id} \"#{@name}\", SteamID: #{@steam_id}, Score: #{@score}, Time: #{@connect_time}"
137
+ "\##@real_id \"#@name\", SteamID: #@steam_id, Score: #@score, Time: #@connect_time"
138
138
  else
139
- "\##{@id} \"#{@name}\", Score: #{@score}, Time: #{@connect_time}"
139
+ "\##@id \"#@name\", Score: #@score, Time: #@connect_time"
140
140
  end
141
141
  end
142
142
 
@@ -11,9 +11,10 @@ Gem::Specification.new do |s|
11
11
  s.description = 'A multi-language library for querying the Steam Community, Source, GoldSrc servers and Steam master servers'
12
12
 
13
13
  s.add_dependency 'bzip2-ruby', '~> 0.2.7'
14
- s.add_dependency 'multi_json', '~> 1.0.3'
14
+ s.add_dependency 'multi_json', '~> 1.3.1'
15
+ s.add_dependency 'multi_xml', '~> 0.4.1'
15
16
 
16
- s.add_development_dependency 'mocha', '~> 0.10.0'
17
+ s.add_development_dependency 'mocha', '~> 0.11.1'
17
18
  s.add_development_dependency 'rake', '~> 0.9.2'
18
19
  s.add_development_dependency 'shoulda-context', '~> 1.0.0'
19
20
  s.add_development_dependency 'yard', '~> 0.7.2'
data/test/helper.rb CHANGED
@@ -26,12 +26,20 @@ class Test::Unit::TestCase
26
26
  assert !boolean, message
27
27
  end
28
28
 
29
- # Reads a file with fixtures from `./test/`
29
+ # Reads the contents of a fixture file from `./test/`
30
30
  #
31
31
  # @param [String] name The name of the fixtures file
32
32
  # @return [String] The contents of the file
33
33
  def fixture(name)
34
- File.read File.join(File.dirname(__FILE__), 'fixtures', name)
34
+ fixture_io(name).read
35
+ end
36
+
37
+ # Opens a file with fixtures from `./test/`
38
+ #
39
+ # @param [String] name The name of the fixtures file
40
+ # @return [File] The file handle
41
+ def fixture_io(name)
42
+ File.open File.join(File.dirname(__FILE__), 'fixtures', name)
35
43
  end
36
44
 
37
45
  end
@@ -29,7 +29,7 @@ class TestSteamGroup < Test::Unit::TestCase
29
29
  end
30
30
 
31
31
  should 'be able to fetch its members' do
32
- url = mock :read => fixture('valve-members.xml')
32
+ url = fixture_io 'valve-members.xml'
33
33
  SteamGroup.any_instance.expects(:open).with('http://steamcommunity.com/groups/valve/memberslistxml?p=1', { :proxy => true }).returns url
34
34
 
35
35
  group = SteamGroup.new 'valve'
@@ -58,7 +58,7 @@ class TestSteamGroup < Test::Unit::TestCase
58
58
 
59
59
  should 'raise an exception when parsing invalid XML' do
60
60
  error = assert_raises SteamCondenserError do
61
- url = mock :read => fixture('invalid.xml')
61
+ url = fixture_io 'invalid.xml'
62
62
  SteamGroup.any_instance.expects(:open).with('http://steamcommunity.com/groups/valve/memberslistxml?p=1', { :proxy => true }).returns url
63
63
 
64
64
  SteamGroup.new 'valve'
@@ -67,8 +67,8 @@ class TestSteamGroup < Test::Unit::TestCase
67
67
  end
68
68
 
69
69
  should 'be able to parse just the member count' do
70
- url = mock :read => fixture('valve-members.xml')
71
- SteamGroup.any_instance.expects(:open).with('http://steamcommunity.com/groups/valve/memberslistxml', { :proxy => true }).returns url
70
+ url = fixture_io 'valve-members.xml'
71
+ SteamGroup.any_instance.expects(:open).with('http://steamcommunity.com/groups/valve/memberslistxml?p=1', { :proxy => true }).returns url
72
72
 
73
73
  group = SteamGroup.new 'valve', false
74
74
  assert_equal 221, group.member_count
@@ -42,14 +42,40 @@ class TestSteamId < Test::Unit::TestCase
42
42
  assert SteamId.cached? 'son_of_Thor'
43
43
  end
44
44
 
45
+ should 'have an ID' do
46
+ steam_id1 = SteamId.new 76561197983311154, false
47
+ steam_id2 = SteamId.new 'Son_of_Thor', false
48
+
49
+ assert_equal 76561197983311154, steam_id1.id
50
+ assert_equal 'son_of_thor', steam_id2.id
51
+ end
52
+
45
53
  should 'be able to fetch its data' do
46
- url = mock :read => fixture('sonofthor.xml')
54
+ url = fixture_io 'sonofthor.xml'
47
55
  SteamId.any_instance.expects(:open).with('http://steamcommunity.com/id/son_of_thor?xml=1', { :proxy => true }).returns url
48
56
 
49
57
  steam_id = SteamId.new 'Son_of_Thor'
50
58
 
51
59
  assert_equal 76561197983311154, steam_id.steam_id64
60
+ assert_equal 'son_of_thor', steam_id.custom_url
61
+ assert_equal 'Bellevue, Washington, United States', steam_id.location
62
+ assert_equal 'Dad serious.', steam_id.head_line
63
+ assert_equal 'Son of Thor', steam_id.nickname
64
+ assert_equal 'Torsten Zabka', steam_id.real_name
65
+ assert_equal 'Last Online: 3 days ago', steam_id.state_message
66
+ assert_equal 'We jump that fence when we get to it.', steam_id.summary
67
+ assert_equal 'None', steam_id.trade_ban_state
68
+
69
+ assert_equal 'http://media.steampowered.com/steamcommunity/public/images/avatars/b8/b8438d91481295b7cc8da9578004cd63a2c3b2e4_full.jpg', steam_id.full_avatar_url
70
+ assert_equal 'http://media.steampowered.com/steamcommunity/public/images/avatars/b8/b8438d91481295b7cc8da9578004cd63a2c3b2e4.jpg', steam_id.icon_url
71
+ assert_equal 'http://media.steampowered.com/steamcommunity/public/images/avatars/b8/b8438d91481295b7cc8da9578004cd63a2c3b2e4_medium.jpg', steam_id.medium_avatar_url
72
+
73
+ assert_not steam_id.banned?
74
+ assert_not steam_id.limited?
75
+ assert_not steam_id.online?
52
76
  assert steam_id.fetched?
77
+
78
+ assert steam_id.public?
53
79
  end
54
80
 
55
81
  should 'be found by the 64bit SteamID' do
@@ -68,7 +94,7 @@ class TestSteamId < Test::Unit::TestCase
68
94
 
69
95
  should 'raise an exception when parsing invalid XML' do
70
96
  error = assert_raises SteamCondenserError do
71
- url = mock :read => fixture('invalid.xml')
97
+ url = fixture_io 'invalid.xml'
72
98
  SteamId.any_instance.expects(:open).with('http://steamcommunity.com/id/son_of_thor?xml=1', { :proxy => true }).returns url
73
99
 
74
100
  SteamId.new 'Son_of_Thor'
@@ -41,7 +41,7 @@ class TestWebApi < Test::Unit::TestCase
41
41
  data = mock
42
42
  WebApi.expects(:json).with('interface', 'method', 2, { :test => 'param' }).
43
43
  returns data
44
- MultiJson.expects(:decode).with(data, { :symbolize_keys => true }).
44
+ MultiJson.expects(:load).with(data, { :symbolize_keys => true }).
45
45
  returns({ :result => { :status => 1 }})
46
46
 
47
47
  assert_equal({ :status => 1 }, WebApi.json!('interface', 'method', 2, { :test => 'param' }))
@@ -51,7 +51,7 @@ class TestWebApi < Test::Unit::TestCase
51
51
  data = mock
52
52
  WebApi.expects(:json).with('interface', 'method', 2, { :test => 'param' }).
53
53
  returns data
54
- MultiJson.expects(:decode).with(data, { :symbolize_keys => true }).
54
+ MultiJson.expects(:load).with(data, { :symbolize_keys => true }).
55
55
  returns({ :result => { :status => 2, :statusDetail => 'error' } })
56
56
 
57
57
  error = assert_raises WebApiError do
@@ -0,0 +1,37 @@
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2012, Sebastian Staudt
5
+
6
+ require 'helper'
7
+ require 'steam/packets/steam_packet'
8
+
9
+ class TestSteamPacket < Test::Unit::TestCase
10
+
11
+ class GenericSteamPacket
12
+ include SteamPacket
13
+ end
14
+
15
+ context 'A packet' do
16
+
17
+ setup do
18
+ @packet = GenericSteamPacket.new 0x61, 'test'
19
+ end
20
+
21
+ should 'have a data buffer' do
22
+ data = @packet.instance_variable_get(:@content_data)
23
+ assert_instance_of StringIO, data
24
+ assert_equal 'test', data.string
25
+ end
26
+
27
+ should 'know its header' do
28
+ assert_equal 0x61, @packet.instance_variable_get(:@header_data)
29
+ end
30
+
31
+ should 'have a valid byte representation' do
32
+ assert_equal "\xFF\xFF\xFF\xFFatest", @packet.to_s
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -1,308 +1,296 @@
1
- # This code is free software; you can redistribute it and/or modify it under
2
- # the terms of the new BSD License.
3
- #
4
- # Copyright (c) 2011, Sebastian Staudt
5
-
6
- require 'helper'
7
- require 'steam/servers/game_server'
8
-
9
- class TestGameServer < Test::Unit::TestCase
10
-
11
- context 'A generic game server' do
12
-
13
- class GenericGameServer
14
- include GameServer
15
- end
16
-
17
- setup do
18
- Socket.stubs(:getaddrinfo).
19
- with('game', 27015, Socket::AF_INET, Socket::SOCK_DGRAM).
20
- returns [[nil, nil, 'game', '127.0.0.1']]
21
- @socket = mock
22
-
23
- @server = GenericGameServer.new 'game', 27015
24
- @server.instance_variable_set :@socket, @socket
25
- end
26
-
27
- should 'send packets using its client socket' do
28
- packet = mock
29
- @socket.expects(:send).with packet
30
-
31
- @server.send :send_request, packet
32
- end
33
-
34
- should 'get replies using its client socket' do
35
- packet = mock
36
- @socket.expects(:reply).returns packet
37
-
38
- assert_equal packet, @server.send(:reply)
39
- end
40
-
41
- should 'be able to calculate the latency of the server' do
42
- @server.expects(:send_request).with do |packet|
43
- packet.is_a? A2S_INFO_Packet
44
- end
45
- @server.expects(:reply).with { || sleep 0.05 }
46
-
47
- @server.update_ping
48
- assert_operator @server.instance_variable_get(:@ping), :>=, 50
49
- end
50
-
51
- should 'be able to get a challenge from the server' do
52
- @server.expects(:handle_response_for_request).with :challenge
53
-
54
- @server.update_challenge_number
55
- end
56
-
57
- should 'be able to get information about server' do
58
- @server.expects(:handle_response_for_request).with :info
59
-
60
- @server.update_server_info
61
- end
62
-
63
- should 'be able to get the server rules' do
64
- @server.expects(:handle_response_for_request).with :rules
65
-
66
- @server.update_rules
67
- end
68
-
69
- should 'be able to get the players on the server' do
70
- @server.expects(:handle_response_for_request).with :players
71
-
72
- @server.update_players
73
- end
74
-
75
- should 'provide a convenience wrapper for basic server methods' do
76
- @server.expects :update_ping
77
- @server.expects :update_server_info
78
- @server.expects :update_challenge_number
79
-
80
- @server.init
81
- end
82
-
83
- should 'know if its RCON connection is authenticated' do
84
- assert_equal @server.instance_variable_get(:@rcon_authenticated),
85
- @server.rcon_authenticated?
86
- end
87
-
88
- should 'cache the ping of the server' do
89
- @server.expects :update_ping
90
-
91
- @server.ping
92
-
93
- @server.instance_variable_set :@ping, 0
94
-
95
- @server.ping
96
- end
97
-
98
- should 'cache the players on the server' do
99
- @server.expects :update_players
100
-
101
- @server.players
102
-
103
- @server.instance_variable_set :@player_hash, 0
104
-
105
- @server.players
106
- end
107
-
108
- should 'cache the server rules' do
109
- @server.expects :update_rules
110
-
111
- @server.rules
112
-
113
- @server.instance_variable_set :@rules_hash, 0
114
-
115
- @server.rules
116
- end
117
-
118
- should 'cache the server information' do
119
- @server.expects :update_server_info
120
-
121
- @server.server_info
122
-
123
- @server.instance_variable_set :@info_hash, 0
124
-
125
- @server.server_info
126
- end
127
-
128
- context 'be able to get additional information about the players on a Source server' do
129
-
130
- setup do
131
- status = fixture 'status_source'
132
-
133
- @someone = mock
134
- @somebody = mock
135
- player_hash = { 'someone' => @someone, 'somebody' => @somebody }
136
- @server.instance_variable_set :@player_hash, player_hash
137
-
138
- @server.expects(:handle_response_for_request).with :players
139
- @server.expects(:rcon_exec).with('status').returns status
140
-
141
- someone_data = { :name => 'someone', :userid => '1', :uniqueid => 'STEAM_0:0:123456', :score => '10', :time => '3:52', :ping => '12', :loss => '0', :state => 'active' }
142
- somebody_data = { :name => 'somebody', :userid => '2', :uniqueid => 'STEAM_0:0:123457', :score => '3', :time => '2:42', :ping => '34', :loss => '0', :state => 'active' }
143
-
144
- attributes = mock
145
- GameServer.expects(:player_status_attributes).
146
- with('userid name uniqueid score connected ping loss state').
147
- returns attributes
148
- GameServer.expects(:split_player_status).
149
- with(attributes, '1 "someone" STEAM_0:0:123456 10 3:52 12 0 active').
150
- returns somebody_data
151
- GameServer.expects(:split_player_status).
152
- with(attributes, '2 "somebody" STEAM_0:0:123457 3 2:42 34 0 active').
153
- returns someone_data
154
-
155
- @somebody.expects(:add_info).with somebody_data
156
- @someone.expects(:add_info).with someone_data
157
- end
158
-
159
- should 'with the RCON password' do
160
- @server.expects(:rcon_auth).with 'password'
161
-
162
- @server.update_players 'password'
163
- end
164
-
165
- should 'if the RCON connection is authenticated' do
166
- @server.instance_variable_set :@rcon_authenticated, true
167
-
168
- @server.update_players
169
- end
170
-
171
- end
172
-
173
- context 'be able to get additional information about the players on a GoldSrc server' do
174
-
175
- setup do
176
- status = fixture 'status_goldsrc'
177
-
178
- @someone = mock
179
- @somebody = mock
180
- player_hash = { 'someone' => @someone, 'somebody' => @somebody }
181
- @server.instance_variable_set :@player_hash, player_hash
182
-
183
- @server.expects(:handle_response_for_request).with :players
184
- @server.expects(:rcon_exec).with('status').returns status
185
-
186
- someone_data = { :name => 'someone', :userid => '1', :uniqueid => 'STEAM_0:0:123456', :score => '10', :time => '3:52', :ping => '12', :loss => '0', :adr => '0' }
187
- somebody_data = { :name => 'somebody', :userid => '2', :uniqueid => 'STEAM_0:0:123457', :score => '3', :time => '2:42', :ping => '34', :loss => '0', :adr => '0' }
188
-
189
- attributes = mock
190
- GameServer.expects(:player_status_attributes).
191
- with('name userid uniqueid frag time ping loss adr').
192
- returns attributes
193
- GameServer.expects(:split_player_status).
194
- with(attributes, '1 "someone" 1 STEAM_0:0:123456 10 3:52 12 0 0').
195
- returns somebody_data
196
- GameServer.expects(:split_player_status).
197
- with(attributes, '2 "somebody" 2 STEAM_0:0:123457 3 2:42 34 0 0').
198
- returns someone_data
199
-
200
- @somebody.expects(:add_info).with somebody_data
201
- @someone.expects(:add_info).with someone_data
202
- end
203
-
204
- should 'with the RCON password' do
205
- @server.expects(:rcon_auth).with 'password'
206
-
207
- @server.update_players 'password'
208
- end
209
-
210
- should 'if the RCON connection is authenticated' do
211
- @server.instance_variable_set :@rcon_authenticated, true
212
-
213
- @server.update_players
214
- end
215
-
216
- end
217
-
218
- should 'handle challenge requests' do
219
- @server.expects(:send_request).with do |packet|
220
- packet.is_a? A2S_SERVERQUERY_GETCHALLENGE_Packet
221
- end
222
-
223
- packet = mock
224
- packet.expects(:kind_of?).with(S2A_INFO_BasePacket).returns false
225
- packet.expects(:kind_of?).with(S2A_PLAYER_Packet).returns false
226
- packet.expects(:kind_of?).with(S2A_RULES_Packet).returns false
227
- packet.expects(:kind_of?).with(S2C_CHALLENGE_Packet).twice.returns true
228
-
229
- packet.expects(:challenge_number).returns 1234
230
- @server.expects(:reply).returns packet
231
-
232
- @server.handle_response_for_request :challenge
233
-
234
- assert_equal 1234, @server.instance_variable_get(:@challenge_number)
235
- end
236
-
237
- should 'handle info requests' do
238
- @server.expects(:send_request).with do |packet|
239
- packet.is_a? A2S_INFO_Packet
240
- end
241
-
242
- packet = mock
243
- packet.expects(:kind_of?).with(S2A_INFO_BasePacket).twice.returns true
244
- packet.expects(:info_hash).returns({ :test => 'test' })
245
- @server.expects(:reply).returns packet
246
-
247
- @server.handle_response_for_request :info
248
-
249
- assert_equal({ :test => 'test' }, @server.instance_variable_get(:@info_hash))
250
- end
251
-
252
- should 'server rule requests' do
253
- @server.expects(:send_request).with do |packet|
254
- packet.is_a? A2S_RULES_Packet
255
- end
256
-
257
- packet = mock
258
- packet.expects(:kind_of?).with(S2A_INFO_BasePacket).returns false
259
- packet.expects(:kind_of?).with(S2A_PLAYER_Packet).returns false
260
- packet.expects(:kind_of?).with(S2A_RULES_Packet).twice.returns true
261
- packet.expects(:rules_hash).returns({ :test => 'test' })
262
- @server.expects(:reply).returns packet
263
-
264
- @server.handle_response_for_request :rules
265
-
266
- assert_equal({ :test => 'test' }, @server.instance_variable_get(:@rules_hash))
267
- end
268
-
269
- should 'player requests' do
270
- @server.expects(:send_request).with do |packet|
271
- packet.is_a? A2S_PLAYER_Packet
272
- end
273
-
274
- packet = mock
275
- packet.expects(:kind_of?).with(S2A_INFO_BasePacket).returns false
276
- packet.expects(:kind_of?).with(S2A_PLAYER_Packet).twice.returns true
277
- packet.expects(:player_hash).returns({ :test => 'test' })
278
- @server.expects(:reply).returns packet
279
-
280
- @server.handle_response_for_request :players
281
-
282
- assert_equal({ :test => 'test' }, @server.instance_variable_get(:@player_hash))
283
- end
284
-
285
- should 'handle unexpected answers and retry' do
286
- @server.expects(:send_request).twice.with do |packet|
287
- packet.is_a? A2S_PLAYER_Packet
288
- end
289
-
290
- packet1 = mock
291
- packet1.expects(:kind_of?).with(S2A_INFO_BasePacket).returns true
292
- packet1.expects(:kind_of?).with(S2A_PLAYER_Packet).returns false
293
- packet1.expects(:info_hash).returns({ :test => 'test1' })
294
- packet2 = mock
295
- packet2.expects(:kind_of?).with(S2A_INFO_BasePacket).returns false
296
- packet2.expects(:kind_of?).with(S2A_PLAYER_Packet).twice.returns true
297
- packet2.expects(:player_hash).returns({ :test => 'test2' })
298
- @server.expects(:reply).twice.returns(packet1).returns packet2
299
-
300
- @server.handle_response_for_request :players
301
-
302
- assert_equal({ :test => 'test1' }, @server.instance_variable_get(:@info_hash))
303
- assert_equal({ :test => 'test2' }, @server.instance_variable_get(:@player_hash))
304
- end
305
-
306
- end
307
-
308
- end
1
+ # This code is free software; you can redistribute it and/or modify it under
2
+ # the terms of the new BSD License.
3
+ #
4
+ # Copyright (c) 2011, Sebastian Staudt
5
+
6
+ require 'helper'
7
+ require 'steam/servers/game_server'
8
+
9
+ class TestGameServer < Test::Unit::TestCase
10
+
11
+ context 'A generic game server' do
12
+
13
+ class GenericGameServer
14
+ include GameServer
15
+ end
16
+
17
+ setup do
18
+ Socket.stubs(:getaddrinfo).
19
+ with('game', 27015, Socket::AF_INET, Socket::SOCK_DGRAM).
20
+ returns [[nil, nil, 'game', '127.0.0.1']]
21
+ @socket = mock
22
+
23
+ @server = GenericGameServer.new 'game', 27015
24
+ @server.instance_variable_set :@socket, @socket
25
+ end
26
+
27
+ should 'send packets using its client socket' do
28
+ packet = mock
29
+ @socket.expects(:send).with packet
30
+
31
+ @server.send :send_request, packet
32
+ end
33
+
34
+ should 'get replies using its client socket' do
35
+ packet = mock
36
+ @socket.expects(:reply).returns packet
37
+
38
+ assert_equal packet, @server.send(:reply)
39
+ end
40
+
41
+ should 'be able to calculate the latency of the server' do
42
+ @server.expects(:send_request).with do |packet|
43
+ packet.is_a? A2S_INFO_Packet
44
+ end
45
+ @server.expects(:reply).with { || sleep 0.05 }
46
+
47
+ @server.update_ping
48
+ assert_operator @server.instance_variable_get(:@ping), :>=, 50
49
+ end
50
+
51
+ should 'be able to get a challenge from the server' do
52
+ @server.expects(:handle_response_for_request).with :challenge
53
+
54
+ @server.update_challenge_number
55
+ end
56
+
57
+ should 'be able to get information about server' do
58
+ @server.expects(:handle_response_for_request).with :info
59
+
60
+ @server.update_server_info
61
+ end
62
+
63
+ should 'be able to get the server rules' do
64
+ @server.expects(:handle_response_for_request).with :rules
65
+
66
+ @server.update_rules
67
+ end
68
+
69
+ should 'be able to get the players on the server' do
70
+ @server.expects(:handle_response_for_request).with :players
71
+
72
+ @server.update_players
73
+ end
74
+
75
+ should 'provide a convenience wrapper for basic server methods' do
76
+ @server.expects :update_ping
77
+ @server.expects :update_server_info
78
+ @server.expects :update_challenge_number
79
+
80
+ @server.init
81
+ end
82
+
83
+ should 'know if its RCON connection is authenticated' do
84
+ assert_equal @server.instance_variable_get(:@rcon_authenticated),
85
+ @server.rcon_authenticated?
86
+ end
87
+
88
+ should 'cache the ping of the server' do
89
+ @server.expects :update_ping
90
+
91
+ @server.ping
92
+
93
+ @server.instance_variable_set :@ping, 0
94
+
95
+ @server.ping
96
+ end
97
+
98
+ should 'cache the players on the server' do
99
+ @server.expects :update_players
100
+
101
+ @server.players
102
+
103
+ @server.instance_variable_set :@player_hash, 0
104
+
105
+ @server.players
106
+ end
107
+
108
+ should 'cache the server rules' do
109
+ @server.expects :update_rules
110
+
111
+ @server.rules
112
+
113
+ @server.instance_variable_set :@rules_hash, 0
114
+
115
+ @server.rules
116
+ end
117
+
118
+ should 'cache the server information' do
119
+ @server.expects :update_server_info
120
+
121
+ @server.server_info
122
+
123
+ @server.instance_variable_set :@info_hash, 0
124
+
125
+ @server.server_info
126
+ end
127
+
128
+ context 'be able to get additional information about the players on a Source server' do
129
+
130
+ setup do
131
+ status = fixture 'status_source'
132
+
133
+ @someone = mock
134
+ @somebody = mock
135
+ player_hash = { 'someone' => @someone, 'somebody' => @somebody }
136
+ @server.instance_variable_set :@player_hash, player_hash
137
+
138
+ @server.expects(:handle_response_for_request).with :players
139
+ @server.expects(:rcon_exec).with('status').returns status
140
+
141
+ someone_data = { :name => 'someone', :userid => '1', :uniqueid => 'STEAM_0:0:123456', :score => '10', :time => '3:52', :ping => '12', :loss => '0', :state => 'active' }
142
+ somebody_data = { :name => 'somebody', :userid => '2', :uniqueid => 'STEAM_0:0:123457', :score => '3', :time => '2:42', :ping => '34', :loss => '0', :state => 'active' }
143
+
144
+ attributes = mock
145
+ GameServer.expects(:player_status_attributes).
146
+ with('userid name uniqueid score connected ping loss state').
147
+ returns attributes
148
+ GameServer.expects(:split_player_status).
149
+ with(attributes, '1 "someone" STEAM_0:0:123456 10 3:52 12 0 active').
150
+ returns somebody_data
151
+ GameServer.expects(:split_player_status).
152
+ with(attributes, '2 "somebody" STEAM_0:0:123457 3 2:42 34 0 active').
153
+ returns someone_data
154
+
155
+ @somebody.expects(:add_info).with somebody_data
156
+ @someone.expects(:add_info).with someone_data
157
+ end
158
+
159
+ should 'with the RCON password' do
160
+ @server.expects(:rcon_auth).with 'password'
161
+
162
+ @server.update_players 'password'
163
+ end
164
+
165
+ should 'if the RCON connection is authenticated' do
166
+ @server.instance_variable_set :@rcon_authenticated, true
167
+
168
+ @server.update_players
169
+ end
170
+
171
+ end
172
+
173
+ should 'be able to get additional information about the players on a GoldSrc server with the RCON password' do
174
+ status = fixture 'status_goldsrc'
175
+
176
+ @someone = mock
177
+ @somebody = mock
178
+ player_hash = { 'someone' => @someone, 'somebody' => @somebody }
179
+ @server.instance_variable_set :@player_hash, player_hash
180
+
181
+ @server.expects(:handle_response_for_request).with :players
182
+ @server.expects(:rcon_exec).with('status').returns status
183
+
184
+ someone_data = { :name => 'someone', :userid => '1', :uniqueid => 'STEAM_0:0:123456', :score => '10', :time => '3:52', :ping => '12', :loss => '0', :adr => '0' }
185
+ somebody_data = { :name => 'somebody', :userid => '2', :uniqueid => 'STEAM_0:0:123457', :score => '3', :time => '2:42', :ping => '34', :loss => '0', :adr => '0' }
186
+
187
+ attributes = mock
188
+ GameServer.expects(:player_status_attributes).
189
+ with('name userid uniqueid frag time ping loss adr').
190
+ returns attributes
191
+ GameServer.expects(:split_player_status).
192
+ with(attributes, '1 "someone" 1 STEAM_0:0:123456 10 3:52 12 0 0').
193
+ returns somebody_data
194
+ GameServer.expects(:split_player_status).
195
+ with(attributes, '2 "somebody" 2 STEAM_0:0:123457 3 2:42 34 0 0').
196
+ returns someone_data
197
+
198
+ @somebody.expects(:add_info).with somebody_data
199
+ @someone.expects(:add_info).with someone_data
200
+
201
+ @server.expects(:rcon_auth).with 'password'
202
+
203
+ @server.update_players 'password'
204
+ end
205
+
206
+ should 'handle challenge requests' do
207
+ @server.expects(:send_request).with do |packet|
208
+ packet.is_a? A2S_PLAYER_Packet
209
+ end
210
+
211
+ packet = mock
212
+ packet.expects(:kind_of?).with(S2A_INFO_BasePacket).returns false
213
+ packet.expects(:kind_of?).with(S2A_PLAYER_Packet).returns false
214
+ packet.expects(:kind_of?).with(S2A_RULES_Packet).returns false
215
+ packet.expects(:kind_of?).with(S2C_CHALLENGE_Packet).twice.returns true
216
+
217
+ packet.expects(:challenge_number).returns 1234
218
+ @server.expects(:reply).returns packet
219
+
220
+ @server.handle_response_for_request :challenge
221
+
222
+ assert_equal 1234, @server.instance_variable_get(:@challenge_number)
223
+ end
224
+
225
+ should 'handle info requests' do
226
+ @server.expects(:send_request).with do |packet|
227
+ packet.is_a? A2S_INFO_Packet
228
+ end
229
+
230
+ packet = mock
231
+ packet.expects(:kind_of?).with(S2A_INFO_BasePacket).twice.returns true
232
+ packet.expects(:info_hash).returns({ :test => 'test' })
233
+ @server.expects(:reply).returns packet
234
+
235
+ @server.handle_response_for_request :info
236
+
237
+ assert_equal({ :test => 'test' }, @server.instance_variable_get(:@info_hash))
238
+ end
239
+
240
+ should 'handle rule requests' do
241
+ @server.expects(:send_request).with do |packet|
242
+ packet.is_a? A2S_RULES_Packet
243
+ end
244
+
245
+ packet = mock
246
+ packet.expects(:kind_of?).with(S2A_INFO_BasePacket).returns false
247
+ packet.expects(:kind_of?).with(S2A_PLAYER_Packet).returns false
248
+ packet.expects(:kind_of?).with(S2A_RULES_Packet).twice.returns true
249
+ packet.expects(:rules_hash).returns({ :test => 'test' })
250
+ @server.expects(:reply).returns packet
251
+
252
+ @server.handle_response_for_request :rules
253
+
254
+ assert_equal({ :test => 'test' }, @server.instance_variable_get(:@rules_hash))
255
+ end
256
+
257
+ should 'handle player requests' do
258
+ @server.expects(:send_request).with do |packet|
259
+ packet.is_a? A2S_PLAYER_Packet
260
+ end
261
+
262
+ packet = mock
263
+ packet.expects(:kind_of?).with(S2A_INFO_BasePacket).returns false
264
+ packet.expects(:kind_of?).with(S2A_PLAYER_Packet).twice.returns true
265
+ packet.expects(:player_hash).returns({ :test => 'test' })
266
+ @server.expects(:reply).returns packet
267
+
268
+ @server.handle_response_for_request :players
269
+
270
+ assert_equal({ :test => 'test' }, @server.instance_variable_get(:@player_hash))
271
+ end
272
+
273
+ should 'handle unexpected answers and retry' do
274
+ @server.expects(:send_request).twice.with do |packet|
275
+ packet.is_a? A2S_PLAYER_Packet
276
+ end
277
+
278
+ packet1 = mock
279
+ packet1.expects(:kind_of?).with(S2A_INFO_BasePacket).returns true
280
+ packet1.expects(:kind_of?).with(S2A_PLAYER_Packet).returns false
281
+ packet1.expects(:info_hash).returns({ :test => 'test1' })
282
+ packet2 = mock
283
+ packet2.expects(:kind_of?).with(S2A_INFO_BasePacket).returns false
284
+ packet2.expects(:kind_of?).with(S2A_PLAYER_Packet).twice.returns true
285
+ packet2.expects(:player_hash).returns({ :test => 'test2' })
286
+ @server.expects(:reply).twice.returns(packet1).returns packet2
287
+
288
+ @server.handle_response_for_request :players
289
+
290
+ assert_equal({ :test => 'test1' }, @server.instance_variable_get(:@info_hash))
291
+ assert_equal({ :test => 'test2' }, @server.instance_variable_get(:@player_hash))
292
+ end
293
+
294
+ end
295
+
296
+ end