nexus_mods 2.0.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3aa5f91eb62460b72b2f0f31434cc1d3741c70970b3e4db5f22b57e95bbe6b4c
4
- data.tar.gz: 31cdf30406cbed8fa23a432cc7b916ed8223c9344a3d07379adb5cff19f4bfea
3
+ metadata.gz: a25aa2568a5be1e1a8592196344fde1ca68bcf814b67a51cf6921f7ed64c0f70
4
+ data.tar.gz: cdb50c9b1394af620109ea82439a3254b96ad223286ae1f797fcbc37c32a1a58
5
5
  SHA512:
6
- metadata.gz: 03a0ddca6e1d752aee0cb6ec887067340d3f31546cb6212da7c01082327fd47f898aa176f0c24175483db6657a8ff54d00bedd71411c37a00141b26e5c68b09c
7
- data.tar.gz: ffd3177a8b3dcee3f1c96eeb03e5a9526c5bbcf967ae81c6d926a0f7158eeccae5b7214b4e8c0d769e98f6d5d2058135d8cab5e27436c1ae3df890378c6bca1e
6
+ metadata.gz: b7f49167041ee67f3855c0a474cdba90a744660f424356943516016628f1e65f37154c7c6b596eca2b7ac2430eae4b0726db6a45d3685b750bdf7a33e8674d2e
7
+ data.tar.gz: ce904391c82e93f029a41a47d3e7e391af8dac23625ac38eda01b2066ea9261ae3ff7bbea2aed23fbbba9b124846be80640bdbbf94ef98291b1b020824ec74df
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ # [v2.2.0](https://github.com/Muriel-Salvan/nexus_mods/compare/v2.1.0...v2.2.0) (2023-04-12 12:56:31)
2
+
3
+ ### Features
4
+
5
+ * [[Feature] Add possibility to retrieve the cache timestamp of API resources](https://github.com/Muriel-Salvan/nexus_mods/commit/ba67323a32472deb1f063a51683ff9282ef96981)
6
+
7
+ # [v2.1.0](https://github.com/Muriel-Salvan/nexus_mods/compare/v2.0.1...v2.1.0) (2023-04-11 18:24:04)
8
+
9
+ ### Features
10
+
11
+ * [[Feature] Add API entry point to retrieve last updated mods](https://github.com/Muriel-Salvan/nexus_mods/commit/cf702fc2c146cc7d61aadca005aa0e784b807c44)
12
+
1
13
  # [v2.0.1](https://github.com/Muriel-Salvan/nexus_mods/compare/v2.0.0...v2.0.1) (2023-04-11 10:22:54)
2
14
 
3
15
  ### Patches
@@ -0,0 +1,49 @@
1
+ class NexusMods
2
+
3
+ module Api
4
+
5
+ # A NexusMods mod updates.
6
+ class ModUpdates
7
+
8
+ attr_reader(
9
+ *%i[
10
+ mod_id
11
+ latest_file_update
12
+ latest_mod_activity
13
+ ]
14
+ )
15
+
16
+ # Constructor
17
+ #
18
+ # Parameters::
19
+ # * *mod_id* (Integer): The mod's id
20
+ # * *latest_file_update* (Time): The mod's latest file update
21
+ # * *latest_mod_activity* (Time): The mod's latest activity
22
+ def initialize(
23
+ mod_id:,
24
+ latest_file_update:,
25
+ latest_mod_activity:
26
+ )
27
+ @mod_id = mod_id
28
+ @latest_file_update = latest_file_update
29
+ @latest_mod_activity = latest_mod_activity
30
+ end
31
+
32
+ # Equality operator
33
+ #
34
+ # Parameters::
35
+ # * *other* (Object): Other object to compare with
36
+ # Result::
37
+ # * Boolean: Are objects equal?
38
+ def ==(other)
39
+ other.is_a?(ModUpdates) &&
40
+ @mod_id == other.mod_id &&
41
+ @latest_file_update == other.latest_file_update &&
42
+ @latest_mod_activity == other.latest_mod_activity
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -50,25 +50,43 @@ class NexusMods
50
50
  #
51
51
  # Parameters::
52
52
  # * *path* (String): API path to contact (from v1/ and without .json)
53
+ # * *parameters* (Hash<Symbol,Object>): Optional parameters to add to the path [default: {}]
53
54
  # * *verb* (Symbol): Verb to be used (:get, :post...) [default: :get]
54
55
  # * *clear_cache* (Boolean): Should we clear the API cache for this resource? [default: false]
55
56
  # Result::
56
57
  # * Object: The JSON response
57
- def api(path, verb: :get, clear_cache: false)
58
- clear_cached_api_cache(path, verb:) if clear_cache
59
- cached_api(path, verb:)
58
+ def api(path, parameters: {}, verb: :get, clear_cache: false)
59
+ clear_cached_api_cache(path, parameters:, verb:) if clear_cache
60
+ cached_api(path, parameters:, verb:)
61
+ end
62
+
63
+ # Get the timestamp of the cached data linked to a given API call
64
+ #
65
+ # Parameters::
66
+ # * *path* (String): API path to contact (from v1/ and without .json)
67
+ # * *parameters* (Hash<Symbol,Object>): Optional parameters to add to the path [default: {}]
68
+ # * *verb* (Symbol): Verb to be used (:get, :post...) [default: :get]
69
+ # Result::
70
+ # * Time or nil: The refresh time of the data, or nil if not part of the cache
71
+ def api_cache_timestamp(path, parameters: {}, verb: :get)
72
+ key = ApiClient.cache_key(path, parameters:, verb:)
73
+ return unless Cacheable.cache_adapter.exist?(key)
74
+
75
+ str_time = Cacheable.cache_adapter.context.dig(key, 'invalidate_time')
76
+ str_time.nil? ? nil : Time.parse(str_time)
60
77
  end
61
78
 
62
79
  # Send an HTTP request to the API and get back the HTTP response
63
80
  #
64
81
  # Parameters::
65
82
  # * *path* (String): API path to contact (from v1/ and without .json)
83
+ # * *parameters* (Hash<Symbol,Object>): Optional parameters to add to the path [default: {}]
66
84
  # * *verb* (Symbol): Verb to be used (:get, :post...) [default: :get]
67
85
  # Result::
68
86
  # * Faraday::Response: The HTTP response
69
- def http(path, verb: :get)
87
+ def http(path, parameters: {}, verb: :get)
70
88
  @http_client.send(verb) do |req|
71
- req.url api_uri(path)
89
+ req.url api_uri(path, parameters:)
72
90
  req.headers['apikey'] = @api_key
73
91
  req.headers['User-Agent'] = "nexus_mods/#{NexusMods::VERSION} (#{RUBY_PLATFORM}) Ruby/#{RUBY_VERSION}"
74
92
  end
@@ -97,6 +115,18 @@ class NexusMods
97
115
  # ApiClient: The API client to be used by the cacheable adapter (singleton pattern)
98
116
  attr_accessor :api_client
99
117
 
118
+ # Get the cache key to be used for a given API query
119
+ #
120
+ # Parameters::
121
+ # * *path* (String): API path to contact (from v1/ and without .json)
122
+ # * *parameters* (Hash<Symbol,Object>): Optional parameters to add to the path [default: {}]
123
+ # * *verb* (Symbol): Verb to be used (:get, :post...) [default: :get]
124
+ # Result::
125
+ # * String: The corresponding cache key
126
+ def cache_key(path, parameters:, verb:)
127
+ "#{verb}/#{path}#{parameters.empty? ? '' : "/#{parameters.map { |param, value| "#{param}=#{value}" }.sort.join('/')}"}"
128
+ end
129
+
100
130
  end
101
131
 
102
132
  @api_client = nil
@@ -106,11 +136,12 @@ class NexusMods
106
136
  #
107
137
  # Parameters::
108
138
  # * *path* (String): API path to contact (from v1/ and without .json)
139
+ # * *parameters* (Hash<Symbol,Object>): Optional parameters to add to the path [default: {}]
109
140
  # * *verb* (Symbol): Verb to be used (:get, :post...) [default: :get]
110
141
  # Result::
111
142
  # * Object: The JSON response
112
- def cached_api(path, verb: :get)
113
- res = http(path, verb:)
143
+ def cached_api(path, parameters: {}, verb: :get)
144
+ res = http(path, parameters:, verb:)
114
145
  json = JSON.parse(res.body)
115
146
  uri = api_uri(path)
116
147
  @logger.debug "[API call] - #{verb} #{uri} => #{res.status}\n#{
@@ -139,11 +170,12 @@ class NexusMods
139
170
  cacheable_api(
140
171
  :cached_api,
141
172
  key_format: proc do |_target, _method_name, method_args, method_kwargs|
142
- "#{method_kwargs[:verb]}/#{method_args.first}"
173
+ cache_key(method_args.first, parameters: method_kwargs[:parameters], verb: method_kwargs[:verb])
143
174
  end,
144
175
  expiry_from_key: proc do |key|
145
176
  # Example of keys:
146
177
  # get/games
178
+ # get/games/skyrimspecialedition/mods/updated/period=1d
147
179
  # get/games/skyrimspecialedition/mods/2014
148
180
  # get/games/skyrimspecialedition/mods/2014/files
149
181
  # get/users/validate
@@ -155,13 +187,19 @@ class NexusMods
155
187
  else
156
188
  case key_components[2]
157
189
  when 'mods'
158
- case key_components[4]
159
- when nil
160
- ApiClient.api_client.api_cache_expiry[:mod]
161
- when 'files'
162
- ApiClient.api_client.api_cache_expiry[:mod_files]
190
+ case key_components[3]
191
+ when 'updated'
192
+ # According to the API doc, this is updated every 5 minutes
193
+ 5 * 60
163
194
  else
164
- raise "Unknown API path: #{key}"
195
+ case key_components[4]
196
+ when nil
197
+ ApiClient.api_client.api_cache_expiry[:mod]
198
+ when 'files'
199
+ ApiClient.api_client.api_cache_expiry[:mod_files]
200
+ else
201
+ raise "Unknown API path: #{key}"
202
+ end
165
203
  end
166
204
  else
167
205
  raise "Unknown API path: #{key}"
@@ -183,10 +221,11 @@ class NexusMods
183
221
  #
184
222
  # Parameters::
185
223
  # * *path* (String): API path to contact (from v1/ and without .json)
224
+ # * *parameters* (Hash<Symbol,Object>): Optional parameters to add to the path [default: {}]
186
225
  # Result::
187
226
  # * String: The URI
188
- def api_uri(path)
189
- "https://api.nexusmods.com/v1/#{path}.json"
227
+ def api_uri(path, parameters: {})
228
+ "https://api.nexusmods.com/v1/#{path}.json#{parameters.empty? ? '' : "?#{parameters.map { |param, value| "#{param}=#{value}" }.join('&')}"}"
190
229
  end
191
230
 
192
231
  end
@@ -45,7 +45,7 @@ class NexusMods
45
45
  expiry_cache[key].nil? || (Time.now.utc - Time.parse(context['invalidate_time']).utc > expiry_cache[key])
46
46
  end,
47
47
  update_context_after_fetch: proc do |_key, _value, _options, context|
48
- context['invalidate_time'] = Time.now.utc.strftime('%FT%TUTC')
48
+ context['invalidate_time'] = Time.now.utc.strftime('%FT%T.%9NUTC')
49
49
  end
50
50
  }
51
51
  )
@@ -13,6 +13,8 @@ module Cacheable
13
13
  # * The context information is JSON serializable.
14
14
  class PersistentJsonAdapter < MemoryAdapter
15
15
 
16
+ attr_reader :context
17
+
16
18
  # Fetch a key with the givien cache options
17
19
  #
18
20
  # Parameters::
@@ -87,10 +89,6 @@ module Cacheable
87
89
  @context = loaded_content['context']
88
90
  end
89
91
 
90
- private
91
-
92
- attr_reader :context
93
-
94
92
  end
95
93
 
96
94
  end
@@ -1,5 +1,5 @@
1
1
  class NexusMods
2
2
 
3
- VERSION = '2.0.1'
3
+ VERSION = '2.2.0'
4
4
 
5
5
  end
data/lib/nexus_mods.rb CHANGED
@@ -8,6 +8,7 @@ require 'nexus_mods/api/game'
8
8
  require 'nexus_mods/api/user'
9
9
  require 'nexus_mods/api/mod'
10
10
  require 'nexus_mods/api/mod_file'
11
+ require 'nexus_mods/api/mod_updates'
11
12
 
12
13
  # Ruby API to access NexusMods REST API
13
14
  class NexusMods
@@ -130,7 +131,7 @@ class NexusMods
130
131
  nexusmods_url: game_json['nexusmods_url'],
131
132
  genre: game_json['genre'],
132
133
  domain_name: game_json['domain_name'],
133
- approved_date: Time.at(game_json['approved_date']),
134
+ approved_date: Time.at(game_json['approved_date']).utc,
134
135
  files_count: game_json['file_count'],
135
136
  files_views: game_json['file_views'],
136
137
  files_endorsements: game_json['file_endorsements'],
@@ -142,6 +143,14 @@ class NexusMods
142
143
  end
143
144
  end
144
145
 
146
+ # Get the cached timestamp of the list of games
147
+ #
148
+ # Result::
149
+ # * Time or nil: Freshness time of the data in the API cache, or nil if not present in the cache
150
+ def games_cache_timestamp
151
+ @api_client.api_cache_timestamp('games')
152
+ end
153
+
145
154
  # Get information about a mod
146
155
  #
147
156
  # Parameters::
@@ -182,6 +191,17 @@ class NexusMods
182
191
  )
183
192
  end
184
193
 
194
+ # Get the cached timestamp of a mod information
195
+ #
196
+ # Parameters::
197
+ # * *game_domain_name* (String): Game domain name to query by default [default: @game_domain_name]
198
+ # * *mod_id* (Integer): The mod ID [default: @mod_id]
199
+ # Result::
200
+ # * Time or nil: Freshness time of the data in the API cache, or nil if not present in the cache
201
+ def mod_cache_timestamp(game_domain_name: @game_domain_name, mod_id: @mod_id)
202
+ @api_client.api_cache_timestamp("games/#{game_domain_name}/mods/#{mod_id}")
203
+ end
204
+
185
205
  # Get files belonging to a mod
186
206
  #
187
207
  # Parameters::
@@ -213,4 +233,75 @@ class NexusMods
213
233
  end
214
234
  end
215
235
 
236
+ # Get the cached timestamp of a mod files information
237
+ #
238
+ # Parameters::
239
+ # * *game_domain_name* (String): Game domain name to query by default [default: @game_domain_name]
240
+ # * *mod_id* (Integer): The mod ID [default: @mod_id]
241
+ # Result::
242
+ # * Time or nil: Freshness time of the data in the API cache, or nil if not present in the cache
243
+ def mod_files_cache_timestamp(game_domain_name: @game_domain_name, mod_id: @mod_id)
244
+ @api_client.api_cache_timestamp("games/#{game_domain_name}/mods/#{mod_id}/files")
245
+ end
246
+
247
+ # Get a list of updated mod ids since a given time
248
+ #
249
+ # Parameters::
250
+ # * *game_domain_name* (String): Game domain name to query by default [default: @game_domain_name]
251
+ # * *since* (Symbol): The time from which we look for updated mods [default: :one_day]
252
+ # Possible values are:
253
+ # * *one_day*: Since 1 day
254
+ # * *one_week*: Since 1 week
255
+ # * *one_month*: Since 1 month
256
+ # * *clear_cache* (Boolean): Should we clear the API cache for this resource? [default: false]
257
+ # Result::
258
+ # * Array<ModUpdates>: Mod's updates information
259
+ def updated_mods(game_domain_name: @game_domain_name, since: :one_day, clear_cache: false)
260
+ @api_client.api("games/#{game_domain_name}/mods/updated", parameters: period_to_url_params(since), clear_cache:).map do |updated_mod_json|
261
+ Api::ModUpdates.new(
262
+ mod_id: updated_mod_json['mod_id'],
263
+ latest_file_update: Time.at(updated_mod_json['latest_file_update']).utc,
264
+ latest_mod_activity: Time.at(updated_mod_json['latest_mod_activity']).utc
265
+ )
266
+ end
267
+ end
268
+
269
+ # Get the cached timestamp of a mod files information
270
+ #
271
+ # Parameters::
272
+ # * *game_domain_name* (String): Game domain name to query by default [default: @game_domain_name]
273
+ # * *since* (Symbol): The time from which we look for updated mods [default: :one_day]
274
+ # Possible values are:
275
+ # * *one_day*: Since 1 day
276
+ # * *one_week*: Since 1 week
277
+ # * *one_month*: Since 1 month
278
+ # Result::
279
+ # * Time or nil: Freshness time of the data in the API cache, or nil if not present in the cache
280
+ def updated_mods_cache_timestamp(game_domain_name: @game_domain_name, since: :one_day)
281
+ @api_client.api_cache_timestamp("games/#{game_domain_name}/mods/updated", parameters: period_to_url_params(since))
282
+ end
283
+
284
+ private
285
+
286
+ # Get the URL parameters from the required period
287
+ #
288
+ # Parameters::
289
+ # * *since* (Symbol): The time from which we look for updated mods
290
+ # Possible values are:
291
+ # * *one_day*: Since 1 day
292
+ # * *one_week*: Since 1 week
293
+ # * *one_month*: Since 1 month
294
+ # Result::
295
+ # * Hash<Symbol,Object>: Corresponding URL parameters
296
+ def period_to_url_params(since)
297
+ nexus_mods_period = {
298
+ one_day: '1d',
299
+ one_week: '1w',
300
+ one_month: '1m'
301
+ }[since]
302
+ raise "Unknown time stamp: #{since}" if nexus_mods_period.nil?
303
+
304
+ { period: nexus_mods_period }
305
+ end
306
+
216
307
  end
@@ -5,7 +5,7 @@ module NexusModsTest
5
5
  module Games
6
6
 
7
7
  # Test game with id 100
8
- def json_game100
8
+ def self.json_game100
9
9
  {
10
10
  'id' => 100,
11
11
  'name' => 'Morrowind',
@@ -35,8 +35,12 @@ module NexusModsTest
35
35
  }
36
36
  end
37
37
 
38
+ def json_game100
39
+ Games.json_game100
40
+ end
41
+
38
42
  # Test game with id 101
39
- def json_game101
43
+ def self.json_game101
40
44
  {
41
45
  'id' => 101,
42
46
  'name' => 'Oblivion',
@@ -66,6 +70,10 @@ module NexusModsTest
66
70
  }
67
71
  end
68
72
 
73
+ def json_game101
74
+ Games.json_game101
75
+ end
76
+
69
77
  # Expect a game to be the test game of id 100
70
78
  #
71
79
  # Parameters::
@@ -5,7 +5,7 @@ module NexusModsTest
5
5
  module ModFiles
6
6
 
7
7
  # Test mod file with id 2472
8
- def json_mod_file2472
8
+ def self.json_mod_file2472
9
9
  {
10
10
  'id' => [
11
11
  2472,
@@ -32,8 +32,12 @@ module NexusModsTest
32
32
  }
33
33
  end
34
34
 
35
+ def json_mod_file2472
36
+ ModFiles.json_mod_file2472
37
+ end
38
+
35
39
  # Test mod file with id 2487
36
- def json_mod_file2487
40
+ def self.json_mod_file2487
37
41
  {
38
42
  'id' => [
39
43
  2487,
@@ -60,10 +64,14 @@ module NexusModsTest
60
64
  }
61
65
  end
62
66
 
67
+ def json_mod_file2487
68
+ ModFiles.json_mod_file2487
69
+ end
70
+
63
71
  # Expect a mod's file to be the example one with id 2472
64
72
  #
65
73
  # Parameters::
66
- # * *mod_file* (NexusMods::Api::File): Mod file to validate
74
+ # * *mod_file* (NexusMods::Api::ModFile): Mod file to validate
67
75
  def expect_mod_file_to_be2472(mod_file)
68
76
  expect(mod_file.ids).to eq [2472, 1704]
69
77
  expect(mod_file.uid).to eq 7_318_624_274_856
@@ -87,7 +95,7 @@ module NexusModsTest
87
95
  # Expect a mod's file to be the example one with id 2487
88
96
  #
89
97
  # Parameters::
90
- # * *mod_file* (NexusMods::Api::File): Mod file to validate
98
+ # * *mod_file* (NexusMods::Api::ModFile): Mod file to validate
91
99
  def expect_mod_file_to_be2487(mod_file)
92
100
  expect(mod_file.ids).to eq [2487, 1705]
93
101
  expect(mod_file.uid).to eq 7_318_624_274_857
@@ -0,0 +1,57 @@
1
+ module NexusModsTest
2
+
3
+ module Factories
4
+
5
+ module ModUpdates
6
+
7
+ # Test mod updates with id 2014
8
+ def self.json_mod_updates2014
9
+ {
10
+ 'mod_id' => 2014,
11
+ 'latest_file_update' => 1_655_813_855,
12
+ 'latest_mod_activity' => 1_681_169_675
13
+ }
14
+ end
15
+
16
+ def json_mod_updates2014
17
+ ModUpdates.json_mod_updates2014
18
+ end
19
+
20
+ # Test mod updates with id 100
21
+ def self.json_mod_updates100
22
+ {
23
+ 'mod_id' => 100,
24
+ 'latest_file_update' => 1_681_143_964,
25
+ 'latest_mod_activity' => 1_681_143_964
26
+ }
27
+ end
28
+
29
+ def json_mod_updates100
30
+ ModUpdates.json_mod_updates100
31
+ end
32
+
33
+ # Expect a mod's updates to be the example one with id 2014
34
+ #
35
+ # Parameters::
36
+ # * *mod_updates* (NexusMods::Api::ModUpdates): Mod updates to validate
37
+ def expect_mod_file_to_be2014(mod_updates)
38
+ expect(mod_updates.mod_id).to eq 2014
39
+ expect(mod_updates.latest_file_update).to eq Time.parse('2022-06-21 12:17:35 UTC')
40
+ expect(mod_updates.latest_mod_activity).to eq Time.parse('2023-04-10 23:34:35 UTC')
41
+ end
42
+
43
+ # Expect a mod's updates to be the example one with id 100
44
+ #
45
+ # Parameters::
46
+ # * *mod_updates* (NexusMods::Api::ModUpdates): Mod updates to validate
47
+ def expect_mod_file_to_be100(mod_updates)
48
+ expect(mod_updates.mod_id).to eq 100
49
+ expect(mod_updates.latest_file_update).to eq Time.parse('2023-04-10 16:26:04 UTC')
50
+ expect(mod_updates.latest_mod_activity).to eq Time.parse('2023-04-10 16:26:04 UTC')
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -5,7 +5,7 @@ module NexusModsTest
5
5
  module Mods
6
6
 
7
7
  # Example of JSON object returned by the API for a mod information, having all possible fields
8
- def json_complete_mod
8
+ def self.json_complete_mod
9
9
  {
10
10
  'name' => 'ApachiiSkyHair SSE',
11
11
  'summary' => 'New Female and Male Hairstyles for Humans, Elves and Orcs. Converted hair from Sims2 and Sims3.<br />Standalone version.',
@@ -44,8 +44,12 @@ module NexusModsTest
44
44
  }
45
45
  end
46
46
 
47
+ def json_complete_mod
48
+ Mods.json_complete_mod
49
+ end
50
+
47
51
  # Example of JSON object returned by the API for a mod information, having minimum fields
48
- def json_partial_mod
52
+ def self.json_partial_mod
49
53
  {
50
54
  'mod_downloads' => 13_634_545,
51
55
  'mod_unique_downloads' => 4_052_221,
@@ -75,6 +79,10 @@ module NexusModsTest
75
79
  }
76
80
  end
77
81
 
82
+ def json_partial_mod
83
+ Mods.json_partial_mod
84
+ end
85
+
78
86
  # Expect a mod to be the example complete one
79
87
  #
80
88
  # Parameters::
@@ -4,6 +4,7 @@ require 'rspec/support/object_formatter'
4
4
  require 'nexus_mods_test/factories/games'
5
5
  require 'nexus_mods_test/factories/mods'
6
6
  require 'nexus_mods_test/factories/mod_files'
7
+ require 'nexus_mods_test/factories/mod_updates'
7
8
  require 'nexus_mods'
8
9
 
9
10
  module NexusModsTest
@@ -180,6 +181,7 @@ RSpec.configure do |config|
180
181
  config.include NexusModsTest::Factories::Games
181
182
  config.include NexusModsTest::Factories::Mods
182
183
  config.include NexusModsTest::Factories::ModFiles
184
+ config.include NexusModsTest::Factories::ModUpdates
183
185
  config.before do
184
186
  @nexus_mods = nil
185
187
  # Reload the ApiClient as it stores caches at class level
@@ -0,0 +1,91 @@
1
+ describe NexusMods::Api::ModUpdates do
2
+
3
+ context 'when testing mod updates' do
4
+
5
+ before do
6
+ expect_validate_user
7
+ end
8
+
9
+ # Expect the given array of mod updates to be the ones of examples (for mods 100 and 2014)
10
+ #
11
+ # Parameters::
12
+ # * *mod_updates* (Array<NexusMods::Api::ModUpdates>): The list of mod updates to validate
13
+ def expect_mod_updates_to_be_example(mod_updates)
14
+ sorted_mod_updates = mod_updates.sort_by(&:mod_id)
15
+ expect_mod_file_to_be100(sorted_mod_updates.first)
16
+ expect_mod_file_to_be2014(sorted_mod_updates[1])
17
+ end
18
+
19
+ {
20
+ 'last day' => {
21
+ since: :one_day,
22
+ expected_url_params: 'period=1d'
23
+ },
24
+ 'last week' => {
25
+ since: :one_week,
26
+ expected_url_params: 'period=1w'
27
+ },
28
+ 'last month' => {
29
+ since: :one_month,
30
+ expected_url_params: 'period=1m'
31
+ }
32
+ }.each do |since, since_config|
33
+
34
+ context "when testing updated months since #{since}" do
35
+
36
+ it 'returns updated mods' do
37
+ expect_http_call_to(
38
+ path: "/v1/games/skyrimspecialedition/mods/updated.json?#{since_config[:expected_url_params]}",
39
+ json: [
40
+ json_mod_updates2014,
41
+ json_mod_updates100
42
+ ]
43
+ )
44
+ expect_mod_updates_to_be_example(nexus_mods.updated_mods(game_domain_name: 'skyrimspecialedition', since: since_config[:since]))
45
+ end
46
+
47
+ it 'returns updated mods information for the default game' do
48
+ expect_http_call_to(
49
+ path: "/v1/games/skyrimspecialedition/mods/updated.json?#{since_config[:expected_url_params]}",
50
+ json: [
51
+ json_mod_updates2014,
52
+ json_mod_updates100
53
+ ]
54
+ )
55
+ expect_mod_updates_to_be_example(nexus_mods(game_domain_name: 'skyrimspecialedition').updated_mods(since: since_config[:since]))
56
+ end
57
+
58
+ it 'returns updated mods information for the default game set using accessor' do
59
+ expect_http_call_to(
60
+ path: "/v1/games/skyrimspecialedition/mods/updated.json?#{since_config[:expected_url_params]}",
61
+ json: [
62
+ json_mod_updates2014,
63
+ json_mod_updates100
64
+ ]
65
+ )
66
+ nexus_mods.game_domain_name = 'skyrimspecialedition'
67
+ expect_mod_updates_to_be_example(nexus_mods.updated_mods(since: since_config[:since]))
68
+ end
69
+
70
+ it 'compares objects for equality' do
71
+ expect_http_call_to(
72
+ path: "/v1/games/skyrimspecialedition/mods/updated.json?#{since_config[:expected_url_params]}",
73
+ json: [json_mod_updates2014]
74
+ )
75
+ mod_updates1 = nexus_mods.updated_mods(game_domain_name: 'skyrimspecialedition', since: since_config[:since])
76
+ mod_updates2 = nexus_mods.updated_mods(game_domain_name: 'skyrimspecialedition', since: since_config[:since])
77
+ expect(mod_updates1.object_id).not_to eq mod_updates2.object_id
78
+ expect(mod_updates1).to eq mod_updates2
79
+ end
80
+
81
+ end
82
+
83
+ end
84
+
85
+ it 'fails to fetch updated mods for an unknown period' do
86
+ expect { nexus_mods.updated_mods(game_domain_name: 'skyrimspecialedition', since: :unknown_since) }.to raise_error 'Unknown time stamp: unknown_since'
87
+ end
88
+
89
+ end
90
+
91
+ end
@@ -10,115 +10,245 @@ describe NexusMods do
10
10
  nexus_mods.api_limits
11
11
  end
12
12
 
13
- it 'caches games queries' do
14
- expect_validate_user
15
- expect_http_call_to(
16
- path: '/v1/games.json',
17
- json: [
18
- json_game100,
19
- json_game101
13
+ {
14
+ 'games' => {
15
+ expected_api_path: '/v1/games.json',
16
+ mocked_api_json: [
17
+ NexusModsTest::Factories::Games.json_game100,
18
+ NexusModsTest::Factories::Games.json_game101
19
+ ],
20
+ query: proc { |nm| nm.games },
21
+ query_without_cache: proc { |nm| nm.games(clear_cache: true) },
22
+ get_cache_timestamp: proc { |nm| nm.games_cache_timestamp },
23
+ expiry_cache_param: :games
24
+ },
25
+ 'mods' => {
26
+ expected_api_path: '/v1/games/skyrimspecialedition/mods/2014.json',
27
+ mocked_api_json: NexusModsTest::Factories::Mods.json_complete_mod,
28
+ query: proc { |nm| nm.mod(game_domain_name: 'skyrimspecialedition', mod_id: 2014) },
29
+ query_without_cache: proc { |nm| nm.mod(game_domain_name: 'skyrimspecialedition', mod_id: 2014, clear_cache: true) },
30
+ get_cache_timestamp: proc { |nm| nm.mod_cache_timestamp(game_domain_name: 'skyrimspecialedition', mod_id: 2014) },
31
+ expiry_cache_param: :mod
32
+ },
33
+ 'mod files' => {
34
+ expected_api_path: '/v1/games/skyrimspecialedition/mods/2014/files.json',
35
+ mocked_api_json: {
36
+ files: [
37
+ NexusModsTest::Factories::ModFiles.json_mod_file2472,
38
+ NexusModsTest::Factories::ModFiles.json_mod_file2487
39
+ ]
40
+ },
41
+ query: proc { |nm| nm.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2014) },
42
+ query_without_cache: proc { |nm| nm.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2014, clear_cache: true) },
43
+ get_cache_timestamp: proc { |nm| nm.mod_files_cache_timestamp(game_domain_name: 'skyrimspecialedition', mod_id: 2014) },
44
+ expiry_cache_param: :mod_files
45
+ }
46
+ }.merge(
47
+ {
48
+ 'last day' => {
49
+ since: :one_day,
50
+ expected_url_params: 'period=1d'
51
+ },
52
+ 'last week' => {
53
+ since: :one_week,
54
+ expected_url_params: 'period=1w'
55
+ },
56
+ 'last month' => {
57
+ since: :one_month,
58
+ expected_url_params: 'period=1m'
59
+ }
60
+ }.to_h do |since, since_config|
61
+ [
62
+ "mod updates since #{since}",
63
+ {
64
+ expected_api_path: "/v1/games/skyrimspecialedition/mods/updated.json?#{since_config[:expected_url_params]}",
65
+ mocked_api_json: [
66
+ NexusModsTest::Factories::ModUpdates.json_mod_updates2014,
67
+ NexusModsTest::Factories::ModUpdates.json_mod_updates100
68
+ ],
69
+ query: proc { |nm| nm.updated_mods(game_domain_name: 'skyrimspecialedition', since: since_config[:since]) },
70
+ query_without_cache: proc { |nm| nm.updated_mods(game_domain_name: 'skyrimspecialedition', since: since_config[:since], clear_cache: true) },
71
+ get_cache_timestamp: proc { |nm| nm.updated_mods_cache_timestamp(game_domain_name: 'skyrimspecialedition', since: since_config[:since]) }
72
+ }
20
73
  ]
21
- )
22
- games = nexus_mods.games
23
- expect(nexus_mods.games).to eq(games)
24
- end
74
+ end
75
+ ).each do |resource, resource_config|
25
76
 
26
- it 'does not cache games queries if asked' do
27
- expect_validate_user
28
- expect_http_call_to(
29
- path: '/v1/games.json',
30
- json: [
31
- json_game100,
32
- json_game101
33
- ],
34
- times: 2
35
- )
36
- games = nexus_mods.games
37
- expect(nexus_mods.games(clear_cache: true)).to eq(games)
38
- end
77
+ context "when testing #{resource}" do
39
78
 
40
- it 'caches mod queries' do
41
- expect_validate_user
42
- expect_http_call_to(
43
- path: '/v1/games/skyrimspecialedition/mods/2014.json',
44
- json: json_complete_mod
45
- )
46
- mod = nexus_mods.mod(game_domain_name: 'skyrimspecialedition', mod_id: 2014)
47
- expect(nexus_mods.mod(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to eq(mod)
48
- end
79
+ it 'caches API queries' do
80
+ expect_validate_user
81
+ expect_http_call_to(
82
+ path: resource_config[:expected_api_path],
83
+ json: resource_config[:mocked_api_json]
84
+ )
85
+ resource = resource_config[:query].call(nexus_mods)
86
+ expect(resource_config[:query].call(nexus_mods)).to eq resource
87
+ end
49
88
 
50
- it 'does not cache mod queries if asked' do
51
- expect_validate_user
52
- expect_http_call_to(
53
- path: '/v1/games/skyrimspecialedition/mods/2014.json',
54
- json: json_complete_mod,
55
- times: 2
56
- )
57
- mod = nexus_mods.mod(game_domain_name: 'skyrimspecialedition', mod_id: 2014)
58
- expect(nexus_mods.mod(game_domain_name: 'skyrimspecialedition', mod_id: 2014, clear_cache: true)).to eq(mod)
59
- end
89
+ it 'does not cache API queries if asked' do
90
+ expect_validate_user
91
+ expect_http_call_to(
92
+ path: resource_config[:expected_api_path],
93
+ json: resource_config[:mocked_api_json],
94
+ times: 2
95
+ )
96
+ resource = resource_config[:query].call(nexus_mods)
97
+ expect(resource_config[:query_without_cache].call(nexus_mods)).to eq resource
98
+ end
60
99
 
61
- it 'caches mod files queries' do
62
- expect_validate_user
63
- expect_http_call_to(
64
- path: '/v1/games/skyrimspecialedition/mods/2014/files.json',
65
- json: { files: [json_mod_file2472, json_mod_file2487] }
66
- )
67
- mod_files = nexus_mods.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2014)
68
- expect(nexus_mods.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to eq(mod_files)
69
- end
100
+ if resource_config[:expiry_cache_param]
70
101
 
71
- it 'does not cache mod files queries if asked' do
72
- expect_validate_user
73
- expect_http_call_to(
74
- path: '/v1/games/skyrimspecialedition/mods/2014/files.json',
75
- json: { files: [json_mod_file2472, json_mod_file2487] },
76
- times: 2
77
- )
78
- mod_files = nexus_mods.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2014)
79
- expect(nexus_mods.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2014, clear_cache: true)).to eq(mod_files)
80
- end
102
+ it 'expires API queries cache' do
103
+ expect_validate_user
104
+ expect_http_call_to(
105
+ path: resource_config[:expected_api_path],
106
+ json: resource_config[:mocked_api_json],
107
+ times: 2
108
+ )
109
+ nexus_mods_instance = nexus_mods(api_cache_expiry: { resource_config[:expiry_cache_param] => 1 })
110
+ resource = resource_config[:query].call(nexus_mods_instance)
111
+ sleep 2
112
+ expect(resource_config[:query].call(nexus_mods_instance)).to eq resource
113
+ end
81
114
 
82
- it 'expires games queries cache' do
83
- expect_validate_user
84
- expect_http_call_to(
85
- path: '/v1/games.json',
86
- json: [
87
- json_game100,
88
- json_game101
89
- ],
90
- times: 2
91
- )
92
- nexus_mods_instance = nexus_mods(api_cache_expiry: { games: 1 })
93
- games = nexus_mods_instance.games
94
- sleep 2
95
- expect(nexus_mods_instance.games).to eq(games)
96
- end
115
+ end
97
116
 
98
- it 'expires mod queries cache' do
99
- expect_validate_user
100
- expect_http_call_to(
101
- path: '/v1/games/skyrimspecialedition/mods/2014.json',
102
- json: json_complete_mod,
103
- times: 2
104
- )
105
- nexus_mods_instance = nexus_mods(api_cache_expiry: { mod: 1 })
106
- mod = nexus_mods_instance.mod(game_domain_name: 'skyrimspecialedition', mod_id: 2014)
107
- sleep 2
108
- expect(nexus_mods_instance.mod(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to eq(mod)
109
- end
117
+ it 'stores no timestamp of the data stored in the API cache before fetching data' do
118
+ expect_validate_user
119
+ expect(resource_config[:get_cache_timestamp].call(nexus_mods)).to be_nil
120
+ end
121
+
122
+ it 'retrieves the timestamp of the data stored in the API cache' do
123
+ expect_validate_user
124
+ expect_http_call_to(
125
+ path: resource_config[:expected_api_path],
126
+ json: resource_config[:mocked_api_json]
127
+ )
128
+ before = Time.now
129
+ resource_config[:query].call(nexus_mods)
130
+ after = Time.now
131
+ expect(resource_config[:get_cache_timestamp].call(nexus_mods)).to be_between(before, after)
132
+ end
133
+
134
+ it 'retrieves the timestamp of the games data stored in the cache even after cache is used' do
135
+ expect_validate_user
136
+ expect_http_call_to(
137
+ path: resource_config[:expected_api_path],
138
+ json: resource_config[:mocked_api_json]
139
+ )
140
+ before = Time.now
141
+ resource_config[:query].call(nexus_mods)
142
+ after = Time.now
143
+ resource_config[:query].call(nexus_mods)
144
+ expect(resource_config[:get_cache_timestamp].call(nexus_mods)).to be_between(before, after)
145
+ end
146
+
147
+ it 'retrieves the timestamp of the games data stored in the cache even after cache is persisted' do
148
+ with_api_cache_file do |api_cache_file|
149
+ expect_validate_user(times: 2)
150
+ expect_http_call_to(
151
+ path: resource_config[:expected_api_path],
152
+ json: resource_config[:mocked_api_json]
153
+ )
154
+ before = Time.now
155
+ resource_config[:query].call(nexus_mods(api_cache_file:))
156
+ after = Time.now
157
+ reset_nexus_mods
158
+ expect(resource_config[:get_cache_timestamp].call(nexus_mods(api_cache_file:))).to be_between(before, after)
159
+ end
160
+ end
161
+
162
+ it 'updates the timestamp of the data stored in the API cache' do
163
+ expect_validate_user
164
+ expect_http_call_to(
165
+ path: resource_config[:expected_api_path],
166
+ json: resource_config[:mocked_api_json],
167
+ times: 2
168
+ )
169
+ resource_config[:query].call(nexus_mods)
170
+ sleep 1
171
+ before = Time.now
172
+ resource_config[:query_without_cache].call(nexus_mods)
173
+ after = Time.now
174
+ expect(resource_config[:get_cache_timestamp].call(nexus_mods)).to be_between(before, after)
175
+ end
176
+
177
+ context 'when testing cache persistence in files' do
178
+
179
+ it 'persists API cache in a file' do
180
+ with_api_cache_file do |api_cache_file|
181
+ expect_validate_user
182
+ expect_http_call_to(
183
+ path: resource_config[:expected_api_path],
184
+ json: resource_config[:mocked_api_json]
185
+ )
186
+ resource_config[:query].call(nexus_mods(api_cache_file:))
187
+ expect(File.exist?(api_cache_file)).to be true
188
+ expect(File.size(api_cache_file)).to be > 0
189
+ end
190
+ end
191
+
192
+ it 'uses API cache from a file' do
193
+ with_api_cache_file do |api_cache_file|
194
+ expect_validate_user(times: 2)
195
+ expect_http_call_to(
196
+ path: resource_config[:expected_api_path],
197
+ json: resource_config[:mocked_api_json]
198
+ )
199
+ # Generate the cache first
200
+ resource = resource_config[:query].call(nexus_mods(api_cache_file:))
201
+ # Force a new instance of NexusMods API to run
202
+ reset_nexus_mods
203
+ expect(resource_config[:query].call(nexus_mods(api_cache_file:))).to eq resource
204
+ end
205
+ end
206
+
207
+ if resource_config[:expiry_cache_param]
208
+
209
+ it 'uses API cache from a file, taking expiry time into account' do
210
+ with_api_cache_file do |api_cache_file|
211
+ expect_validate_user(times: 2)
212
+ expect_http_call_to(
213
+ path: resource_config[:expected_api_path],
214
+ json: resource_config[:mocked_api_json],
215
+ times: 2
216
+ )
217
+ # Generate the cache first
218
+ resource = resource_config[:query].call(nexus_mods(api_cache_file:, api_cache_expiry: { resource_config[:expiry_cache_param] => 1 }))
219
+ # Force a new instance of NexusMods API to run
220
+ reset_nexus_mods
221
+ sleep 2
222
+ # As the expiry time is 1 second, then the cache should still be invalidated
223
+ expect(resource_config[:query].call(nexus_mods(api_cache_file:, api_cache_expiry: { resource_config[:expiry_cache_param] => 1 }))).to eq resource
224
+ end
225
+ end
226
+
227
+ it 'uses API cache from a file, taking expiry time of the new process into account' do
228
+ with_api_cache_file do |api_cache_file|
229
+ expect_validate_user(times: 2)
230
+ expect_http_call_to(
231
+ path: resource_config[:expected_api_path],
232
+ json: resource_config[:mocked_api_json],
233
+ times: 2
234
+ )
235
+ # Generate the cache first
236
+ resource = resource_config[:query].call(nexus_mods(api_cache_file:, api_cache_expiry: { resource_config[:expiry_cache_param] => 10 }))
237
+ # Force a new instance of NexusMods API to run
238
+ reset_nexus_mods
239
+ sleep 2
240
+ # Even if the expiry time was 10 seconds while fetching the resource,
241
+ # if we decide it has to be 1 second now then it has to be invalidated.
242
+ expect(resource_config[:query].call(nexus_mods(api_cache_file:, api_cache_expiry: { resource_config[:expiry_cache_param] => 1 }))).to eq resource
243
+ end
244
+ end
245
+
246
+ end
247
+
248
+ end
249
+
250
+ end
110
251
 
111
- it 'expires mod files queries cache' do
112
- expect_validate_user
113
- expect_http_call_to(
114
- path: '/v1/games/skyrimspecialedition/mods/2014/files.json',
115
- json: { files: [json_mod_file2472, json_mod_file2487] },
116
- times: 2
117
- )
118
- nexus_mods_instance = nexus_mods(api_cache_expiry: { mod_files: 1 })
119
- mod_files = nexus_mods_instance.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2014)
120
- sleep 2
121
- expect(nexus_mods_instance.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2014)).to eq(mod_files)
122
252
  end
123
253
 
124
254
  it 'only clears the cache of the wanted resource' do
@@ -136,89 +266,12 @@ describe NexusMods do
136
266
  mod_files20151 = nexus_mods.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2015)
137
267
  mod_files20142 = nexus_mods.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2014, clear_cache: true)
138
268
  mod_files20152 = nexus_mods.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2015)
139
- expect(mod_files20141).to eq(mod_files20142)
140
- expect(mod_files20151).to eq(mod_files20152)
269
+ expect(mod_files20141).to eq mod_files20142
270
+ expect(mod_files20151).to eq mod_files20152
141
271
  end
142
272
 
143
273
  context 'with file persistence' do
144
274
 
145
- it 'persists API cache in a file' do
146
- with_api_cache_file do |api_cache_file|
147
- expect_validate_user
148
- expect_http_call_to(
149
- path: '/v1/games.json',
150
- json: [
151
- json_game100,
152
- json_game101
153
- ]
154
- )
155
- nexus_mods(api_cache_file:).games
156
- expect(File.exist?(api_cache_file)).to be true
157
- expect(File.size(api_cache_file)).to be > 0
158
- end
159
- end
160
-
161
- it 'uses API cache from a file' do
162
- with_api_cache_file do |api_cache_file|
163
- expect_validate_user(times: 2)
164
- expect_http_call_to(
165
- path: '/v1/games.json',
166
- json: [
167
- json_game100,
168
- json_game101
169
- ]
170
- )
171
- # Generate the cache first
172
- games = nexus_mods(api_cache_file:).games
173
- # Force a new instance of NexusMods API to run
174
- reset_nexus_mods
175
- expect(nexus_mods(api_cache_file:).games).to eq games
176
- end
177
- end
178
-
179
- it 'uses API cache from a file, taking expiry time into account' do
180
- with_api_cache_file do |api_cache_file|
181
- expect_validate_user(times: 2)
182
- expect_http_call_to(
183
- path: '/v1/games.json',
184
- json: [
185
- json_game100,
186
- json_game101
187
- ],
188
- times: 2
189
- )
190
- # Generate the cache first
191
- games = nexus_mods(api_cache_file:, api_cache_expiry: { games: 1 }).games
192
- # Force a new instance of NexusMods API to run
193
- reset_nexus_mods
194
- sleep 2
195
- # As the expiry time is 1 second, then the cache should still be invalidated
196
- expect(nexus_mods(api_cache_file:, api_cache_expiry: { games: 1 }).games).to eq games
197
- end
198
- end
199
-
200
- it 'uses API cache from a file, taking expiry time of the new process into account' do
201
- with_api_cache_file do |api_cache_file|
202
- expect_validate_user(times: 2)
203
- expect_http_call_to(
204
- path: '/v1/games.json',
205
- json: [
206
- json_game100,
207
- json_game101
208
- ],
209
- times: 2
210
- )
211
- # Generate the cache first
212
- games = nexus_mods(api_cache_file:, api_cache_expiry: { games: 10 }).games
213
- # Force a new instance of NexusMods API to run
214
- reset_nexus_mods
215
- sleep 2
216
- # Even if the expiry time was 10 seconds while fetching the resource,
217
- # if we decide it has to be 1 second now then it has to be invalidated.
218
- expect(nexus_mods(api_cache_file:, api_cache_expiry: { games: 1 }).games).to eq games
219
- end
220
- end
221
-
222
275
  it 'completes the API cache from a file' do
223
276
  with_api_cache_file do |api_cache_file|
224
277
  expect_validate_user(times: 3)
@@ -276,10 +329,10 @@ describe NexusMods do
276
329
  nexus_mods_instance3 = nexus_mods(api_cache_file:)
277
330
  mod_files20143 = nexus_mods_instance3.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2014)
278
331
  mod_files20153 = nexus_mods_instance3.mod_files(game_domain_name: 'skyrimspecialedition', mod_id: 2015)
279
- expect(mod_files20141).to eq(mod_files20142)
280
- expect(mod_files20141).to eq(mod_files20143)
281
- expect(mod_files20151).to eq(mod_files20152)
282
- expect(mod_files20151).to eq(mod_files20153)
332
+ expect(mod_files20141).to eq mod_files20142
333
+ expect(mod_files20141).to eq mod_files20143
334
+ expect(mod_files20151).to eq mod_files20152
335
+ expect(mod_files20151).to eq mod_files20153
283
336
  end
284
337
  end
285
338
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nexus_mods
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Muriel Salvan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-11 00:00:00.000000000 Z
11
+ date: 2023-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -137,6 +137,7 @@ files:
137
137
  - lib/nexus_mods/api/game.rb
138
138
  - lib/nexus_mods/api/mod.rb
139
139
  - lib/nexus_mods/api/mod_file.rb
140
+ - lib/nexus_mods/api/mod_updates.rb
140
141
  - lib/nexus_mods/api/user.rb
141
142
  - lib/nexus_mods/api_client.rb
142
143
  - lib/nexus_mods/cacheable_api.rb
@@ -146,12 +147,14 @@ files:
146
147
  - lib/nexus_mods/version.rb
147
148
  - spec/nexus_mods_test/factories/games.rb
148
149
  - spec/nexus_mods_test/factories/mod_files.rb
150
+ - spec/nexus_mods_test/factories/mod_updates.rb
149
151
  - spec/nexus_mods_test/factories/mods.rb
150
152
  - spec/nexus_mods_test/helpers.rb
151
153
  - spec/nexus_mods_test/scenarios/nexus_mods/api/api_limits_spec.rb
152
154
  - spec/nexus_mods_test/scenarios/nexus_mods/api/game_spec.rb
153
155
  - spec/nexus_mods_test/scenarios/nexus_mods/api/mod_file_spec.rb
154
156
  - spec/nexus_mods_test/scenarios/nexus_mods/api/mod_spec.rb
157
+ - spec/nexus_mods_test/scenarios/nexus_mods/api/mod_updates_spec.rb
155
158
  - spec/nexus_mods_test/scenarios/nexus_mods/nexus_mods_access_spec.rb
156
159
  - spec/nexus_mods_test/scenarios/nexus_mods/nexus_mods_caching_spec.rb
157
160
  - spec/nexus_mods_test/scenarios/nexus_mods/nexus_mods_common_spec.rb