]> git.openstreetmap.org Git - rails.git/blob - test/functional/trace_controller_test.rb
Update to iD v1.3.6
[rails.git] / test / functional / trace_controller_test.rb
1 require File.dirname(__FILE__) + '/../test_helper'
2
3 class TraceControllerTest < ActionController::TestCase
4   fixtures :users, :gpx_files
5   set_fixture_class :gpx_files => 'Trace'
6
7   def setup
8     @gpx_trace_dir = Object.send("remove_const", "GPX_TRACE_DIR")
9     Object.const_set("GPX_TRACE_DIR", File.dirname(__FILE__) + "/../traces")
10   end
11
12   def teardown
13     Object.send("remove_const", "GPX_TRACE_DIR")
14     Object.const_set("GPX_TRACE_DIR", @gpx_trace_dir)
15   end
16
17   ##
18   # test all routes which lead to this controller
19   def test_routes
20     assert_routing(
21       { :path => "/api/0.6/gpx/create", :method => :post },
22       { :controller => "trace", :action => "api_create" }
23     )
24     assert_routing(
25       { :path => "/api/0.6/gpx/1", :method => :get },
26       { :controller => "trace", :action => "api_read", :id => "1" }
27     )
28     assert_routing(
29       { :path => "/api/0.6/gpx/1", :method => :put },
30       { :controller => "trace", :action => "api_update", :id => "1" }
31     )
32     assert_routing(
33       { :path => "/api/0.6/gpx/1", :method => :delete },
34       { :controller => "trace", :action => "api_delete", :id => "1" }
35     )
36     assert_recognizes(
37       { :controller => "trace", :action => "api_read", :id => "1" },
38       { :path => "/api/0.6/gpx/1/details", :method => :get }
39     )
40     assert_routing(
41       { :path => "/api/0.6/gpx/1/data", :method => :get },
42       { :controller => "trace", :action => "api_data", :id => "1" }
43     )
44     assert_routing(
45       { :path => "/api/0.6/gpx/1/data.xml", :method => :get },
46       { :controller => "trace", :action => "api_data", :id => "1", :format => "xml" }
47     )
48
49     assert_routing(
50       { :path => "/traces", :method => :get },
51       { :controller => "trace", :action => "list" }
52     )
53     assert_routing(
54       { :path => "/traces/page/1", :method => :get },
55       { :controller => "trace", :action => "list", :page => "1" }
56     )
57     assert_routing(
58       { :path => "/traces/tag/tagname", :method => :get },
59       { :controller => "trace", :action => "list", :tag => "tagname" }
60     )
61     assert_routing(
62       { :path => "/traces/tag/tagname/page/1", :method => :get },
63       { :controller => "trace", :action => "list", :tag => "tagname", :page => "1" }
64     )
65     assert_routing(
66       { :path => "/user/username/traces", :method => :get },
67       { :controller => "trace", :action => "list", :display_name => "username" }
68     )
69     assert_routing(
70       { :path => "/user/username/traces/page/1", :method => :get },
71       { :controller => "trace", :action => "list", :display_name => "username", :page => "1" }
72     )
73     assert_routing(
74       { :path => "/user/username/traces/tag/tagname", :method => :get },
75       { :controller => "trace", :action => "list", :display_name => "username", :tag => "tagname" }
76     )
77     assert_routing(
78       { :path => "/user/username/traces/tag/tagname/page/1", :method => :get },
79       { :controller => "trace", :action => "list", :display_name => "username", :tag => "tagname", :page => "1" }
80     )
81
82     assert_routing(
83       { :path => "/traces/mine", :method => :get },
84       { :controller => "trace", :action => "mine" }
85     )
86     assert_routing(
87       { :path => "/traces/mine/page/1", :method => :get },
88       { :controller => "trace", :action => "mine", :page => "1" }
89     )
90     assert_routing(
91       { :path => "/traces/mine/tag/tagname", :method => :get },
92       { :controller => "trace", :action => "mine", :tag => "tagname" }
93     )
94     assert_routing(
95       { :path => "/traces/mine/tag/tagname/page/1", :method => :get },
96       { :controller => "trace", :action => "mine", :tag => "tagname", :page => "1" }
97     )
98
99     assert_routing(
100       { :path => "/traces/rss", :method => :get },
101       { :controller => "trace", :action => "georss", :format => :rss }
102     )
103     assert_routing(
104       { :path => "/traces/tag/tagname/rss", :method => :get },
105       { :controller => "trace", :action => "georss", :tag => "tagname", :format => :rss }
106     )
107     assert_routing(
108       { :path => "/user/username/traces/rss", :method => :get },
109       { :controller => "trace", :action => "georss", :display_name => "username", :format => :rss }
110     )
111     assert_routing(
112       { :path => "/user/username/traces/tag/tagname/rss", :method => :get },
113       { :controller => "trace", :action => "georss", :display_name => "username", :tag => "tagname", :format => :rss }
114     )
115
116     assert_routing(
117       { :path => "/user/username/traces/1", :method => :get },
118       { :controller => "trace", :action => "view", :display_name => "username", :id => "1" }
119     )
120     assert_routing(
121       { :path => "/user/username/traces/1/picture", :method => :get },
122       { :controller => "trace", :action => "picture", :display_name => "username", :id => "1" }
123     )
124     assert_routing(
125       { :path => "/user/username/traces/1/icon", :method => :get },
126       { :controller => "trace", :action => "icon", :display_name => "username", :id => "1" }
127     )
128
129     assert_routing(
130       { :path => "/trace/create", :method => :get },
131       { :controller => "trace", :action => "create" }
132     )
133     assert_routing(
134       { :path => "/trace/create", :method => :post },
135       { :controller => "trace", :action => "create" }
136     )
137     assert_routing(
138       { :path => "/trace/1/data", :method => :get },
139       { :controller => "trace", :action => "data", :id => "1" }
140     )
141     assert_routing(
142       { :path => "/trace/1/data.xml", :method => :get },
143       { :controller => "trace", :action => "data", :id => "1", :format => "xml" }
144     )
145     assert_routing(
146       { :path => "/trace/1/edit", :method => :get },
147       { :controller => "trace", :action => "edit", :id => "1" }
148     )
149     assert_routing(
150       { :path => "/trace/1/edit", :method => :post },
151       { :controller => "trace", :action => "edit", :id => "1" }
152     )
153     assert_routing(
154       { :path => "/trace/1/edit", :method => :patch },
155       { :controller => "trace", :action => "edit", :id => "1" }
156     )
157     assert_routing(
158       { :path => "/trace/1/delete", :method => :post },
159       { :controller => "trace", :action => "delete", :id => "1" }
160     )
161   end
162
163   # Check that the list of changesets is displayed
164   def test_list
165     get :list
166     check_trace_list Trace.public
167
168     get :list, :tag => "London"
169     check_trace_list Trace.tagged("London").public
170   end
171
172   # Check that I can get mine
173   def test_list_mine
174     # First try to get it when not logged in
175     get :mine
176     assert_redirected_to :controller => 'user', :action => 'login', :referer => '/traces/mine'
177
178     # Now try when logged in
179     get :mine, {}, {:user => users(:public_user).id}
180     assert_redirected_to :controller => 'trace', :action => 'list', :display_name => users(:public_user).display_name
181
182     # Fetch the actual list
183     get :list, {:display_name => users(:public_user).display_name}, {:user => users(:public_user).id}
184     check_trace_list users(:public_user).traces
185   end
186
187   # Check the list of changesets for a specific user
188   def test_list_user
189     # Test a user with no traces
190     get :list, :display_name => users(:second_public_user).display_name
191     check_trace_list users(:second_public_user).traces.public
192
193     # Test a user with some traces - should see only public ones
194     get :list, :display_name => users(:public_user).display_name
195     check_trace_list users(:public_user).traces.public
196
197     # Should still see only public ones when authenticated as another user
198     get :list, {:display_name => users(:public_user).display_name}, {:user => users(:normal_user).id}
199     check_trace_list users(:public_user).traces.public
200
201     # Should see all traces when authenticated as the target user
202     get :list, {:display_name => users(:public_user).display_name}, {:user => users(:public_user).id}
203     check_trace_list users(:public_user).traces
204
205     # Should only see traces with the correct tag when a tag is specified
206     get :list, {:display_name => users(:public_user).display_name, :tag => "London"}, {:user => users(:public_user).id}
207     check_trace_list users(:public_user).traces.tagged("London")
208   end
209
210   # Check that the rss loads
211   def test_rss
212     get :georss, :format => :rss
213     check_trace_feed Trace.public
214
215     get :georss, :tag => "London", :format => :rss
216     check_trace_feed Trace.tagged("London").public
217
218     get :georss, :display_name => users(:public_user).display_name, :format => :rss
219     check_trace_feed users(:public_user).traces.public
220
221     get :georss, :display_name => users(:public_user).display_name, :tag => "Birmingham", :format => :rss
222     check_trace_feed users(:public_user).traces.tagged("Birmingham").public
223   end
224
225   # Test viewing a trace
226   def test_view
227     # First with no auth, which should work since the trace is public
228     get :view, {:display_name => users(:normal_user).display_name, :id => gpx_files(:public_trace_file).id}
229     check_trace_view gpx_files(:public_trace_file)
230
231     # Now with some other user, which should work since the trace is public
232     get :view, {:display_name => users(:normal_user).display_name, :id => gpx_files(:public_trace_file).id}, {:user => users(:public_user).id}
233     check_trace_view gpx_files(:public_trace_file)
234
235     # And finally we should be able to do it with the owner of the trace
236     get :view, {:display_name => users(:normal_user).display_name, :id => gpx_files(:public_trace_file).id}, {:user => users(:normal_user).id}
237     check_trace_view gpx_files(:public_trace_file)
238   end
239
240   # Check an anonymous trace can't be viewed by another user
241   def test_view_anon
242     # First with no auth
243     get :view, {:display_name => users(:public_user).display_name, :id => gpx_files(:anon_trace_file).id}
244     assert_response :redirect
245     assert_redirected_to :action => :list
246
247     # Now with some other user, which should work since the trace is anon
248     get :view, {:display_name => users(:public_user).display_name, :id => gpx_files(:anon_trace_file).id}, {:user => users(:normal_user).id}
249     assert_response :redirect
250     assert_redirected_to :action => :list
251
252     # And finally we should be able to do it with the owner of the trace
253     get :view, {:display_name => users(:public_user).display_name, :id => gpx_files(:anon_trace_file).id}, {:user => users(:public_user).id}
254     check_trace_view gpx_files(:anon_trace_file)
255   end
256
257   # Test viewing a trace that doesn't exist
258   def test_view_not_found
259     # First with no auth, which should work since the trace is public
260     get :view, {:display_name => users(:public_user).display_name, :id => 0}
261     assert_response :redirect
262     assert_redirected_to :action => :list
263
264     # Now with some other user, which should work since the trace is public
265     get :view, {:display_name => users(:public_user).display_name, :id => 0}, {:user => users(:public_user).id}
266     assert_response :redirect
267     assert_redirected_to :action => :list
268
269     # And finally we should be able to do it with the owner of the trace
270     get :view, {:display_name => users(:public_user).display_name, :id => 5}, {:user => users(:public_user).id}
271     assert_response :redirect
272     assert_redirected_to :action => :list
273   end
274
275   # Test downloading a trace
276   def test_data
277     # First with no auth, which should work since the trace is public
278     get :data, {:display_name => users(:normal_user).display_name, :id => gpx_files(:public_trace_file).id}
279     check_trace_data gpx_files(:public_trace_file)
280
281     # Now with some other user, which should work since the trace is public
282     get :data, {:display_name => users(:normal_user).display_name, :id => gpx_files(:public_trace_file).id}, {:user => users(:public_user).id}
283     check_trace_data gpx_files(:public_trace_file)
284
285     # And finally we should be able to do it with the owner of the trace
286     get :data, {:display_name => users(:normal_user).display_name, :id => gpx_files(:public_trace_file).id}, {:user => users(:normal_user).id}
287     check_trace_data gpx_files(:public_trace_file)
288   end
289
290   # Test downloading a compressed trace
291   def test_data_compressed
292     # First get the data as is
293     get :data, {:display_name => users(:public_user).display_name, :id => gpx_files(:identifiable_trace_file).id}
294     check_trace_data gpx_files(:identifiable_trace_file), "application/x-gzip", "gpx.gz"
295
296     # Now ask explicitly for XML format
297     get :data, {:display_name => users(:public_user).display_name, :id => gpx_files(:identifiable_trace_file).id, :format => "xml"}
298     check_trace_data gpx_files(:identifiable_trace_file), "application/xml", "xml"
299
300     # Now ask explicitly for GPX format
301     get :data, {:display_name => users(:public_user).display_name, :id => gpx_files(:identifiable_trace_file).id, :format => "gpx"}
302     check_trace_data gpx_files(:identifiable_trace_file)
303   end
304
305   # Check an anonymous trace can't be downloaded by another user
306   def test_data_anon
307     # First with no auth
308     get :data, {:display_name => users(:public_user).display_name, :id => gpx_files(:anon_trace_file).id}
309     assert_response :not_found
310
311     # Now with some other user, which should work since the trace is anon
312     get :data, {:display_name => users(:public_user).display_name, :id => gpx_files(:anon_trace_file).id}, {:user => users(:normal_user).id}
313     assert_response :not_found
314
315     # And finally we should be able to do it with the owner of the trace
316     get :data, {:display_name => users(:public_user).display_name, :id => gpx_files(:anon_trace_file).id}, {:user => users(:public_user).id}
317     check_trace_data gpx_files(:anon_trace_file)
318   end
319
320   # Test downloading a trace that doesn't exist
321   def test_data_not_found
322     # First with no auth, which should work since the trace is public
323     get :data, {:display_name => users(:public_user).display_name, :id => 0}
324     assert_response :not_found
325
326     # Now with some other user, which should work since the trace is public
327     get :data, {:display_name => users(:public_user).display_name, :id => 0}, {:user => users(:public_user).id}
328     assert_response :not_found
329
330     # And finally we should be able to do it with the owner of the trace
331     get :data, {:display_name => users(:public_user).display_name, :id => 5}, {:user => users(:public_user).id}
332     assert_response :not_found
333   end
334
335   # Test fetching the edit page for a trace
336   def test_edit_get
337     # First with no auth
338     get :edit, {:display_name => users(:normal_user).display_name, :id => gpx_files(:public_trace_file).id}
339     assert_response :redirect
340     assert_redirected_to :controller => :user, :action => :login, :referer => trace_edit_path(:display_name => users(:normal_user).display_name, :id => gpx_files(:public_trace_file).id)
341
342     # Now with some other user, which should fail
343     get :edit, {:display_name => users(:normal_user).display_name, :id => gpx_files(:public_trace_file).id}, {:user => users(:public_user).id}
344     assert_response :forbidden
345
346     # Now with a trace which doesn't exist
347     get :edit, {:display_name => users(:public_user).display_name, :id => 0}, {:user => users(:public_user).id}
348     assert_response :not_found
349
350     # Now with a trace which has been deleted
351     get :edit, {:display_name => users(:public_user).display_name, :id => gpx_files(:deleted_trace_file).id}, {:user => users(:public_user).id}
352     assert_response :not_found
353
354     # Finally with a trace that we are allowed to edit
355     get :edit, {:display_name => users(:normal_user).display_name, :id => gpx_files(:public_trace_file).id}, {:user => users(:normal_user).id}
356     assert_response :success
357   end
358
359   # Test saving edits to a trace
360   def test_edit_post
361     # New details
362     new_details = { :description => "Changed description", :tagstring => "new_tag", :visibility => "private" }
363
364     # First with no auth
365     post :edit, {:display_name => users(:normal_user).display_name, :id => gpx_files(:public_trace_file).id, :trace => new_details}
366     assert_response :forbidden
367
368     # Now with some other user, which should fail
369     post :edit, {:display_name => users(:normal_user).display_name, :id => gpx_files(:public_trace_file).id, :trace => new_details}, {:user => users(:public_user).id}
370     assert_response :forbidden
371
372     # Now with a trace which doesn't exist
373     post :edit, {:display_name => users(:public_user).display_name, :id => 0}, {:user => users(:public_user).id, :trace => new_details}
374     assert_response :not_found
375
376     # Now with a trace which has been deleted
377     post :edit, {:display_name => users(:public_user).display_name, :id => gpx_files(:deleted_trace_file).id, :trace => new_details}, {:user => users(:public_user).id}
378     assert_response :not_found
379
380     # Finally with a trace that we are allowed to edit
381     post :edit, {:display_name => users(:normal_user).display_name, :id => gpx_files(:public_trace_file).id, :trace => new_details}, {:user => users(:normal_user).id}
382     assert_response :redirect
383     assert_redirected_to :action => :view, :display_name => users(:normal_user).display_name
384     trace = Trace.find(gpx_files(:public_trace_file).id)
385     assert_equal new_details[:description], trace.description
386     assert_equal new_details[:tagstring], trace.tagstring
387     assert_equal new_details[:visibility], trace.visibility
388   end
389
390   # Test deleting a trace
391   def test_delete
392     # First with no auth
393     post :delete, {:display_name => users(:normal_user).display_name, :id => gpx_files(:public_trace_file).id,}
394     assert_response :forbidden
395
396     # Now with some other user, which should fail
397     post :delete, {:display_name => users(:normal_user).display_name, :id => gpx_files(:public_trace_file).id}, {:user => users(:public_user).id}
398     assert_response :forbidden
399
400     # Now with a trace which doesn't exist
401     post :delete, {:display_name => users(:public_user).display_name, :id => 0}, {:user => users(:public_user).id}
402     assert_response :not_found
403
404     # Now with a trace has already been deleted
405     post :delete, {:display_name => users(:public_user).display_name, :id => gpx_files(:deleted_trace_file).id}, {:user => users(:public_user).id}
406     assert_response :not_found
407
408     # Finally with a trace that we are allowed to delete
409     post :delete, {:display_name => users(:normal_user).display_name, :id => gpx_files(:public_trace_file).id}, {:user => users(:normal_user).id}
410     assert_response :redirect
411     assert_redirected_to :action => :list, :display_name => users(:normal_user).display_name
412     trace = Trace.find(gpx_files(:public_trace_file).id)
413     assert_equal false, trace.visible
414   end
415
416   # Check getting a specific trace through the api
417   def test_api_read
418     # First with no auth
419     get :api_read, :id => gpx_files(:public_trace_file).id
420     assert_response :unauthorized
421
422     # Now with some other user, which should work since the trace is public
423     basic_authorization(users(:public_user).display_name, "test")
424     get :api_read, :id => gpx_files(:public_trace_file).id
425     assert_response :success
426
427     # And finally we should be able to do it with the owner of the trace
428     basic_authorization(users(:normal_user).display_name, "test")
429     get :api_read, :id => gpx_files(:public_trace_file).id
430     assert_response :success
431   end
432
433   # Check an anoymous trace can't be specifically fetched by another user
434   def test_api_read_anon
435     # Furst with no auth
436     get :api_read, :id => gpx_files(:anon_trace_file).id
437     assert_response :unauthorized
438
439     # Now try with another user, which shouldn't work since the trace is anon
440     basic_authorization(users(:normal_user).display_name, "test")
441     get :api_read, :id => gpx_files(:anon_trace_file).id
442     assert_response :forbidden
443
444     # And finally we should be able to get the trace details with the trace owner
445     basic_authorization(users(:public_user).display_name, "test")
446     get :api_read, :id => gpx_files(:anon_trace_file).id
447     assert_response :success
448   end
449
450   # Check the api details for a trace that doesn't exist
451   def test_api_read_not_found
452     # Try first with no auth, as it should requure it
453     get :api_read, :id => 0
454     assert_response :unauthorized
455
456     # Login, and try again
457     basic_authorization(users(:public_user).display_name, "test")
458     get :api_read, :id => 0
459     assert_response :not_found
460
461     # Now try a trace which did exist but has been deleted
462     basic_authorization(users(:public_user).display_name, "test")
463     get :api_read, :id => 5
464     assert_response :not_found
465   end
466
467   # Check updating a trace through the api
468   def test_api_update
469     # First with no auth
470     content gpx_files(:public_trace_file).to_xml
471     put :api_update, :id => gpx_files(:public_trace_file).id
472     assert_response :unauthorized
473
474     # Now with some other user, which should fail
475     basic_authorization(users(:public_user).display_name, "test")
476     content gpx_files(:public_trace_file).to_xml
477     put :api_update, :id => gpx_files(:public_trace_file).id
478     assert_response :forbidden
479
480     # Now with a trace which doesn't exist
481     basic_authorization(users(:public_user).display_name, "test")
482     content gpx_files(:public_trace_file).to_xml
483     put :api_update, :id => 0
484     assert_response :not_found
485
486     # Now with a trace which did exist but has been deleted
487     basic_authorization(users(:public_user).display_name, "test")
488     content gpx_files(:deleted_trace_file).to_xml
489     put :api_update, :id => gpx_files(:deleted_trace_file).id
490     assert_response :not_found
491
492     # Now try an update with the wrong ID
493     basic_authorization(users(:normal_user).display_name, "test")
494     content gpx_files(:anon_trace_file).to_xml
495     put :api_update, :id => gpx_files(:public_trace_file).id
496     assert_response :bad_request, 
497        "should not be able to update a trace with a different ID from the XML"
498
499     # And finally try an update that should work
500     basic_authorization(users(:normal_user).display_name, "test")
501     t = gpx_files(:public_trace_file)
502     t.description = "Changed description"
503     t.visibility = "private"
504     content t.to_xml
505     put :api_update, :id => t.id
506     assert_response :success
507     nt = Trace.find(t.id)
508     assert_equal nt.description, t.description
509     assert_equal nt.visibility, t.visibility
510   end
511
512   # Check deleting a trace through the api
513   def test_api_delete
514     # First with no auth
515     delete :api_delete, :id => gpx_files(:public_trace_file).id
516     assert_response :unauthorized
517
518     # Now with some other user, which should fail
519     basic_authorization(users(:public_user).display_name, "test")
520     delete :api_delete, :id => gpx_files(:public_trace_file).id
521     assert_response :forbidden
522
523     # Now with a trace which doesn't exist
524     basic_authorization(users(:public_user).display_name, "test")
525     delete :api_delete, :id => 0
526     assert_response :not_found
527
528     # And finally we should be able to do it with the owner of the trace
529     basic_authorization(users(:normal_user).display_name, "test")
530     delete :api_delete, :id => gpx_files(:public_trace_file).id
531     assert_response :success
532
533     # Try it a second time, which should fail
534     basic_authorization(users(:normal_user).display_name, "test")
535     delete :api_delete, :id => gpx_files(:public_trace_file).id
536     assert_response :not_found
537   end
538
539 private
540
541   def check_trace_feed(traces)
542     assert_response :success
543     assert_template "georss"
544     assert_equal "application/rss+xml", @response.content_type
545     assert_select "rss", :count => 1 do
546       assert_select "channel", :count => 1 do
547         assert_select "title"
548         assert_select "description"
549         assert_select "link"
550         assert_select "image"
551         assert_select "item", :count => traces.visible.count do |items|
552           traces.visible.order("timestamp DESC").zip(items).each do |trace,item|
553             assert_select item, "title", trace.name
554             assert_select item, "link", "http://test.host/user/#{trace.user.display_name}/traces/#{trace.id}"
555             assert_select item, "guid", "http://test.host/user/#{trace.user.display_name}/traces/#{trace.id}"
556             assert_select item, "description"
557 #            assert_select item, "dc:creator", trace.user.display_name
558             assert_select item, "pubDate", trace.timestamp.rfc822
559           end
560         end
561       end
562     end
563   end
564
565   def check_trace_list(traces)
566     assert_response :success
567     assert_template "list"
568
569     if traces.count > 0
570       assert_select "table#trace_list tbody", :count => 1 do
571         assert_select "tr", :count => traces.visible.count do |rows|
572           traces.visible.order("timestamp DESC").zip(rows).each do |trace,row|
573             assert_select row, "span.trace_summary", Regexp.new(Regexp.escape("(#{trace.size} points)"))
574             assert_select row, "td", Regexp.new(Regexp.escape(trace.description))
575             assert_select row, "td", Regexp.new(Regexp.escape("by #{trace.user.display_name}"))
576           end
577         end
578       end
579     else
580       assert_select "h4", /Nothing here yet/
581     end
582   end
583
584   def check_trace_view(trace)
585     assert_response :success
586     assert_template "view"
587
588     assert_select "table", :count => 1 do
589       assert_select "td", /^#{Regexp.quote(trace.name)} /
590       assert_select "td", trace.user.display_name
591       assert_select "td", trace.description
592     end
593   end
594
595   def check_trace_data(trace, content_type = "application/gpx+xml", extension = "gpx")
596     assert_response :success
597     assert_equal content_type, @response.content_type
598     assert_equal "attachment; filename=\"#{trace.id}.#{extension}\"", @response.header["Content-Disposition"]
599   end
600 end