nexus_mods 2.0.1 → 2.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.
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