wcc-contentful 1.3.2 → 1.4.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +80 -13
  3. data/lib/wcc/contentful/active_record_shim.rb +2 -2
  4. data/lib/wcc/contentful/configuration.rb +12 -5
  5. data/lib/wcc/contentful/downloads_schema.rb +14 -2
  6. data/lib/wcc/contentful/entry_locale_transformer.rb +107 -0
  7. data/lib/wcc/contentful/exceptions.rb +5 -0
  8. data/lib/wcc/contentful/link_visitor.rb +12 -1
  9. data/lib/wcc/contentful/middleware/store/caching_middleware.rb +25 -8
  10. data/lib/wcc/contentful/middleware/store/locale_middleware.rb +30 -0
  11. data/lib/wcc/contentful/middleware/store.rb +20 -16
  12. data/lib/wcc/contentful/model_builder.rb +9 -2
  13. data/lib/wcc/contentful/model_methods.rb +4 -6
  14. data/lib/wcc/contentful/simple_client/cdn.rb +5 -2
  15. data/lib/wcc/contentful/simple_client/management.rb +16 -0
  16. data/lib/wcc/contentful/simple_client.rb +1 -0
  17. data/lib/wcc/contentful/store/base.rb +6 -1
  18. data/lib/wcc/contentful/store/cdn_adapter.rb +13 -4
  19. data/lib/wcc/contentful/store/factory.rb +8 -1
  20. data/lib/wcc/contentful/store/memory_store.rb +27 -8
  21. data/lib/wcc/contentful/store/postgres_store.rb +4 -3
  22. data/lib/wcc/contentful/store/query/condition.rb +89 -0
  23. data/lib/wcc/contentful/store/query.rb +9 -35
  24. data/lib/wcc/contentful/store/rspec_examples/basic_store.rb +84 -12
  25. data/lib/wcc/contentful/store/rspec_examples/locale_queries.rb +220 -0
  26. data/lib/wcc/contentful/store/rspec_examples/operators/eq.rb +1 -1
  27. data/lib/wcc/contentful/store/rspec_examples.rb +13 -1
  28. data/lib/wcc/contentful/sync_engine.rb +1 -1
  29. data/lib/wcc/contentful/test/double.rb +1 -1
  30. data/lib/wcc/contentful/test/factory.rb +2 -4
  31. data/lib/wcc/contentful/version.rb +1 -1
  32. data/lib/wcc/contentful.rb +17 -6
  33. metadata +78 -46
@@ -34,6 +34,22 @@ class WCC::Contentful::SimpleClient::Management < WCC::Contentful::SimpleClient
34
34
  resp.assert_ok!
35
35
  end
36
36
 
37
+ def locales(**query)
38
+ resp =
39
+ _instrument 'locales', query: query do
40
+ get('locales', query)
41
+ end
42
+ resp.assert_ok!
43
+ end
44
+
45
+ def locale(key, query = {})
46
+ resp =
47
+ _instrument 'locales', content_type: key, query: query do
48
+ get("locales/#{key}", query)
49
+ end
50
+ resp.assert_ok!
51
+ end
52
+
37
53
  def editor_interface(content_type_id, query = {})
38
54
  resp =
39
55
  _instrument 'editor_interfaces', content_type: content_type_id, query: query do
@@ -124,6 +124,7 @@ module WCC::Contentful
124
124
 
125
125
  q = @query_defaults.dup
126
126
  q = q.merge(query) if query
127
+ q.compact!
127
128
 
128
129
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
129
130
  loop do
@@ -17,6 +17,10 @@ module WCC::Contentful::Store
17
17
  class Base
18
18
  include WCC::Contentful::Store::Interface
19
19
 
20
+ def initialize(configuration = nil)
21
+ @configuration = configuration || WCC::Contentful.configuration
22
+ end
23
+
20
24
  # Sets the value of the entry with the given ID in the store.
21
25
  # @abstract
22
26
  def set(_id, _value)
@@ -103,7 +107,8 @@ module WCC::Contentful::Store
103
107
  Query.new(
104
108
  self,
105
109
  content_type: content_type,
106
- options: options
110
+ options: options,
111
+ configuration: @configuration
107
112
  )
108
113
  end
109
114
 
@@ -29,7 +29,7 @@ module WCC::Contentful::Store
29
29
  end
30
30
 
31
31
  def find(key, hint: nil, **options)
32
- options = { locale: '*' }.merge!(options || {})
32
+ options = options&.dup || {}
33
33
  entry =
34
34
  if hint
35
35
  client.public_send(hint.underscore, key, options)
@@ -114,7 +114,7 @@ module WCC::Contentful::Store
114
114
  op = :in if op.nil?
115
115
  end
116
116
 
117
- param = parameter(field, operator: op, context: context, locale: true)
117
+ param = parameter(field, operator: op, context: context, locale: false)
118
118
 
119
119
  self.class.new(
120
120
  @store,
@@ -157,10 +157,10 @@ module WCC::Contentful::Store
157
157
  @response ||=
158
158
  if @relation[:content_type] == 'Asset'
159
159
  @client.assets(
160
- { locale: '*' }.merge!(@relation.reject { |k| k == :content_type }).merge!(@options)
160
+ @relation.reject { |k| k == :content_type }.merge(@options)
161
161
  )
162
162
  else
163
- @client.entries({ locale: '*' }.merge!(@relation).merge!(@options))
163
+ @client.entries(@relation.merge(@options))
164
164
  end
165
165
  end
166
166
 
@@ -180,6 +180,15 @@ module WCC::Contentful::Store
180
180
  included
181
181
  end
182
182
 
183
+ # Constructs the CDN query parameter from a structured field definition and
184
+ # operator.
185
+ # Notes:
186
+ # * "eq" can be omitted, e.g. 'fields.slug=/' is equivalent to 'fields.slug[eq]=/'
187
+ # * If "locale" is specified in the query, matching is done against that locale,
188
+ # unless the query explicitly specifies the locale. Examples:
189
+ # 'locale=es-US&fields.title=página principal' matches on the es locale
190
+ # 'locale=en-US&fields.title=página principal' returns nothing
191
+ # 'locale=en-US&fields.title.es-US=página principal' returns the page, but in the english locale.
183
192
  def parameter(field, operator: nil, context: nil, locale: false)
184
193
  if sys?(field)
185
194
  "#{field}#{op_param(operator)}"
@@ -5,6 +5,7 @@ require_relative 'memory_store'
5
5
  require_relative 'cdn_adapter'
6
6
  require_relative '../middleware/store'
7
7
  require_relative '../middleware/store/caching_middleware'
8
+ require_relative '../middleware/store/locale_middleware'
8
9
 
9
10
  module WCC::Contentful::Store
10
11
  # This factory presents a DSL for configuring the store stack. The store stack
@@ -46,6 +47,8 @@ module WCC::Contentful::Store
46
47
  self.middleware << [middleware, middleware_params, configure_proc]
47
48
  end
48
49
 
50
+ # Replaces a middleware in the chain. The middleware to replace is selected
51
+ # by matching the class.
49
52
  def replace(middleware, *middleware_params, &block)
50
53
  idx = self.middleware.find_index { |m| m[0] == middleware }
51
54
  raise ArgumentError, "Middleware #{middleware} not present" if idx.nil?
@@ -54,6 +57,8 @@ module WCC::Contentful::Store
54
57
  self.middleware[idx] = [middleware, middleware_params, configure_proc]
55
58
  end
56
59
 
60
+ # Removes a middleware from the chain, finding it by matching the class
61
+ # constant.
57
62
  def unuse(middleware)
58
63
  idx = self.middleware.find_index { |m| m[0] == middleware }
59
64
  return if idx.nil?
@@ -174,7 +179,9 @@ module WCC::Contentful::Store
174
179
  # The middleware that by default lives at the top of the middleware stack.
175
180
  def default_middleware
176
181
  [
177
- [WCC::Contentful::Store::InstrumentationMiddleware]
182
+ [WCC::Contentful::Store::InstrumentationMiddleware],
183
+ # Stores do not guarantee that the entry is resolved to the locale
184
+ [WCC::Contentful::Middleware::Store::LocaleMiddleware]
178
185
  ].freeze
179
186
  end
180
187
  end
@@ -7,9 +7,12 @@ module WCC::Contentful::Store
7
7
  # point for more useful implementations. It only implements equality queries
8
8
  # and does not support querying through an association.
9
9
  class MemoryStore < Base
10
- def initialize
10
+ delegate :locale_fallbacks, to: :@configuration
11
+
12
+ def initialize(configuration = nil)
11
13
  super
12
14
 
15
+ @configuration = configuration
13
16
  @mutex = Concurrent::ReentrantReadWriteLock.new
14
17
  @hash = {}
15
18
  end
@@ -36,7 +39,7 @@ module WCC::Contentful::Store
36
39
 
37
40
  def find(key, **_options)
38
41
  @mutex.with_read_lock do
39
- @hash[key]
42
+ @hash[key].deep_dup
40
43
  end
41
44
  end
42
45
 
@@ -64,9 +67,12 @@ module WCC::Contentful::Store
64
67
 
65
68
  # For each condition, we apply a new Enumerable#select with a block that
66
69
  # enforces the condition.
67
- query.conditions.reduce(relation) do |memo, condition|
68
- __send__("apply_#{condition.op}", memo, condition)
69
- end
70
+ relation =
71
+ query.conditions.reduce(relation) do |memo, condition|
72
+ __send__("apply_#{condition.op}", memo, condition)
73
+ end
74
+
75
+ relation.map(&:deep_dup)
70
76
  end
71
77
 
72
78
  private
@@ -80,8 +86,7 @@ module WCC::Contentful::Store
80
86
  end
81
87
 
82
88
  def eq?(entry, condition)
83
- # The condition's path tells us where to find the value in the JSON object
84
- val = entry.dig(*condition.path)
89
+ val = select_value_for_compare(entry, condition)
85
90
 
86
91
  # For arrays, equality is defined as does the array include the expected value.
87
92
  # See https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters/array-equality-inequality
@@ -101,7 +106,7 @@ module WCC::Contentful::Store
101
106
  end
102
107
 
103
108
  def in?(entry, condition)
104
- val = entry.dig(*condition.path)
109
+ val = select_value_for_compare(entry, condition)
105
110
 
106
111
  if val.is_a? Array
107
112
  # TODO: detect if in ruby 3.1 and use val.intersect?(condition.expected)
@@ -110,5 +115,19 @@ module WCC::Contentful::Store
110
115
  condition.expected.include?(val)
111
116
  end
112
117
  end
118
+
119
+ # Selects the value for the condition from the entry, taking into account locale fallbacks
120
+ def select_value_for_compare(entry, condition)
121
+ condition.each_locale_fallback do |cond|
122
+ # The condition's path tells us where to find the value in the JSON object
123
+ val = entry.dig(*cond.path)
124
+
125
+ # If the object has no value for this locale, try the fallbacks
126
+ next if val.nil?
127
+
128
+ # The object has a value for this locale, so we must compare against it and not the fallbacks
129
+ return val
130
+ end
131
+ end
113
132
  end
114
133
  end
@@ -17,8 +17,8 @@ module WCC::Contentful::Store
17
17
  attr_reader :connection_pool
18
18
  attr_accessor :logger
19
19
 
20
- def initialize(_config = nil, connection_options = nil, pool_options = nil)
21
- super()
20
+ def initialize(configuration = nil, connection_options = nil, pool_options = nil)
21
+ super(configuration)
22
22
  @schema_ensured = false
23
23
  connection_options ||= { dbname: 'postgres' }
24
24
  pool_options ||= {}
@@ -100,7 +100,8 @@ module WCC::Contentful::Store
100
100
  Query.new(
101
101
  self,
102
102
  content_type: content_type,
103
- options: options
103
+ options: options,
104
+ configuration: @configuration
104
105
  )
105
106
  end
106
107
 
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ class WCC::Contentful::Store::Query
4
+ Condition =
5
+ Struct.new(:path, :op, :expected, :locale_fallbacks) do
6
+ LINK_KEYS = %w[id type linkType].freeze # rubocop:disable Lint/ConstantDefinitionInBlock
7
+
8
+ ##
9
+ # Breaks the path into an array of tuples, where each tuple represents an
10
+ # entry subquery.
11
+ # If the query is a simple query on a field in an entry, there will be one
12
+ # tuple in the array:
13
+ # { 'title' => 'foo' } becomes
14
+ # [['fields', 'title', 'en-US']]
15
+ #
16
+ # If the query is a query through a link, there will be multiple tuples:
17
+ # { 'page' => { 'title' => 'foo' } } becomes
18
+ # [['fields', 'page', 'en-US'], ['fields', 'title', 'en-US']]
19
+ def path_tuples
20
+ return @path_tuples if @path_tuples
21
+
22
+ arr = []
23
+ remaining = path.dup
24
+ until remaining.empty?
25
+ locale = nil
26
+ link_sys = nil
27
+ link_field = nil
28
+
29
+ sys_or_fields = remaining.shift
30
+ field = remaining.shift
31
+ locale = remaining.shift if sys_or_fields == 'fields'
32
+
33
+ if remaining[0] == 'sys' && LINK_KEYS.include?(remaining[1])
34
+ link_sys = remaining.shift
35
+ link_field = remaining.shift
36
+ end
37
+
38
+ arr << [sys_or_fields, field, locale, link_sys, link_field].compact
39
+ end
40
+ @path_tuples = arr.freeze
41
+ end
42
+
43
+ ##
44
+ # Starting with the last part of the path that is a locale, iterates all the
45
+ # combinations of potential locale fallbacks.
46
+ # e.g. if the path is ['fields', 'page', 'es-MX', 'fields', 'title', 'es-MX']
47
+ # then we get:
48
+ # ['fields', 'page', 'es-MX', 'fields', 'title', 'es-MX'] (self)
49
+ # ['fields', 'page', 'es-MX', 'fields', 'title', 'es-US']
50
+ # ['fields', 'page', 'es-MX', 'fields', 'title', 'en-US']
51
+ # ['fields', 'page', 'es-US', 'fields', 'title', 'es-MX']
52
+ # ['fields', 'page', 'es-US', 'fields', 'title', 'es-US']
53
+ # ['fields', 'page', 'es-US', 'fields', 'title', 'en-US']
54
+ # ['fields', 'page', 'en-US', 'fields', 'title', 'es-MX']
55
+ # ['fields', 'page', 'en-US', 'fields', 'title', 'es-US']
56
+ # ['fields', 'page', 'en-US', 'fields', 'title', 'en-US']
57
+ def each_locale_fallback(&block)
58
+ return to_enum(:each_locale_fallback) unless block_given?
59
+
60
+ _each_locale_fallback(path_tuples, 0, &block)
61
+ end
62
+
63
+ private
64
+
65
+ # Find the next fallback tuples from this set of tuples
66
+ def _each_locale_fallback(original_tuples, start_at, &block)
67
+ tuples = original_tuples.deep_dup
68
+ varying = tuples[start_at]
69
+
70
+ if varying[2].nil?
71
+ # This is a non-localizable query, so just yield it
72
+ yield Condition.new(tuples.flatten, op, expected, locale_fallbacks)
73
+ return
74
+ end
75
+
76
+ while varying[2]
77
+ if tuples.length > start_at + 1
78
+ # There's more locales that we need to vary to the right, so recurse into those
79
+ _each_locale_fallback(tuples, start_at + 1, &block)
80
+ else
81
+ # We're the tail of the condition, so yield it.
82
+ yield Condition.new(tuples.flatten, op, expected, locale_fallbacks)
83
+ end
84
+
85
+ varying[2] = locale_fallbacks[varying[2]]
86
+ end
87
+ end
88
+ end
89
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative '../../contentful'
4
4
  require_relative './query/interface'
5
+ require_relative './query/condition'
5
6
 
6
7
  module WCC::Contentful::Store
7
8
  # The default query object returned by Stores that extend WCC::Contentful::Store::Base.
@@ -26,11 +27,12 @@ module WCC::Contentful::Store
26
27
 
27
28
  attr_reader :store, :content_type, :conditions
28
29
 
29
- def initialize(store, content_type:, conditions: nil, options: nil, **extra)
30
+ def initialize(store, content_type:, conditions: nil, options: nil, configuration: nil, **extra) # rubocop:disable Metrics/ParameterLists
30
31
  @store = store
31
32
  @content_type = content_type
32
33
  @conditions = conditions || []
33
34
  @options = options || {}
35
+ @configuration = configuration || WCC::Contentful.configuration
34
36
  @extra = extra
35
37
  end
36
38
 
@@ -60,7 +62,7 @@ module WCC::Contentful::Store
60
62
  # Can be an array, symbol, or dotted-notation path specification.
61
63
  # @expected The expected value to compare the field's value against.
62
64
  # @context A context object optionally containing `context[:locale]`
63
- def apply_operator(operator, field, expected, context = nil)
65
+ def apply_operator(operator, field, expected, _context = nil)
64
66
  operator ||= expected.is_a?(Array) ? :in : :eq
65
67
  raise ArgumentError, "Operator #{operator} not supported" unless respond_to?(operator)
66
68
  raise ArgumentError, 'value cannot be nil (try using exists: false)' if expected.nil?
@@ -75,10 +77,10 @@ module WCC::Contentful::Store
75
77
  field = field.to_s if field.is_a? Symbol
76
78
  path = field.is_a?(Array) ? field : field.split('.')
77
79
 
78
- path = self.class.normalize_condition_path(path, context)
80
+ path = self.class.normalize_condition_path(path, @options)
79
81
 
80
82
  _append_condition(
81
- Condition.new(path, operator, expected)
83
+ Condition.new(path, operator, expected, @configuration&.locale_fallbacks || {})
82
84
  )
83
85
  end
84
86
 
@@ -177,15 +179,15 @@ module WCC::Contentful::Store
177
179
  end
178
180
 
179
181
  def known_locales
180
- @known_locales = WCC::Contentful.locales.keys
182
+ @known_locales ||= WCC::Contentful.locales&.keys || ['en-US']
181
183
  end
182
184
  RESERVED_NAMES = %w[fields sys].freeze
183
185
 
184
186
  # Takes a path array in non-normal form and inserts 'sys', 'fields',
185
187
  # and the current locale as appropriate to normalize it.
186
188
  # rubocop:disable Metrics/BlockNesting
187
- def normalize_condition_path(path, context = nil)
188
- context_locale = context[:locale] if context.present?
189
+ def normalize_condition_path(path, options = nil)
190
+ context_locale = options[:locale]&.to_s if options.present?
189
191
  context_locale ||= 'en-US'
190
192
 
191
193
  rev_path = path.reverse
@@ -234,33 +236,5 @@ module WCC::Contentful::Store
234
236
  end
235
237
  # rubocop:enable Metrics/BlockNesting
236
238
  end
237
-
238
- Condition =
239
- Struct.new(:path, :op, :expected) do
240
- LINK_KEYS = %w[id type linkType].freeze # rubocop:disable Lint/ConstantDefinitionInBlock
241
-
242
- def path_tuples
243
- @path_tuples ||=
244
- [].tap do |arr|
245
- remaining = path.dup
246
- until remaining.empty?
247
- locale = nil
248
- link_sys = nil
249
- link_field = nil
250
-
251
- sys_or_fields = remaining.shift
252
- field = remaining.shift
253
- locale = remaining.shift if sys_or_fields == 'fields'
254
-
255
- if remaining[0] == 'sys' && LINK_KEYS.include?(remaining[1])
256
- link_sys = remaining.shift
257
- link_field = remaining.shift
258
- end
259
-
260
- arr << [sys_or_fields, field, locale, link_sys, link_field].compact
261
- end
262
- end
263
- end
264
- end
265
239
  end
266
240
  end
@@ -105,7 +105,7 @@ RSpec.shared_examples 'basic store' do
105
105
  "type": "Asset",
106
106
  "createdAt": "2018-02-12T19:53:39.309Z",
107
107
  "updatedAt": "2018-02-12T19:53:39.309Z",
108
- "revision": 1
108
+ "revision": 2
109
109
  },
110
110
  "fields": {
111
111
  "title": {
@@ -133,7 +133,10 @@ RSpec.shared_examples 'basic store' do
133
133
  describe '#set/#find' do
134
134
  describe 'ensures that the stored value is of type Hash' do
135
135
  it 'should not raise an error if value is a Hash' do
136
- data = { token: 'jenny_8675309' }
136
+ data = {
137
+ 'sys' => { 'id' => 'sync:token', 'type' => 'token' },
138
+ 'token' => 'state'
139
+ }
137
140
 
138
141
  # assert
139
142
  expect { subject.set('sync:token', data) }.to_not raise_error
@@ -146,7 +149,11 @@ RSpec.shared_examples 'basic store' do
146
149
  end
147
150
 
148
151
  it 'stores and finds data by ID' do
149
- data = { 'key' => 'val', '1' => { 'deep' => 9 } }
152
+ data = {
153
+ 'sys' => { 'id' => '1234' },
154
+ 'key' => 'val',
155
+ '1' => { 'deep' => 9 }
156
+ }
150
157
 
151
158
  # act
152
159
  subject.set('1234', data)
@@ -157,7 +164,11 @@ RSpec.shared_examples 'basic store' do
157
164
  end
158
165
 
159
166
  it 'find returns nil if key doesnt exist' do
160
- data = { 'key' => 'val', '1' => { 'deep' => 9 } }
167
+ data = {
168
+ 'sys' => { 'id' => '1234' },
169
+ 'key' => 'val',
170
+ '1' => { 'deep' => 9 }
171
+ }
161
172
  subject.set('1234', data)
162
173
 
163
174
  # act
@@ -181,8 +192,16 @@ RSpec.shared_examples 'basic store' do
181
192
  end
182
193
 
183
194
  it 'set returns prior value if exists' do
184
- data = { 'key' => 'val', '1' => { 'deep' => 9 } }
185
- data2 = { 'key' => 'val', '2' => { 'deep' => 11 } }
195
+ data = {
196
+ 'sys' => { 'id' => '1234', 'revision' => 1 },
197
+ 'key' => 'val',
198
+ '1' => { 'deep' => 9 }
199
+ }
200
+ data2 = {
201
+ 'sys' => { 'id' => '1234', 'revision' => 2 },
202
+ 'key' => 'val',
203
+ '1' => { 'deep' => 11 }
204
+ }
186
205
 
187
206
  # act
188
207
  prior1 = subject.set('1234', data)
@@ -193,11 +212,27 @@ RSpec.shared_examples 'basic store' do
193
212
  expect(prior2).to eq(data)
194
213
  expect(subject.find('1234')).to eq(data2)
195
214
  end
215
+
216
+ it 'modifying found entry does not modify underlying data' do
217
+ subject.index(entry)
218
+
219
+ # act
220
+ found = subject.find('1qLdW7i7g4Ycq6i4Cckg44')
221
+ found['fields']['slug']['en-US'] = 'new slug'
222
+
223
+ # assert
224
+ found2 = subject.find('1qLdW7i7g4Ycq6i4Cckg44')
225
+ expect(found2.dig('fields', 'slug', 'en-US')).to eq('redirect-with-slug-and-url')
226
+ end
196
227
  end
197
228
 
198
229
  describe '#delete' do
199
230
  it 'deletes an item out of the store' do
200
- data = { 'key' => 'val', '1' => { 'deep' => 9 } }
231
+ data = {
232
+ 'sys' => { 'id' => '1234' },
233
+ 'key' => 'val',
234
+ '1' => { 'deep' => 9 }
235
+ }
201
236
  subject.set('9999', data)
202
237
 
203
238
  # act
@@ -209,7 +244,11 @@ RSpec.shared_examples 'basic store' do
209
244
  end
210
245
 
211
246
  it "returns nil if item doesn't exist" do
212
- data = { 'key' => 'val', '1' => { 'deep' => 9 } }
247
+ data = {
248
+ 'sys' => { 'id' => '9999' },
249
+ 'key' => 'val',
250
+ '1' => { 'deep' => 9 }
251
+ }
213
252
  subject.set('9999', data)
214
253
 
215
254
  # act
@@ -327,7 +366,10 @@ RSpec.shared_examples 'basic store' do
327
366
  end
328
367
 
329
368
  it 'updates an "Asset" when exists' do
330
- existing = { 'test' => { 'data' => 'asdf' } }
369
+ existing = {
370
+ 'sys' => { 'id' => '3pWma8spR62aegAWAWacyA', 'revision' => 1 },
371
+ 'test' => { 'data' => 'asdf' }
372
+ }
331
373
  subject.set('3pWma8spR62aegAWAWacyA', existing)
332
374
 
333
375
  # act
@@ -341,7 +383,7 @@ RSpec.shared_examples 'basic store' do
341
383
  it 'does not overwrite an asset if revision is lower' do
342
384
  initial = asset
343
385
  updated = asset.deep_dup
344
- updated['sys']['revision'] = 2
386
+ updated['sys']['revision'] = 3
345
387
  updated['fields']['title']['en-US'] = 'test title'
346
388
 
347
389
  subject.index(updated)
@@ -355,7 +397,10 @@ RSpec.shared_examples 'basic store' do
355
397
  end
356
398
 
357
399
  it 'removes a "DeletedEntry"' do
358
- existing = { 'test' => { 'data' => 'asdf' } }
400
+ existing = {
401
+ 'sys' => { 'id' => '6HQsABhZDiWmi0ekCouUuy' },
402
+ 'test' => { 'data' => 'asdf' }
403
+ }
359
404
  subject.set('6HQsABhZDiWmi0ekCouUuy', existing)
360
405
 
361
406
  # act
@@ -381,7 +426,10 @@ RSpec.shared_examples 'basic store' do
381
426
  end
382
427
 
383
428
  it 'removes a "DeletedAsset"' do
384
- existing = { 'test' => { 'data' => 'asdf' } }
429
+ existing = {
430
+ 'sys' => { 'id' => '3pWma8spR62aegAWAWacyA' },
431
+ 'test' => { 'data' => 'asdf' }
432
+ }
385
433
  subject.set('3pWma8spR62aegAWAWacyA', existing)
386
434
 
387
435
  # act
@@ -528,6 +576,18 @@ RSpec.shared_examples 'basic store' do
528
576
  expect(found.dig('sys', 'id')).to eq('idTwo')
529
577
  expect(found.dig('fields', 'system', 'en-US')).to eq('Two')
530
578
  end
579
+
580
+ it 'modifying found entry does not modify underlying data' do
581
+ subject.index(entry)
582
+
583
+ # act
584
+ found = subject.find_by(filter: { 'sys.id' => '1qLdW7i7g4Ycq6i4Cckg44' }, content_type: 'redirect')
585
+ found['fields']['slug']['en-US'] = 'new slug'
586
+
587
+ # assert
588
+ found2 = subject.find_by(filter: { 'sys.id' => '1qLdW7i7g4Ycq6i4Cckg44' }, content_type: 'redirect')
589
+ expect(found2.dig('fields', 'slug', 'en-US')).to eq('redirect-with-slug-and-url')
590
+ end
531
591
  end
532
592
 
533
593
  describe '#find_all' do
@@ -572,6 +632,18 @@ RSpec.shared_examples 'basic store' do
572
632
  %w[k1 k5 k9]
573
633
  )
574
634
  end
635
+
636
+ it 'modifying found entry does not modify underlying data' do
637
+ subject.index(entry)
638
+
639
+ # act
640
+ found = subject.find_all(content_type: 'redirect').eq('sys.id', '1qLdW7i7g4Ycq6i4Cckg44').first
641
+ found['fields']['slug']['en-US'] = 'new slug'
642
+
643
+ # assert
644
+ found2 = subject.find_all(content_type: 'redirect').eq('sys.id', '1qLdW7i7g4Ycq6i4Cckg44').first
645
+ expect(found2.dig('fields', 'slug', 'en-US')).to eq('redirect-with-slug-and-url')
646
+ end
575
647
  end
576
648
 
577
649
  def make_link_to(id, link_type = 'Entry')