contentful_middleman 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +7 -3
  3. data/CHANGELOG.md +10 -0
  4. data/CONTRIBUTING.md +1 -1
  5. data/Gemfile +1 -2
  6. data/Guardfile +5 -0
  7. data/README.md +122 -8
  8. data/contentful_middleman.gemspec +9 -3
  9. data/lib/contentful_middleman/commands/contentful.rb +0 -1
  10. data/lib/contentful_middleman/core.rb +18 -0
  11. data/lib/contentful_middleman/helpers.rb +29 -0
  12. data/lib/contentful_middleman/import_task.rb +6 -3
  13. data/lib/contentful_middleman/instance.rb +26 -5
  14. data/lib/contentful_middleman/mappers/base.rb +10 -2
  15. data/lib/contentful_middleman/version.rb +1 -1
  16. data/lib/contentful_middleman/webhook_handler.rb +33 -0
  17. data/spec/contentful_middleman/commands/context_spec.rb +22 -0
  18. data/spec/contentful_middleman/core_spec.rb +89 -0
  19. data/spec/contentful_middleman/helpers_spec.rb +99 -0
  20. data/spec/contentful_middleman/import_task_spec.rb +41 -0
  21. data/spec/contentful_middleman/instance_spec.rb +71 -0
  22. data/spec/contentful_middleman/local_data/file_spec.rb +34 -0
  23. data/spec/contentful_middleman/local_data/store_spec.rb +47 -0
  24. data/spec/contentful_middleman/mappers/base_spec.rb +88 -0
  25. data/spec/contentful_middleman/tools/backup_spec.rb +113 -0
  26. data/spec/contentful_middleman/version_hash_spec.rb +58 -0
  27. data/spec/contentful_middleman/webhook_handler_spec.rb +32 -0
  28. data/spec/fixtures/backup_fixtures/.tmp/backups/.gitkeep +0 -0
  29. data/spec/fixtures/backup_fixtures/baz +0 -0
  30. data/spec/fixtures/space_hash_fixtures/.blah-space-hash +1 -0
  31. data/spec/fixtures/space_hash_fixtures/.foo-space-hash +1 -0
  32. data/spec/fixtures/space_hash_fixtures/.foobar-space-hash +1 -0
  33. data/spec/fixtures/space_hash_fixtures/.my_space-space-hash +1 -0
  34. data/spec/fixtures/vcr_fixtures/instance/entries_1.yml +93 -0
  35. data/spec/fixtures/vcr_fixtures/instance/entries_2.yml +293 -0
  36. data/spec/fixtures/vcr_fixtures/mappers/entries.yml +913 -0
  37. data/spec/fixtures/vcr_fixtures/mappers/entries_localized.yml +996 -0
  38. data/spec/spec_helper.rb +57 -2
  39. metadata +153 -14
  40. data/TODO +0 -15
  41. data/spec/contentful_middleman_spec.rb +0 -5
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ class HelpersMock
4
+ include ContentfulMiddleman::Helpers
5
+ end
6
+
7
+ class InstanceDouble
8
+ end
9
+
10
+ describe ContentfulMiddleman::Helpers do
11
+ let(:entry) do
12
+ {
13
+ value_field: {
14
+ 'es' => 'foo',
15
+ 'en-US' => 'bar'
16
+ },
17
+ array_field: [
18
+ {
19
+ 'es' => 'foobar',
20
+ 'en-US' => 'baz'
21
+ }
22
+ ]
23
+ }
24
+ end
25
+
26
+ subject { HelpersMock.new }
27
+
28
+ before(:each) do
29
+ ContentfulMiddleman.instance_variable_set(:@contentful_middleman_instances, [])
30
+ end
31
+
32
+
33
+ describe 'instance methods' do
34
+ describe '#contentful_instances' do
35
+ it 'default - is an empty array' do
36
+ expect(subject.contentful_instances).to eq([])
37
+ end
38
+
39
+ it 'returns multiple instances' do
40
+ ContentfulMiddleman.instances << InstanceDouble.new
41
+ ContentfulMiddleman.instances << InstanceDouble.new
42
+
43
+ expect(subject.contentful_instances.size).to eq(2)
44
+ end
45
+ end
46
+
47
+ describe 'localization helpers' do
48
+ describe '#localize_value' do
49
+ it 'returns value if not a hash independently of locale' do
50
+ expect(subject.localize_value('foo', 'es')).to eq('foo')
51
+ end
52
+
53
+ describe 'value is a hash' do
54
+ it 'returns fallback_locale value if locale not found' do
55
+ expect(subject.localize_value({'en-US' => 'foo'}, 'es')).to eq('foo')
56
+ expect(subject.localize_value({'de-DE' => 'bar'}, 'es', 'de-DE')).to eq('bar')
57
+ end
58
+
59
+ it 'returns localized value if locale found' do
60
+ expect(subject.localize_value({'es' => 'foobar'}, 'es')).to eq('foobar')
61
+ end
62
+
63
+ it 'fails if locale or fallback_locale not found' do
64
+ expect { subject.localize_value({'de-DE' => 'baz'}, 'es') }.to raise_error KeyError
65
+ end
66
+ end
67
+ end
68
+
69
+ describe '#localize_array' do
70
+ it 'calls #localize_value for every element in the array' do
71
+ expect(subject).to receive(:localize_value).with({'es' => 'foo'}, 'es', 'en-US')
72
+
73
+ subject.localize_array([{'es' => 'foo'}], 'es')
74
+ end
75
+ end
76
+
77
+ describe '#localize' do
78
+ it 'calls #localize_value for a value field' do
79
+ expect(subject).to receive(:localize_value).with({'es' => 'foo', 'en-US' => 'bar'}, 'es', 'en-US').and_call_original
80
+
81
+ expect(subject.localize(entry, :value_field, 'es')).to eq('foo')
82
+ end
83
+
84
+ it 'calls #localize_array for an array field' do
85
+ expect(subject).to receive(:localize_array).with([{'es' => 'foobar', 'en-US' => 'baz'}], 'es', 'en-US').and_call_original
86
+
87
+ expect(subject.localize(entry, :array_field, 'es')).to eq(['foobar'])
88
+ end
89
+ end
90
+
91
+ it '#localize_entry' do
92
+ expect(subject.localize_entry(entry, 'es')).to eq({
93
+ value_field: 'foo',
94
+ array_field: ['foobar']
95
+ })
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ class ClientDouble
4
+ def entries
5
+ []
6
+ end
7
+ end
8
+
9
+ describe ContentfulMiddleman::ImportTask do
10
+ let(:path) { File.expand_path(File.join(File.dirname(__FILE__), '..', 'fixtures', 'space_hash_fixtures')) }
11
+ subject { described_class.new 'foobar', {}, {}, ClientDouble.new }
12
+
13
+ describe 'instance methods' do
14
+ before do
15
+ ContentfulMiddleman::VersionHash.source_root = path
16
+ end
17
+
18
+ describe '#run' do
19
+ it 'doesnt change when data did not change' do
20
+ expect_any_instance_of(ContentfulMiddleman::LocalData::Store).to receive(:write)
21
+
22
+ subject.run
23
+ expect(subject.changed_local_data?).to eq(false)
24
+ end
25
+
26
+ it 'changes when data is new' do
27
+ subject = described_class.new 'blah', {}, {}, ClientDouble.new
28
+
29
+ if ::File.exist?(::File.join(path, '.blah-space-hash'))
30
+ ::File.delete(::File.join(path, '.blah-space-hash'))
31
+ end
32
+
33
+ expect_any_instance_of(ContentfulMiddleman::LocalData::Store).to receive(:write)
34
+
35
+ subject.run
36
+
37
+ expect(subject.changed_local_data?).to eq(true)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ class ExtensionDouble
4
+ attr_reader :options
5
+ def initialize(options = OptionsDouble.new)
6
+ @options = options
7
+ end
8
+ end
9
+
10
+ class MapperDouble
11
+ end
12
+
13
+ describe ContentfulMiddleman::Instance do
14
+ let(:extension) { ExtensionDouble.new }
15
+ let(:options) { extension.options }
16
+ subject { described_class.new(extension) }
17
+
18
+ describe 'instance methods' do
19
+ describe '#entries' do
20
+ it 'gets entries from API' do
21
+ vcr('instance/entries_1') {
22
+ client = subject.send(:client)
23
+
24
+ expect(client).to receive(:entries).with(options.cda_query)
25
+
26
+ subject.entries
27
+ }
28
+ end
29
+
30
+ it 'all_entries' do
31
+ vcr('instance/entries_2') {
32
+ allow(options).to receive(:all_entries) { true }
33
+ client = subject.send(:client)
34
+
35
+ expect(client).to receive(:entries).with(limit: 1).and_call_original
36
+ expect(client).to receive(:entries).with(options.cda_query.merge(limit: 1000, skip: 0, order: 'sys.createdAt')).and_call_original
37
+
38
+ subject.entries
39
+ }
40
+ end
41
+ end
42
+
43
+ it '#space_name' do
44
+ expect(subject.space_name).to eq('cats')
45
+ end
46
+
47
+ describe '#content_types_ids_to_mappers' do
48
+ it 'returns an empty hash if none set' do
49
+ expect(subject.content_types_ids_to_mappers).to eq({})
50
+ end
51
+
52
+ it 'returns a hash of ct_id => mapper' do
53
+ allow(options).to receive(:content_types) { {an_id: {mapper: MapperDouble}} }
54
+
55
+ expect(subject.content_types_ids_to_mappers).to eq({an_id: MapperDouble})
56
+ end
57
+ end
58
+
59
+ describe '#content_types_ids_to_names' do
60
+ it 'returns an empty hash if none set' do
61
+ expect(subject.content_types_ids_to_names).to eq({})
62
+ end
63
+
64
+ it 'returns a hash of ct_id => name' do
65
+ allow(options).to receive(:content_types) { {an_id: {name: 'foo'}} }
66
+
67
+ expect(subject.content_types_ids_to_names).to eq({an_id: 'foo'})
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ class ThorDouble
4
+ def create_file(path, *args, &block)
5
+ end
6
+ end
7
+
8
+ describe ContentfulMiddleman::LocalData::File do
9
+ describe 'class methods' do
10
+ it '::thor= / ::thor' do
11
+ expect(described_class.thor).to eq nil
12
+
13
+ described_class.thor = 'foo'
14
+
15
+ expect(described_class.thor).to eq 'foo'
16
+ end
17
+ end
18
+
19
+ describe 'instance methods' do
20
+ let(:thor) { ThorDouble.new }
21
+ subject { described_class.new 'foo', 'bar' }
22
+
23
+ before do
24
+ ContentfulMiddleman::LocalData::Store.base_path = 'foo'
25
+ described_class.thor = thor
26
+ end
27
+
28
+ it '#write' do
29
+ expect(thor).to receive(:create_file).with(::File.join('foo', 'bar.yaml'), nil, {})
30
+
31
+ subject.write
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ class FileDouble
4
+ def write
5
+ end
6
+ end
7
+
8
+ describe ContentfulMiddleman::LocalData::Store do
9
+ let(:path) { File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'backup_fixtures')) }
10
+
11
+ before do
12
+ described_class.base_path = nil
13
+ end
14
+
15
+ describe 'class methods' do
16
+ it '::base_path / ::base_path=' do
17
+ expect(described_class.base_path).to eq nil
18
+
19
+ described_class.base_path = 'foo'
20
+
21
+ expect(described_class.base_path).to eq 'foo'
22
+ end
23
+ end
24
+
25
+ describe 'instance methods' do
26
+ before do
27
+ described_class.base_path = 'foo'
28
+ end
29
+
30
+ let(:file) { FileDouble.new }
31
+ subject { described_class.new [file], path }
32
+
33
+ describe '#write' do
34
+ it 'writes with backup' do
35
+ expect(subject).to receive(:do_with_backup)
36
+
37
+ subject.write
38
+ end
39
+
40
+ it 'calls write on every file object' do
41
+ expect(file).to receive(:write)
42
+
43
+ subject.write
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ describe ContentfulMiddleman::Mapper::Base do
4
+ let(:entries) do
5
+ vcr('mappers/entries') {
6
+ client = Contentful::Client.new(
7
+ access_token: 'b4c0n73n7fu1',
8
+ space: 'cfexampleapi',
9
+ dynamic_entries: :auto
10
+ )
11
+
12
+ client.entries
13
+ }
14
+ end
15
+
16
+ let(:entries_localized) do
17
+ vcr('mappers/entries_localized') {
18
+ client = Contentful::Client.new(
19
+ access_token: 'b4c0n73n7fu1',
20
+ space: 'cfexampleapi',
21
+ dynamic_entries: :auto
22
+ )
23
+
24
+ client.entries(locale: '*', include: 1)
25
+ }
26
+ end
27
+
28
+ let(:options) { OptionsDouble.new }
29
+ subject { described_class.new entries, options }
30
+
31
+ describe 'instance methods' do
32
+ let(:context) { ContentfulMiddleman::Context.new }
33
+
34
+ describe '#map' do
35
+ it 'maps entries without multiple locales' do
36
+ expect(context.hashize).to eq({})
37
+
38
+ expected = {
39
+ :id=>"6KntaYXaHSyIw8M6eo26OK",
40
+ :name=>"Doge",
41
+ :image=> {
42
+ :title=>"Doge",
43
+ :url=> "//images.contentful.com/cfexampleapi/1x0xpXu4pSGS4OukSyWGUK/cc1239c6385428ef26f4180190532818/doge.jpg"
44
+ },
45
+ :description=>"such json\nwow"
46
+ }
47
+
48
+ subject.map(context, entries.first)
49
+
50
+ expect(context.hashize).to eq(expected)
51
+ end
52
+
53
+ it 'maps entries with multiple locales' do
54
+ subject = described_class.new entries, OptionsDouble.new(cda_query: {locale: '*'})
55
+ expect(context.hashize).to eq({})
56
+
57
+ expected = {
58
+ :id=>"6KntaYXaHSyIw8M6eo26OK",
59
+ :name=> {
60
+ :'en-US'=>"Doge"
61
+ },
62
+ :image=>{
63
+ :'en-US'=>{
64
+ "sys"=>{
65
+ "type"=>"Link",
66
+ "linkType"=>"Asset",
67
+ "id"=>"1x0xpXu4pSGS4OukSyWGUK"
68
+ }
69
+ }
70
+ },
71
+ :description=>{
72
+ :'en-US'=>"such json\nwow"
73
+ }
74
+ }
75
+
76
+ subject.map(context, entries_localized.first)
77
+
78
+ expect(context.hashize).to eq(expected)
79
+ end
80
+ end
81
+ end
82
+
83
+ describe 'attributes' do
84
+ it ':entries' do
85
+ expect(subject.entries).to match(entries)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,113 @@
1
+ require 'spec_helper'
2
+
3
+ class DoBackupDouble
4
+ include ContentfulMiddleman::Tools::Backup::InstanceMethods
5
+ end
6
+
7
+ describe ContentfulMiddleman::Tools::NullBackup do
8
+ describe 'instance methods' do
9
+ describe 'do nothing' do
10
+ it '#restore' do
11
+ expect(subject.restore).to eq nil
12
+ end
13
+
14
+ it '#destroy' do
15
+ expect(subject.destroy).to eq nil
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ describe ContentfulMiddleman::Tools::Backup do
22
+ let(:path) { File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'backup_fixtures')) }
23
+ subject { described_class.new('foo', 'foo_source') }
24
+
25
+ before do
26
+ ENV['MM_ROOT'] = path
27
+ end
28
+
29
+ describe 'class methods' do
30
+ it '::basepath' do
31
+ expect(described_class.basepath).to eq File.join(path, '.tmp', 'backups')
32
+ end
33
+
34
+ describe '::ensure_backup_path!' do
35
+ it 'does nothing if ::basepath exists' do
36
+ expect(FileUtils).not_to receive(:mkdir_p)
37
+
38
+ described_class.ensure_backup_path!
39
+ end
40
+
41
+ it 'creates basepath directory if it doesnt exist' do
42
+ expect(FileUtils).to receive(:mkdir_p).with(described_class.basepath).and_call_original
43
+
44
+ FileUtils.rm_rf(described_class.basepath)
45
+
46
+ expect(::File.exist?(described_class.basepath)).to be_falsey
47
+
48
+ described_class.ensure_backup_path!
49
+
50
+ expect(::File.exist?(described_class.basepath)).to be_truthy
51
+ end
52
+ end
53
+ end
54
+
55
+ describe 'instance methods' do
56
+ before do
57
+ # Initialize calls this
58
+ allow(FileUtils).to receive(:mkdir)
59
+ allow(FileUtils).to receive(:mv)
60
+ end
61
+
62
+ it '#restore' do
63
+ expect(FileUtils).to receive(:rm_rf).with('foo_source')
64
+ expect(FileUtils).to receive(:mv).with(/foo-/, 'foo_source')
65
+
66
+ subject.restore
67
+ end
68
+
69
+ it '#destroy' do
70
+ expect(FileUtils).to receive(:rm_rf).with(/foo-/)
71
+
72
+ subject.destroy
73
+ end
74
+ end
75
+ end
76
+
77
+ describe DoBackupDouble do
78
+ let(:path) { File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'backup_fixtures')) }
79
+
80
+ before do
81
+ ENV['MM_ROOT'] = path
82
+
83
+ # Backup::Initialize calls this
84
+ allow(FileUtils).to receive(:mkdir)
85
+ allow(FileUtils).to receive(:mv)
86
+ end
87
+
88
+ describe 'ContentfulMiddleman::Tools::Backup::InstanceMethods' do
89
+ describe 'instance methods' do
90
+ describe '#do_with_backup' do
91
+ it 'when invalid path, uses a NullBackup and does nothing' do
92
+ expect(ContentfulMiddleman::Tools::NullBackup).to receive(:new).and_call_original
93
+ expect_any_instance_of(ContentfulMiddleman::Tools::NullBackup).to receive(:destroy)
94
+ subject.do_with_backup('bar', 'baz') { }
95
+ end
96
+
97
+ it 'when valid path' do
98
+ expect(ContentfulMiddleman::Tools::Backup).to receive(:new).with('foo', File.join(path, 'baz')).and_call_original
99
+ expect_any_instance_of(ContentfulMiddleman::Tools::Backup).to receive(:destroy)
100
+ subject.do_with_backup('foo', File.join(path, 'baz')) { }
101
+ end
102
+
103
+ it 'when error thrown on block, it calls restore' do
104
+ expect(ContentfulMiddleman::Tools::Backup).to receive(:new).with('foo', File.join(path, 'baz')).and_call_original
105
+ expect_any_instance_of(ContentfulMiddleman::Tools::Backup).to receive(:restore)
106
+ expect_any_instance_of(ContentfulMiddleman::Tools::Backup).to receive(:destroy)
107
+
108
+ expect { subject.do_with_backup('foo', File.join(path, 'baz')) { raise 'some_error' } }.to raise_error 'some_error'
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ class EntryDouble
4
+ attr_reader :id, :updated_at
5
+
6
+ def initialize(id, updated_at)
7
+ @id = id
8
+ @updated_at = updated_at
9
+ end
10
+ end
11
+
12
+ describe ContentfulMiddleman::VersionHash do
13
+ let(:path) { File.expand_path(File.join(File.dirname(__FILE__), '..', 'fixtures', 'space_hash_fixtures')) }
14
+ describe 'class methods' do
15
+ it '::source_root=' do
16
+ described_class.source_root = 'foobar'
17
+ expect(described_class.instance_variable_get(:@source_root)).to eq('foobar')
18
+ end
19
+
20
+ describe '::read_for_space' do
21
+ before do
22
+ described_class.source_root = path
23
+ end
24
+
25
+ it 'unhashed space returns nil' do
26
+ expect(described_class.read_for_space('i_dont_exist')).to eq(nil)
27
+ end
28
+
29
+ it 'hashed space returns hash' do
30
+ expect(described_class.read_for_space('foo').chomp).to eq('bar')
31
+ end
32
+ end
33
+
34
+ describe '::write_for_space_with_entries' do
35
+ let(:entries) { [EntryDouble.new(1, '2015-11-25'), EntryDouble.new(2, '2015-11-25')] }
36
+
37
+ before do
38
+ described_class.source_root = path
39
+ end
40
+
41
+ it 'hashes entries and saves them on a file' do
42
+ allow(::File).to receive(:open).with(File.join(path, '.my_space-space-hash'), 'w')
43
+ expect(Digest::SHA1).to receive(:hexdigest)
44
+
45
+ described_class.write_for_space_with_entries('my_space', entries)
46
+ end
47
+
48
+ it 'matches hash on next read' do
49
+ sorted_entries = entries.sort { |a, b| a.id <=> b.id }
50
+ hash = Digest::SHA1.hexdigest(sorted_entries.map { |e| "#{e.id}#{e.updated_at}" }.join)
51
+
52
+ described_class.write_for_space_with_entries('my_space', entries)
53
+
54
+ expect(described_class.read_for_space('my_space')).to eq(hash)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ class ContentfulMiddleman::WebhookHandler
4
+ def sleep(*)
5
+ nil
6
+ end
7
+
8
+ def system(*)
9
+ nil
10
+ end
11
+ end
12
+
13
+ describe ContentfulMiddleman::WebhookHandler do
14
+ subject { described_class.new ServerDouble.new, Logger.new(STDOUT), 10 }
15
+
16
+ describe 'class methods' do
17
+ it '::start' do
18
+ expect(Contentful::Webhook::Listener::Server).to receive(:start)
19
+
20
+ described_class.start webhook_timeout: 10
21
+ end
22
+ end
23
+
24
+ describe 'instance methods' do
25
+ it '#perform' do
26
+ expect(subject.logger).to receive(:info).twice
27
+ expect(subject).to receive(:sleep).with(10)
28
+ expect(subject).to receive(:system).with('bundle exec middleman contentful --rebuild')
29
+ subject.perform(RequestDouble.new, ResponseDouble.new)
30
+ end
31
+ end
32
+ end
File without changes
@@ -0,0 +1 @@
1
+ da39a3ee5e6b4b0d3255bfef95601890afd80709
@@ -0,0 +1 @@
1
+ da39a3ee5e6b4b0d3255bfef95601890afd80709
@@ -0,0 +1 @@
1
+ bb12d7986a7a821a9d1ede65bc8ce73a1dc6678c