cloudinary 1.13.0 → 1.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/cloudinary.gemspec +1 -2
  4. data/lib/active_storage/service/cloudinary_service.rb +0 -1
  5. data/lib/cloudinary/utils.rb +1 -1
  6. data/lib/cloudinary/version.rb +1 -1
  7. metadata +3 -185
  8. data/spec/access_control_spec.rb +0 -102
  9. data/spec/active_storage/Gemfile +0 -12
  10. data/spec/active_storage/application_system_test_case.rb +0 -5
  11. data/spec/active_storage/database/create_users_migration.rb +0 -9
  12. data/spec/active_storage/database/setup.rb +0 -7
  13. data/spec/active_storage/dummy/Rakefile +0 -5
  14. data/spec/active_storage/dummy/app/assets/config/manifest.js +0 -3
  15. data/spec/active_storage/dummy/app/assets/javascripts/application.js +0 -13
  16. data/spec/active_storage/dummy/app/assets/stylesheets/application.css +0 -15
  17. data/spec/active_storage/dummy/app/controllers/application_controller.rb +0 -5
  18. data/spec/active_storage/dummy/app/helpers/application_helper.rb +0 -4
  19. data/spec/active_storage/dummy/app/jobs/application_job.rb +0 -4
  20. data/spec/active_storage/dummy/app/models/application_record.rb +0 -5
  21. data/spec/active_storage/dummy/app/views/layouts/application.html.erb +0 -14
  22. data/spec/active_storage/dummy/bin/bundle +0 -5
  23. data/spec/active_storage/dummy/bin/rails +0 -6
  24. data/spec/active_storage/dummy/bin/rake +0 -6
  25. data/spec/active_storage/dummy/bin/yarn +0 -11
  26. data/spec/active_storage/dummy/config.ru +0 -7
  27. data/spec/active_storage/dummy/config/application.rb +0 -22
  28. data/spec/active_storage/dummy/config/boot.rb +0 -7
  29. data/spec/active_storage/dummy/config/database.yml +0 -25
  30. data/spec/active_storage/dummy/config/environment.rb +0 -7
  31. data/spec/active_storage/dummy/config/environments/development.rb +0 -52
  32. data/spec/active_storage/dummy/config/environments/production.rb +0 -83
  33. data/spec/active_storage/dummy/config/environments/test.rb +0 -38
  34. data/spec/active_storage/dummy/config/initializers/application_controller_renderer.rb +0 -7
  35. data/spec/active_storage/dummy/config/initializers/assets.rb +0 -16
  36. data/spec/active_storage/dummy/config/initializers/backtrace_silencers.rb +0 -8
  37. data/spec/active_storage/dummy/config/initializers/cookies_serializer.rb +0 -7
  38. data/spec/active_storage/dummy/config/initializers/filter_parameter_logging.rb +0 -6
  39. data/spec/active_storage/dummy/config/initializers/inflections.rb +0 -17
  40. data/spec/active_storage/dummy/config/initializers/mime_types.rb +0 -5
  41. data/spec/active_storage/dummy/config/initializers/wrap_parameters.rb +0 -16
  42. data/spec/active_storage/dummy/config/routes.rb +0 -4
  43. data/spec/active_storage/dummy/config/secrets.yml +0 -32
  44. data/spec/active_storage/dummy/config/spring.rb +0 -8
  45. data/spec/active_storage/dummy/config/storage.yml +0 -3
  46. data/spec/active_storage/dummy/config/webpacker.yml +0 -72
  47. data/spec/active_storage/dummy/package.json +0 -5
  48. data/spec/active_storage/dummy/public/404.html +0 -67
  49. data/spec/active_storage/dummy/public/422.html +0 -67
  50. data/spec/active_storage/dummy/public/500.html +0 -66
  51. data/spec/active_storage/dummy/public/apple-touch-icon-precomposed.png +0 -0
  52. data/spec/active_storage/dummy/public/apple-touch-icon.png +0 -0
  53. data/spec/active_storage/dummy/public/favicon.ico +0 -0
  54. data/spec/active_storage/fixtures/files/colors.bmp +0 -0
  55. data/spec/active_storage/fixtures/files/empty_file.txt +0 -0
  56. data/spec/active_storage/fixtures/files/favicon.ico +0 -0
  57. data/spec/active_storage/fixtures/files/icon.psd +0 -0
  58. data/spec/active_storage/fixtures/files/icon.svg +0 -13
  59. data/spec/active_storage/fixtures/files/image.gif +0 -0
  60. data/spec/active_storage/fixtures/files/racecar.jpg +0 -0
  61. data/spec/active_storage/fixtures/files/racecar.tif +0 -0
  62. data/spec/active_storage/fixtures/files/racecar_rotated.jpg +0 -0
  63. data/spec/active_storage/fixtures/files/report.pdf +0 -0
  64. data/spec/active_storage/fixtures/files/rotated_video.mp4 +0 -0
  65. data/spec/active_storage/fixtures/files/video.mp4 +0 -0
  66. data/spec/active_storage/fixtures/files/video_with_rectangular_samples.mp4 +0 -0
  67. data/spec/active_storage/fixtures/files/video_with_undefined_display_aspect_ratio.mp4 +0 -0
  68. data/spec/active_storage/fixtures/files/video_without_video_stream.mp4 +0 -0
  69. data/spec/active_storage/service/cloudinary_service_spec.rb +0 -107
  70. data/spec/active_storage/service/configurations.yml +0 -4
  71. data/spec/active_storage/test_helper.rb +0 -43
  72. data/spec/api_spec.rb +0 -623
  73. data/spec/archive_spec.rb +0 -145
  74. data/spec/auth_token_spec.rb +0 -77
  75. data/spec/cache_spec.rb +0 -109
  76. data/spec/cloudinary_helper_spec.rb +0 -327
  77. data/spec/cloudinary_spec.rb +0 -56
  78. data/spec/data/sync_static/app/assets/javascripts/1.coffee +0 -1
  79. data/spec/data/sync_static/app/assets/javascripts/1.js +0 -1
  80. data/spec/data/sync_static/app/assets/stylesheets/1.css +0 -3
  81. data/spec/docx.docx +0 -0
  82. data/spec/favicon.ico +0 -0
  83. data/spec/image_spec.rb +0 -107
  84. data/spec/logo.png +0 -0
  85. data/spec/movie.mp4 +0 -0
  86. data/spec/rake_spec.rb +0 -160
  87. data/spec/sample_asset_file.tsv +0 -4
  88. data/spec/search_spec.rb +0 -109
  89. data/spec/spec_helper.rb +0 -273
  90. data/spec/storage_spec.rb +0 -44
  91. data/spec/streaminig_profiles_api_spec.rb +0 -74
  92. data/spec/support/helpers/temp_file_helpers.rb +0 -22
  93. data/spec/support/shared_contexts/rake.rb +0 -19
  94. data/spec/uploader_spec.rb +0 -409
  95. data/spec/utils_methods_spec.rb +0 -81
  96. data/spec/utils_spec.rb +0 -1052
  97. data/spec/video_tag_spec.rb +0 -253
  98. data/spec/video_url_spec.rb +0 -185
@@ -1,253 +0,0 @@
1
- require 'rspec'
2
- require 'spec_helper'
3
- require 'cloudinary'
4
- require 'action_view'
5
- require 'cloudinary/helper'
6
- require 'rails/version'
7
-
8
- if ::Rails::VERSION::MAJOR < 4
9
- def config
10
- @config ||= {}
11
- end
12
- def controller
13
- @controller ||={}
14
- end
15
- end
16
- describe CloudinaryHelper do
17
-
18
- before :all do
19
- # Test the helper in the context it runs in in production
20
- ActionView::Base.send :include, CloudinaryHelper
21
-
22
- end
23
- before(:each) do
24
- Cloudinary.reset_config
25
- Cloudinary.config do |config|
26
- config.cloud_name = DUMMY_CLOUD
27
- config.secure_distribution = nil
28
- config.private_cdn = false
29
- config.secure = false
30
- config.cname = nil
31
- config.cdn_subdomain = false
32
- config.api_key = "1234"
33
- config.api_secret = "b"
34
- end
35
- end
36
-
37
- let(:helper) {
38
- ActionView::Base.new
39
- }
40
- let(:root_path) { "http://res.cloudinary.com/#{DUMMY_CLOUD}" }
41
- let(:upload_path) { "#{root_path}/video/upload" }
42
-
43
- describe 'cl_video_tag' do
44
- let(:basic_options) { { :cloud_name => DUMMY_CLOUD} }
45
- let(:options) { basic_options }
46
- let(:test_tag) { TestTag.new helper.cl_video_tag("movie", options) }
47
- context "when options include video tag attributes" do
48
- let(:options) { basic_options.merge({ :autoplay => true,
49
- :controls => true,
50
- :loop => true,
51
- :muted => true,
52
- :preload => true }) }
53
- it "should support video tag parameters" do
54
- expect(test_tag.attributes.keys).to include("autoplay", "controls", "loop", "muted", "preload")
55
- end
56
- end
57
-
58
- context 'when given transformations' do
59
- let(:options) {
60
- basic_options.merge(
61
- :source_types => "mp4",
62
- :html_height => "100",
63
- :html_width => "200",
64
- :crop => :scale,
65
- :height => "200",
66
- :width => "400",
67
- :video_codec => { :codec => 'h264' },
68
- :audio_codec => 'acc',
69
- :start_offset => 3) }
70
-
71
- it 'should create a tag with "src" attribute that includes the transformations' do
72
- expect(test_tag["src"]).to be_truthy
73
- expect(test_tag["src"]).to include("ac_acc")
74
- expect(test_tag["src"]).to include("vc_h264")
75
- expect(test_tag["src"]).to include("so_3")
76
- expect(test_tag["src"]).to include("w_400")
77
- expect(test_tag["src"]).to include("h_200")
78
- end
79
- it 'should have the correct tag height' do
80
- expect(test_tag["src"]).to include("ac_acc")
81
- expect(test_tag["height"]).to eq("100")
82
- end
83
- end
84
-
85
- describe ":source_types" do
86
- context "when a single source type is provided" do
87
- let(:options) { basic_options.merge(:source_types => "mp4") }
88
- it "should create a video tag" do
89
- expect(test_tag.name).to eq("video")
90
- expect(test_tag['src']).to eq( "#{upload_path}/movie.mp4")
91
- end
92
- it "should not have a `type` attribute" do
93
- expect(test_tag.attributes).not_to include("type")
94
- end
95
- it "should not have inner `source` tags" do
96
- expect(test_tag.children.map(&:name)).not_to include("source")
97
- end
98
- end
99
-
100
- context 'when provided with multiple source types' do
101
- let(:options) { basic_options.merge(:source_types => %w(mp4 webm ogv)) }
102
- it "should create a tag with multiple source tags" do
103
- expect(test_tag.children.length).to eq(3)
104
- expect(test_tag.children[0].name).to eq("source")
105
- expect(test_tag.children[1].name).to eq("source")
106
- expect(test_tag.children.map{|c| c['src']}).to all( include("video/upload"))
107
- end
108
- it "should order the source tags according to the order of the source_types" do
109
- expect(test_tag.children[0][:type]).to eq("video/mp4")
110
- expect(test_tag.children[1][:type]).to eq("video/webm")
111
- expect(test_tag.children[2][:type]).to eq("video/ogg")
112
- end
113
- end
114
- end
115
-
116
- describe ":poster" do
117
- context "when poster is not provided" do
118
- it "should default to jpg with the video transformation" do
119
- expect(test_tag[:poster]).to eq(helper.cl_video_thumbnail_path("movie", { :format => 'jpg' }))
120
- end
121
- end
122
-
123
- context "when given a string" do
124
- let(:options) { basic_options.merge(:poster => TEST_IMAGE_URL) }
125
- it "should include a poster attribute with the given string as url" do
126
- expect(test_tag.attributes).to include('poster')
127
- expect(test_tag[:poster]).to eq(TEST_IMAGE_URL)
128
- end
129
- end
130
-
131
- context "when poster is a hash" do
132
- let(:options) { basic_options.merge(:poster => { :gravity => "north" }) }
133
- it "should include a poster attribute with the given options" do
134
- expect(test_tag[:poster]).to eq("#{upload_path}/g_north/movie.jpg")
135
- end
136
- context "when a public id is provided" do
137
- let(:options) { basic_options.merge(:poster => { :public_id => 'myposter.jpg', :gravity => "north" }) }
138
- it "should include a poster attribute with an image path and the given options" do
139
- expect(test_tag[:poster]).to eq("#{root_path}/image/upload/g_north/myposter.jpg")
140
- end
141
-
142
-
143
- end
144
- end
145
-
146
- context "when poster parameter is nil or false" do
147
- let(:options) { basic_options.merge(:poster => nil) }
148
- it "should not include a poster attribute in the tag for nil" do
149
- expect(test_tag.attributes).not_to include('poster')
150
- end
151
- let(:options) { basic_options.merge(:poster => false) }
152
- it "should not include a poster attribute in the tag for false" do
153
- expect(test_tag.attributes).not_to include('poster')
154
- end
155
- end
156
- end
157
-
158
- context ":source_transformation" do
159
- let(:options) { basic_options.merge(:source_types => %w(mp4 webm),
160
- :source_transformation => { 'mp4' => { 'quality' => 70 },
161
- 'webm' => { 'quality' => 30 } }
162
- ) }
163
- it "should produce the specific transformation for each type" do
164
- expect(test_tag.children_by_type("video/mp4")[0][:src]).to include("q_70")
165
- expect(test_tag.children_by_type("video/webm")[0][:src]).to include("q_30")
166
- end
167
-
168
- end
169
-
170
- describe ':fallback_content' do
171
- context 'when given fallback_content parameter' do
172
- let(:fallback) { "<span id=\"spanid\">Cannot display video</span>" }
173
- let(:options) { basic_options.merge(:fallback_content => fallback) }
174
- it "should include fallback content in the tag" do
175
- expect(test_tag.children.map(&:to_html)).to include(TestTag.new(fallback).element.to_html)
176
- end
177
- end
178
-
179
- context "when given a block" do
180
- let(:test_tag) do
181
- # Actual code being tested ----------------
182
- html = helper.cl_video_tag("movie", options) do
183
- "Cannot display video!"
184
- end
185
- # -----------------------------------
186
- TestTag.new(html)
187
- end
188
- it 'should treat the block return value as fallback content' do
189
- expect(test_tag.children.map(&:to_html)).to include("Cannot display video!")
190
- end
191
- end
192
- describe "dimensions" do
193
- context "when `:crop => 'fit'`" do
194
- let(:options) { basic_options.merge(:crop => 'fit') }
195
- it "should not include a width and height attributes" do
196
- expect(test_tag.attributes.keys).not_to include("width", "height")
197
- end
198
- end
199
- context "when `:crop => 'limit'`" do
200
- let(:options) { basic_options.merge(:crop => 'limit') }
201
- it "should not include a width and height attributes" do
202
- expect(test_tag.attributes.keys).not_to include("width", "height")
203
- end
204
- end
205
- end
206
- end
207
- end
208
- describe 'cl_video_thumbnail_path' do
209
- let(:source) { "movie_id" }
210
- let(:options) { {} }
211
- let(:path) { helper.cl_video_thumbnail_path(source, options) }
212
- it "should generate a cloudinary URI to the video thumbnail" do
213
- expect(path).to eq("#{upload_path}/movie_id.jpg")
214
- end
215
- end
216
- describe 'cl_video_thumbnail_tag' do
217
- let(:source) { "movie_id" }
218
- let(:options) { {} }
219
- let(:result_tag) { TestTag.new(helper.cl_video_thumbnail_tag(source, options)) }
220
- describe ":resource_type" do
221
- context "'video' (default)" do
222
- let(:options) { { :resource_type => 'video' } }
223
- it "should have a 'video/upload' path" do
224
- expect(result_tag.name).to eq('img')
225
- expect(result_tag[:src]).to include("video/upload")
226
- end
227
- it "should generate an img tag with file extension `jpg`" do
228
- expect(result_tag[:src]).to end_with("movie_id.jpg")
229
- end
230
- end
231
- context "'image'" do
232
- let(:options) { { :resource_type => 'image' } }
233
- it "should have a 'image/upload' path" do
234
- expect(result_tag.name).to eq('img')
235
- expect(result_tag[:src]).to include("image/upload")
236
- end
237
- it "should generate an img tag with file extension `jpg`" do
238
- expect(result_tag[:src]).to end_with("movie_id.jpg")
239
- end
240
- end
241
- context "'raw'" do
242
- let(:options) { { :resource_type => 'raw' } }
243
- it "should have a 'raw/upload' path" do
244
- expect(result_tag.name).to eq('img')
245
- expect(result_tag[:src]).to include("raw/upload")
246
- end
247
- it "should generate an img tag with file extension `jpg`" do
248
- expect(result_tag[:src]).to end_with("movie_id.jpg")
249
- end
250
- end
251
- end
252
- end
253
- end
@@ -1,185 +0,0 @@
1
- require 'rspec'
2
- require 'spec_helper'
3
- require 'cloudinary'
4
- require 'action_view'
5
- require 'cloudinary/helper'
6
- require 'action_view/test_case'
7
-
8
- describe Cloudinary::Utils do
9
-
10
- before(:each) do
11
- Cloudinary.reset_config
12
- Cloudinary.config do |config|
13
- config.cloud_name = DUMMY_CLOUD
14
- config.secure_distribution = nil
15
- config.private_cdn = false
16
- config.secure = false
17
- config.cname = nil
18
- config.cdn_subdomain = false
19
- config.api_key = "1234"
20
- config.api_secret = "b"
21
- end
22
- end
23
- let(:root_path) { "http://res.cloudinary.com/#{DUMMY_CLOUD}" }
24
- let(:upload_path) { "#{root_path}/video/upload" }
25
-
26
- describe "cloudinary_url" do
27
- context ":video_codec" do
28
- it 'should support a string value' do
29
- expect(["video_id", { :resource_type => 'video', :video_codec => 'auto' }])
30
- .to produce_url("#{upload_path}/vc_auto/video_id")
31
- .and empty_options
32
- end
33
- it 'should support a hash value' do
34
- expect(["video_id", {
35
- :resource_type => 'video',
36
- :video_codec => {
37
- :codec => 'h264',
38
- :profile => 'basic',
39
- :level => '3.1'
40
- }
41
- }])
42
- .to produce_url("#{upload_path}/vc_h264:basic:3.1/video_id")
43
- .and empty_options
44
- end
45
- end
46
- context ":audio_codec" do
47
- it 'should support a string value' do
48
- expect(["video_id", { :resource_type => 'video', :audio_codec => 'acc' }])
49
- .to produce_url("#{upload_path}/ac_acc/video_id")
50
- .and empty_options
51
- end
52
- end
53
- context ":bit_rate" do
54
- it 'should support an integer value' do
55
- expect(["video_id", { :resource_type => 'video', :bit_rate => 2048 }])
56
- .to produce_url("#{upload_path}/br_2048/video_id")
57
- .and empty_options
58
- end
59
- it 'should support "<integer>k" ' do
60
- expect(["video_id", { :resource_type => 'video', :bit_rate => '44k' }])
61
- .to produce_url("#{upload_path}/br_44k/video_id")
62
- .and empty_options
63
- end
64
- it 'should support "<integer>m"' do
65
- expect(["video_id", { :resource_type => 'video', :bit_rate => '1m' }])
66
- .to produce_url("#{upload_path}/br_1m/video_id")
67
- .and empty_options
68
- end
69
- end
70
- context ":audio_frequency" do
71
- it 'should support an integer value' do
72
- expect(["video_id", { :resource_type => 'video', :audio_frequency => 44100 }])
73
- .to produce_url("#{upload_path}/af_44100/video_id")
74
- .and empty_options
75
- end
76
- end
77
- context ":video_sampling" do
78
- it "should support an integer value" do
79
- expect(["video_id", { :resource_type => 'video', :video_sampling => 20 }])
80
- .to produce_url("#{upload_path}/vs_20/video_id")
81
- .and empty_options
82
- end
83
- it "should support an string value in the a form of \"<float>s\"" do
84
- expect(["video_id", { :resource_type => 'video', :video_sampling => "2.3s" }])
85
- .to produce_url("#{upload_path}/vs_2.3s/video_id")
86
- .and empty_options
87
- end
88
- end
89
- { :so => :start_offset, :eo => :end_offset, :du => :duration }.each do |short, long|
90
- context ":#{long}" do
91
- it "should support decimal seconds " do
92
- expect(["video_id", { :resource_type => 'video', long => 2.63 }])
93
- .to produce_url("#{upload_path}/#{short}_2.63/video_id")
94
- .and empty_options
95
- expect(["video_id", { :resource_type => 'video', long => '2.63' }])
96
- .to produce_url("#{upload_path}/#{short}_2.63/video_id")
97
- .and empty_options
98
- end
99
- it 'should support percents of the video length as "<number>p"' do
100
- expect(["video_id", { :resource_type => 'video', long => '35p' }])
101
- .to produce_url("#{upload_path}/#{short}_35p/video_id")
102
- .and empty_options
103
- end
104
- it 'should support percents of the video length as "<number>%"' do
105
- expect(["video_id", { :resource_type => 'video', long => '35%' }])
106
- .to produce_url("#{upload_path}/#{short}_35p/video_id")
107
- .and empty_options
108
- end
109
- it 'should support the "auto" keyword' do
110
- expect(["video_id", { :resource_type => 'video', long => 'auto' }])
111
- .to produce_url("#{upload_path}/#{short}_auto/video_id")
112
- .and empty_options
113
- end
114
- end
115
- end
116
-
117
- describe ":offset" do
118
- let(:test_url) { Cloudinary::Utils.cloudinary_url("video_id", options) }
119
- [
120
- ['string range', 'so_2.66,eo_3.21', '2.66..3.21'],
121
- ['array', 'so_2.66,eo_3.21', [2.66, 3.21]],
122
- ['range of floats', 'so_2.66,eo_3.21', 2.66..3.21],
123
- ['array of % strings', 'so_35p,eo_70p', %w(35% 70%)],
124
- ['array of p strings', 'so_35p,eo_70p', %w(35p 70p)],
125
- ['array of float percent', 'so_35.5p,eo_70.5p', %w(35.5p 70.5p)]
126
- ].each do |test|
127
- name, url_param, range = test
128
- context "when provided with #{name} #{range}" do
129
- let(:options) { { :resource_type => 'video', :offset => range } }
130
- it "should produce a range transformation in the format of #{url_param}" do
131
- expect { test_url }.to change { options }.to({})
132
- transformation = /([^\/]*)\/video_id$/.match(test_url)[1]
133
- # we can't rely on the order of the parameters so we sort them before comparing
134
- expect(transformation.split(',').sort.reverse.join(',')).to eq(url_param)
135
- end
136
- end
137
- end
138
- end
139
- context "when given existing relevant parameters: :quality, :background, :crop, :width, :height, :gravity, :overlay" do
140
-
141
- { :overlay => :l, :underlay => :u }.each do |param, letter|
142
- it "should support #{param}" do
143
- expect(["test", { :resource_type => 'video', param => "text:hello" }])
144
- .to produce_url("#{upload_path}/#{letter}_text:hello/test")
145
- .and empty_options
146
- end
147
-
148
- it "should not pass width/height to html for #{param}" do
149
- expect(["test", { :resource_type => 'video', param => "text:hello", :height => 100, :width => 100 }])
150
- .to produce_url("#{upload_path}/h_100,#{letter}_text:hello,w_100/test")
151
- .and empty_options
152
- end
153
- end
154
- it "should produce the transformation string" do
155
- expect(["test", { :resource_type => 'video', :background => "#112233" }])
156
- .to produce_url("#{upload_path}/b_rgb:112233/test")
157
- .and empty_options
158
- expect(["test", {
159
- :resource_type => 'video',
160
- :x => 1, :y => 2, :radius => 3,
161
- :gravity => :center,
162
- :quality => 0.4,
163
- :prefix => "a" }])
164
- .to produce_url("#{upload_path}/g_center,p_a,q_0.4,r_3,x_1,y_2/test")
165
- .and empty_options
166
-
167
- end
168
- end
169
- it "should support the fps parameter" do
170
- [
171
- ['24-29.97', 'fps_24-29.97'],
172
- [24, 'fps_24'],
173
- [24.973, 'fps_24.973'],
174
- ['24', 'fps_24'],
175
- ['-24', 'fps_-24'],
176
- ['$v', 'fps_$v'],
177
- [[24, 29.97], 'fps_24-29.97'],
178
- [['24', '$v'], 'fps_24-$v']
179
- ].each do |value, param|
180
- expect(Cloudinary::Utils.generate_transformation_string(:fps => value)).to eq(param)
181
- end
182
- end
183
- end
184
-
185
- end