contentful_middleman 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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