]> git.openstreetmap.org Git - rails.git/commitdiff
Move the notes api methods into a controller in the api namespace
authorAndy Allan <git@gravitystorm.co.uk>
Sun, 24 Feb 2019 14:46:25 +0000 (15:46 +0100)
committerAndy Allan <git@gravitystorm.co.uk>
Thu, 28 Feb 2019 16:12:28 +0000 (17:12 +0100)
23 files changed:
app/controllers/api/notes_controller.rb [new file with mode: 0644]
app/controllers/notes_controller.rb
app/helpers/note_helper.rb
app/views/api/notes/_comment.html.erb [moved from app/views/notes/_comment.html.erb with 100% similarity]
app/views/api/notes/_description.html.erb [moved from app/views/notes/_description.html.erb with 100% similarity]
app/views/api/notes/_entry.html.erb [moved from app/views/notes/_entry.html.erb with 100% similarity]
app/views/api/notes/_note.gpx.builder [moved from app/views/notes/_note.gpx.builder with 100% similarity]
app/views/api/notes/_note.json.jsonify [moved from app/views/notes/_note.json.jsonify with 100% similarity]
app/views/api/notes/_note.rss.builder [moved from app/views/notes/_note.rss.builder with 73% similarity]
app/views/api/notes/_note.xml.builder [moved from app/views/notes/_note.xml.builder with 100% similarity]
app/views/api/notes/feed.rss.builder [moved from app/views/notes/feed.rss.builder with 56% similarity]
app/views/api/notes/index.gpx.builder [moved from app/views/notes/index.gpx.builder with 100% similarity]
app/views/api/notes/index.json.jsonify [moved from app/views/notes/index.json.jsonify with 100% similarity]
app/views/api/notes/index.rss.builder [moved from app/views/notes/index.rss.builder with 54% similarity]
app/views/api/notes/index.xml.builder [moved from app/views/notes/index.xml.builder with 100% similarity]
app/views/api/notes/show.gpx.builder [moved from app/views/notes/show.gpx.builder with 100% similarity]
app/views/api/notes/show.json.jsonify [moved from app/views/notes/show.json.jsonify with 100% similarity]
app/views/api/notes/show.rss.builder [moved from app/views/notes/show.rss.builder with 56% similarity]
app/views/api/notes/show.xml.builder [moved from app/views/notes/show.xml.builder with 100% similarity]
config/locales/en.yml
config/routes.rb
test/controllers/api/notes_controller_test.rb [new file with mode: 0644]
test/controllers/notes_controller_test.rb

diff --git a/app/controllers/api/notes_controller.rb b/app/controllers/api/notes_controller.rb
new file mode 100644 (file)
index 0000000..d4ebef5
--- /dev/null
@@ -0,0 +1,373 @@
+module Api
+  class NotesController < ApplicationController
+    layout "site", :only => [:mine]
+
+    skip_before_action :verify_authenticity_token
+    before_action :check_api_readable
+    before_action :setup_user_auth, :only => [:create, :comment, :show]
+    before_action :authorize, :only => [:close, :reopen, :destroy]
+    before_action :api_deny_access_handler
+
+    authorize_resource
+
+    before_action :check_api_writable, :only => [:create, :comment, :close, :reopen, :destroy]
+    before_action :set_locale
+    around_action :api_call_handle_error, :api_call_timeout
+
+    ##
+    # Return a list of notes in a given area
+    def index
+      # Figure out the bbox - we prefer a bbox argument but also
+      # support the old, deprecated, method with four arguments
+      if params[:bbox]
+        bbox = BoundingBox.from_bbox_params(params)
+      else
+        raise OSM::APIBadUserInput, "No l was given" unless params[:l]
+        raise OSM::APIBadUserInput, "No r was given" unless params[:r]
+        raise OSM::APIBadUserInput, "No b was given" unless params[:b]
+        raise OSM::APIBadUserInput, "No t was given" unless params[:t]
+
+        bbox = BoundingBox.from_lrbt_params(params)
+      end
+
+      # Get any conditions that need to be applied
+      notes = closed_condition(Note.all)
+
+      # Check that the boundaries are valid
+      bbox.check_boundaries
+
+      # Check the the bounding box is not too big
+      bbox.check_size(MAX_NOTE_REQUEST_AREA)
+
+      # Find the notes we want to return
+      @notes = notes.bbox(bbox).order("updated_at DESC").limit(result_limit).preload(:comments)
+
+      # Render the result
+      respond_to do |format|
+        format.rss
+        format.xml
+        format.json
+        format.gpx
+      end
+    end
+
+    ##
+    # Create a new note
+    def create
+      # Check the ACLs
+      raise OSM::APIAccessDenied if current_user.nil? && Acl.no_note_comment(request.remote_ip)
+
+      # Check the arguments are sane
+      raise OSM::APIBadUserInput, "No lat was given" unless params[:lat]
+      raise OSM::APIBadUserInput, "No lon was given" unless params[:lon]
+      raise OSM::APIBadUserInput, "No text was given" if params[:text].blank?
+
+      # Extract the arguments
+      lon = OSM.parse_float(params[:lon], OSM::APIBadUserInput, "lon was not a number")
+      lat = OSM.parse_float(params[:lat], OSM::APIBadUserInput, "lat was not a number")
+      comment = params[:text]
+
+      # Include in a transaction to ensure that there is always a note_comment for every note
+      Note.transaction do
+        # Create the note
+        @note = Note.create(:lat => lat, :lon => lon)
+        raise OSM::APIBadUserInput, "The note is outside this world" unless @note.in_world?
+
+        # Save the note
+        @note.save!
+
+        # Add a comment to the note
+        add_comment(@note, comment, "opened")
+      end
+
+      # Return a copy of the new note
+      respond_to do |format|
+        format.xml { render :action => :show }
+        format.json { render :action => :show }
+      end
+    end
+
+    ##
+    # Add a comment to an existing note
+    def comment
+      # Check the ACLs
+      raise OSM::APIAccessDenied if current_user.nil? && Acl.no_note_comment(request.remote_ip)
+
+      # Check the arguments are sane
+      raise OSM::APIBadUserInput, "No id was given" unless params[:id]
+      raise OSM::APIBadUserInput, "No text was given" if params[:text].blank?
+
+      # Extract the arguments
+      id = params[:id].to_i
+      comment = params[:text]
+
+      # Find the note and check it is valid
+      @note = Note.find(id)
+      raise OSM::APINotFoundError unless @note
+      raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
+      raise OSM::APINoteAlreadyClosedError, @note if @note.closed?
+
+      # Add a comment to the note
+      Note.transaction do
+        add_comment(@note, comment, "commented")
+      end
+
+      # Return a copy of the updated note
+      respond_to do |format|
+        format.xml { render :action => :show }
+        format.json { render :action => :show }
+      end
+    end
+
+    ##
+    # Close a note
+    def close
+      # Check the arguments are sane
+      raise OSM::APIBadUserInput, "No id was given" unless params[:id]
+
+      # Extract the arguments
+      id = params[:id].to_i
+      comment = params[:text]
+
+      # Find the note and check it is valid
+      @note = Note.find_by(:id => id)
+      raise OSM::APINotFoundError unless @note
+      raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
+      raise OSM::APINoteAlreadyClosedError, @note if @note.closed?
+
+      # Close the note and add a comment
+      Note.transaction do
+        @note.close
+
+        add_comment(@note, comment, "closed")
+      end
+
+      # Return a copy of the updated note
+      respond_to do |format|
+        format.xml { render :action => :show }
+        format.json { render :action => :show }
+      end
+    end
+
+    ##
+    # Reopen a note
+    def reopen
+      # Check the arguments are sane
+      raise OSM::APIBadUserInput, "No id was given" unless params[:id]
+
+      # Extract the arguments
+      id = params[:id].to_i
+      comment = params[:text]
+
+      # Find the note and check it is valid
+      @note = Note.find_by(:id => id)
+      raise OSM::APINotFoundError unless @note
+      raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible? || current_user.moderator?
+      raise OSM::APINoteAlreadyOpenError, @note unless @note.closed? || !@note.visible?
+
+      # Reopen the note and add a comment
+      Note.transaction do
+        @note.reopen
+
+        add_comment(@note, comment, "reopened")
+      end
+
+      # Return a copy of the updated note
+      respond_to do |format|
+        format.xml { render :action => :show }
+        format.json { render :action => :show }
+      end
+    end
+
+    ##
+    # Get a feed of recent notes and comments
+    def feed
+      # Get any conditions that need to be applied
+      notes = closed_condition(Note.all)
+
+      # Process any bbox
+      if params[:bbox]
+        bbox = BoundingBox.from_bbox_params(params)
+
+        bbox.check_boundaries
+        bbox.check_size(MAX_NOTE_REQUEST_AREA)
+
+        notes = notes.bbox(bbox)
+      end
+
+      # Find the comments we want to return
+      @comments = NoteComment.where(:note_id => notes).order("created_at DESC").limit(result_limit).preload(:note)
+
+      # Render the result
+      respond_to do |format|
+        format.rss
+      end
+    end
+
+    ##
+    # Read a note
+    def show
+      # Check the arguments are sane
+      raise OSM::APIBadUserInput, "No id was given" unless params[:id]
+
+      # Find the note and check it is valid
+      @note = Note.find(params[:id])
+      raise OSM::APINotFoundError unless @note
+      raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible? || current_user&.moderator?
+
+      # Render the result
+      respond_to do |format|
+        format.xml
+        format.rss
+        format.json
+        format.gpx
+      end
+    end
+
+    ##
+    # Delete (hide) a note
+    def destroy
+      # Check the arguments are sane
+      raise OSM::APIBadUserInput, "No id was given" unless params[:id]
+
+      # Extract the arguments
+      id = params[:id].to_i
+      comment = params[:text]
+
+      # Find the note and check it is valid
+      @note = Note.find(id)
+      raise OSM::APINotFoundError unless @note
+      raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
+
+      # Mark the note as hidden
+      Note.transaction do
+        @note.status = "hidden"
+        @note.save
+
+        add_comment(@note, comment, "hidden", false)
+      end
+
+      # Return a copy of the updated note
+      respond_to do |format|
+        format.xml { render :action => :show }
+        format.json { render :action => :show }
+      end
+    end
+
+    ##
+    # Return a list of notes matching a given string
+    def search
+      # Get the initial set of notes
+      @notes = closed_condition(Note.all)
+
+      # Add any user filter
+      if params[:display_name] || params[:user]
+        if params[:display_name]
+          @user = User.find_by(:display_name => params[:display_name])
+
+          raise OSM::APIBadUserInput, "User #{params[:display_name]} not known" unless @user
+        else
+          @user = User.find_by(:id => params[:user])
+
+          raise OSM::APIBadUserInput, "User #{params[:user]} not known" unless @user
+        end
+
+        @notes = @notes.joins(:comments).where(:note_comments => { :author_id => @user })
+      end
+
+      # Add any text filter
+      @notes = @notes.joins(:comments).where("to_tsvector('english', note_comments.body) @@ plainto_tsquery('english', ?)", params[:q]) if params[:q]
+
+      # Add any date filter
+      if params[:from]
+        begin
+          from = Time.parse(params[:from])
+        rescue ArgumentError
+          raise OSM::APIBadUserInput, "Date #{params[:from]} is in a wrong format"
+        end
+
+        begin
+          to = if params[:to]
+                 Time.parse(params[:to])
+               else
+                 Time.now
+               end
+        rescue ArgumentError
+          raise OSM::APIBadUserInput, "Date #{params[:to]} is in a wrong format"
+        end
+
+        @notes = @notes.where(:created_at => from..to)
+      end
+
+      # Find the notes we want to return
+      @notes = @notes.order("updated_at DESC").limit(result_limit).preload(:comments)
+
+      # Render the result
+      respond_to do |format|
+        format.rss { render :action => :index }
+        format.xml { render :action => :index }
+        format.json { render :action => :index }
+        format.gpx { render :action => :index }
+      end
+    end
+
+    private
+
+    #------------------------------------------------------------
+    # utility functions below.
+    #------------------------------------------------------------
+
+    ##
+    # Get the maximum number of results to return
+    def result_limit
+      if params[:limit]
+        if params[:limit].to_i.positive? && params[:limit].to_i <= 10000
+          params[:limit].to_i
+        else
+          raise OSM::APIBadUserInput, "Note limit must be between 1 and 10000"
+        end
+      else
+        100
+      end
+    end
+
+    ##
+    # Generate a condition to choose which notes we want based
+    # on their status and the user's request parameters
+    def closed_condition(notes)
+      closed_since = if params[:closed]
+                       params[:closed].to_i
+                     else
+                       7
+                     end
+
+      if closed_since.negative?
+        notes.where.not(:status => "hidden")
+      elsif closed_since.positive?
+        notes.where(:status => "open")
+             .or(notes.where(:status => "closed")
+                      .where(notes.arel_table[:closed_at].gt(Time.now - closed_since.days)))
+      else
+        notes.where(:status => "open")
+      end
+    end
+
+    ##
+    # Add a comment to a note
+    def add_comment(note, text, event, notify = true)
+      attributes = { :visible => true, :event => event, :body => text }
+
+      if current_user
+        attributes[:author_id] = current_user.id
+      else
+        attributes[:author_ip] = request.remote_ip
+      end
+
+      comment = note.comments.create!(attributes)
+
+      note.comments.map(&:author).uniq.each do |user|
+        Notifier.note_comment_notification(comment, user).deliver_later if notify && user && user != current_user && user.visible?
+      end
+    end
+  end
+end
index 036238db1abae3c5dcc9e42dd113208e0c4a7470..54fdb28100a37a69d1896887bad5c0844d57cc67 100644 (file)
 class NotesController < ApplicationController
   layout "site", :only => [:mine]
 
 class NotesController < ApplicationController
   layout "site", :only => [:mine]
 
-  skip_before_action :verify_authenticity_token, :except => [:mine]
   before_action :check_api_readable
   before_action :check_api_readable
-  before_action :authorize_web, :only => [:mine]
-  before_action :setup_user_auth, :only => [:create, :comment, :show]
-  before_action :authorize, :only => [:close, :reopen, :destroy]
-  before_action :api_deny_access_handler, :except => [:mine]
+  before_action :authorize_web
 
   authorize_resource
 
 
   authorize_resource
 
-  before_action :check_api_writable, :only => [:create, :comment, :close, :reopen, :destroy]
   before_action :set_locale
   around_action :api_call_handle_error, :api_call_timeout
 
   before_action :set_locale
   around_action :api_call_handle_error, :api_call_timeout
 
-  ##
-  # Return a list of notes in a given area
-  def index
-    # Figure out the bbox - we prefer a bbox argument but also
-    # support the old, deprecated, method with four arguments
-    if params[:bbox]
-      bbox = BoundingBox.from_bbox_params(params)
-    else
-      raise OSM::APIBadUserInput, "No l was given" unless params[:l]
-      raise OSM::APIBadUserInput, "No r was given" unless params[:r]
-      raise OSM::APIBadUserInput, "No b was given" unless params[:b]
-      raise OSM::APIBadUserInput, "No t was given" unless params[:t]
-
-      bbox = BoundingBox.from_lrbt_params(params)
-    end
-
-    # Get any conditions that need to be applied
-    notes = closed_condition(Note.all)
-
-    # Check that the boundaries are valid
-    bbox.check_boundaries
-
-    # Check the the bounding box is not too big
-    bbox.check_size(MAX_NOTE_REQUEST_AREA)
-
-    # Find the notes we want to return
-    @notes = notes.bbox(bbox).order("updated_at DESC").limit(result_limit).preload(:comments)
-
-    # Render the result
-    respond_to do |format|
-      format.rss
-      format.xml
-      format.json
-      format.gpx
-    end
-  end
-
-  ##
-  # Create a new note
-  def create
-    # Check the ACLs
-    raise OSM::APIAccessDenied if current_user.nil? && Acl.no_note_comment(request.remote_ip)
-
-    # Check the arguments are sane
-    raise OSM::APIBadUserInput, "No lat was given" unless params[:lat]
-    raise OSM::APIBadUserInput, "No lon was given" unless params[:lon]
-    raise OSM::APIBadUserInput, "No text was given" if params[:text].blank?
-
-    # Extract the arguments
-    lon = OSM.parse_float(params[:lon], OSM::APIBadUserInput, "lon was not a number")
-    lat = OSM.parse_float(params[:lat], OSM::APIBadUserInput, "lat was not a number")
-    comment = params[:text]
-
-    # Include in a transaction to ensure that there is always a note_comment for every note
-    Note.transaction do
-      # Create the note
-      @note = Note.create(:lat => lat, :lon => lon)
-      raise OSM::APIBadUserInput, "The note is outside this world" unless @note.in_world?
-
-      # Save the note
-      @note.save!
-
-      # Add a comment to the note
-      add_comment(@note, comment, "opened")
-    end
-
-    # Return a copy of the new note
-    respond_to do |format|
-      format.xml { render :action => :show }
-      format.json { render :action => :show }
-    end
-  end
-
-  ##
-  # Add a comment to an existing note
-  def comment
-    # Check the ACLs
-    raise OSM::APIAccessDenied if current_user.nil? && Acl.no_note_comment(request.remote_ip)
-
-    # Check the arguments are sane
-    raise OSM::APIBadUserInput, "No id was given" unless params[:id]
-    raise OSM::APIBadUserInput, "No text was given" if params[:text].blank?
-
-    # Extract the arguments
-    id = params[:id].to_i
-    comment = params[:text]
-
-    # Find the note and check it is valid
-    @note = Note.find(id)
-    raise OSM::APINotFoundError unless @note
-    raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
-    raise OSM::APINoteAlreadyClosedError, @note if @note.closed?
-
-    # Add a comment to the note
-    Note.transaction do
-      add_comment(@note, comment, "commented")
-    end
-
-    # Return a copy of the updated note
-    respond_to do |format|
-      format.xml { render :action => :show }
-      format.json { render :action => :show }
-    end
-  end
-
-  ##
-  # Close a note
-  def close
-    # Check the arguments are sane
-    raise OSM::APIBadUserInput, "No id was given" unless params[:id]
-
-    # Extract the arguments
-    id = params[:id].to_i
-    comment = params[:text]
-
-    # Find the note and check it is valid
-    @note = Note.find_by(:id => id)
-    raise OSM::APINotFoundError unless @note
-    raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
-    raise OSM::APINoteAlreadyClosedError, @note if @note.closed?
-
-    # Close the note and add a comment
-    Note.transaction do
-      @note.close
-
-      add_comment(@note, comment, "closed")
-    end
-
-    # Return a copy of the updated note
-    respond_to do |format|
-      format.xml { render :action => :show }
-      format.json { render :action => :show }
-    end
-  end
-
-  ##
-  # Reopen a note
-  def reopen
-    # Check the arguments are sane
-    raise OSM::APIBadUserInput, "No id was given" unless params[:id]
-
-    # Extract the arguments
-    id = params[:id].to_i
-    comment = params[:text]
-
-    # Find the note and check it is valid
-    @note = Note.find_by(:id => id)
-    raise OSM::APINotFoundError unless @note
-    raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible? || current_user.moderator?
-    raise OSM::APINoteAlreadyOpenError, @note unless @note.closed? || !@note.visible?
-
-    # Reopen the note and add a comment
-    Note.transaction do
-      @note.reopen
-
-      add_comment(@note, comment, "reopened")
-    end
-
-    # Return a copy of the updated note
-    respond_to do |format|
-      format.xml { render :action => :show }
-      format.json { render :action => :show }
-    end
-  end
-
-  ##
-  # Get a feed of recent notes and comments
-  def feed
-    # Get any conditions that need to be applied
-    notes = closed_condition(Note.all)
-
-    # Process any bbox
-    if params[:bbox]
-      bbox = BoundingBox.from_bbox_params(params)
-
-      bbox.check_boundaries
-      bbox.check_size(MAX_NOTE_REQUEST_AREA)
-
-      notes = notes.bbox(bbox)
-    end
-
-    # Find the comments we want to return
-    @comments = NoteComment.where(:note_id => notes).order("created_at DESC").limit(result_limit).preload(:note)
-
-    # Render the result
-    respond_to do |format|
-      format.rss
-    end
-  end
-
-  ##
-  # Read a note
-  def show
-    # Check the arguments are sane
-    raise OSM::APIBadUserInput, "No id was given" unless params[:id]
-
-    # Find the note and check it is valid
-    @note = Note.find(params[:id])
-    raise OSM::APINotFoundError unless @note
-    raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible? || current_user&.moderator?
-
-    # Render the result
-    respond_to do |format|
-      format.xml
-      format.rss
-      format.json
-      format.gpx
-    end
-  end
-
-  ##
-  # Delete (hide) a note
-  def destroy
-    # Check the arguments are sane
-    raise OSM::APIBadUserInput, "No id was given" unless params[:id]
-
-    # Extract the arguments
-    id = params[:id].to_i
-    comment = params[:text]
-
-    # Find the note and check it is valid
-    @note = Note.find(id)
-    raise OSM::APINotFoundError unless @note
-    raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
-
-    # Mark the note as hidden
-    Note.transaction do
-      @note.status = "hidden"
-      @note.save
-
-      add_comment(@note, comment, "hidden", false)
-    end
-
-    # Return a copy of the updated note
-    respond_to do |format|
-      format.xml { render :action => :show }
-      format.json { render :action => :show }
-    end
-  end
-
-  ##
-  # Return a list of notes matching a given string
-  def search
-    # Get the initial set of notes
-    @notes = closed_condition(Note.all)
-
-    # Add any user filter
-    if params[:display_name] || params[:user]
-      if params[:display_name]
-        @user = User.find_by(:display_name => params[:display_name])
-
-        raise OSM::APIBadUserInput, "User #{params[:display_name]} not known" unless @user
-      else
-        @user = User.find_by(:id => params[:user])
-
-        raise OSM::APIBadUserInput, "User #{params[:user]} not known" unless @user
-      end
-
-      @notes = @notes.joins(:comments).where(:note_comments => { :author_id => @user })
-    end
-
-    # Add any text filter
-    @notes = @notes.joins(:comments).where("to_tsvector('english', note_comments.body) @@ plainto_tsquery('english', ?)", params[:q]) if params[:q]
-
-    # Add any date filter
-    if params[:from]
-      begin
-        from = Time.parse(params[:from])
-      rescue ArgumentError
-        raise OSM::APIBadUserInput, "Date #{params[:from]} is in a wrong format"
-      end
-
-      begin
-        to = if params[:to]
-               Time.parse(params[:to])
-             else
-               Time.now
-             end
-      rescue ArgumentError
-        raise OSM::APIBadUserInput, "Date #{params[:to]} is in a wrong format"
-      end
-
-      @notes = @notes.where(:created_at => from..to)
-    end
-
-    # Find the notes we want to return
-    @notes = @notes.order("updated_at DESC").limit(result_limit).preload(:comments)
-
-    # Render the result
-    respond_to do |format|
-      format.rss { render :action => :index }
-      format.xml { render :action => :index }
-      format.json { render :action => :index }
-      format.gpx { render :action => :index }
-    end
-  end
-
   ##
   # Display a list of notes by a specified user
   def mine
   ##
   # Display a list of notes by a specified user
   def mine
@@ -333,63 +31,4 @@ class NotesController < ApplicationController
       end
     end
   end
       end
     end
   end
-
-  private
-
-  #------------------------------------------------------------
-  # utility functions below.
-  #------------------------------------------------------------
-
-  ##
-  # Get the maximum number of results to return
-  def result_limit
-    if params[:limit]
-      if params[:limit].to_i.positive? && params[:limit].to_i <= 10000
-        params[:limit].to_i
-      else
-        raise OSM::APIBadUserInput, "Note limit must be between 1 and 10000"
-      end
-    else
-      100
-    end
-  end
-
-  ##
-  # Generate a condition to choose which notes we want based
-  # on their status and the user's request parameters
-  def closed_condition(notes)
-    closed_since = if params[:closed]
-                     params[:closed].to_i
-                   else
-                     7
-                   end
-
-    if closed_since.negative?
-      notes.where.not(:status => "hidden")
-    elsif closed_since.positive?
-      notes.where(:status => "open")
-           .or(notes.where(:status => "closed")
-                    .where(notes.arel_table[:closed_at].gt(Time.now - closed_since.days)))
-    else
-      notes.where(:status => "open")
-    end
-  end
-
-  ##
-  # Add a comment to a note
-  def add_comment(note, text, event, notify = true)
-    attributes = { :visible => true, :event => event, :body => text }
-
-    if current_user
-      attributes[:author_id] = current_user.id
-    else
-      attributes[:author_ip] = request.remote_ip
-    end
-
-    comment = note.comments.create!(attributes)
-
-    note.comments.map(&:author).uniq.each do |user|
-      Notifier.note_comment_notification(comment, user).deliver_later if notify && user && user != current_user && user.visible?
-    end
-  end
 end
 end
index 89c8cc376a24b7eadaa2b042be51e845306a9b62..4b5be42d78b8786988be869e4dc5d2b5ca3c1901 100644 (file)
@@ -18,7 +18,7 @@ module NoteHelper
     elsif author.status == "deleted"
       t("users.no_such_user.deleted")
     else
     elsif author.status == "deleted"
       t("users.no_such_user.deleted")
     else
-      link_to h(author.display_name), link_options.merge(:controller => "users", :action => "show", :display_name => author.display_name)
+      link_to h(author.display_name), link_options.merge(:controller => "/users", :action => "show", :display_name => author.display_name)
     end
   end
 end
     end
   end
 end
similarity index 73%
rename from app/views/notes/_note.rss.builder
rename to app/views/api/notes/_note.rss.builder
index 9c72b8a5d42c15cd2e30ca248f659852be0c922b..4dc47d158d5d12b41deae7e66e4bd5df095c5bc5 100644 (file)
@@ -2,11 +2,11 @@ xml.item do
   location = describe_location(note.lat, note.lon, 14, locale)
 
   if note.closed?
   location = describe_location(note.lat, note.lon, 14, locale)
 
   if note.closed?
-    xml.title t("notes.rss.closed", :place => location)
+    xml.title t("api.notes.rss.closed", :place => location)
   elsif note.comments.length > 1
   elsif note.comments.length > 1
-    xml.title t("notes.rss.commented", :place => location)
+    xml.title t("api.notes.rss.commented", :place => location)
   else
   else
-    xml.title t("notes.rss.opened", :place => location)
+    xml.title t("api.notes.rss.opened", :place => location)
   end
 
   xml.link browse_note_url(note)
   end
 
   xml.link browse_note_url(note)
similarity index 56%
rename from app/views/notes/feed.rss.builder
rename to app/views/api/notes/feed.rss.builder
index 4a4fb3e1a75c05bb6b1ec9340163f6cdf5a6694a..1b9e5b584ed90105b2ab4a19f090151dbeb9cba1 100644 (file)
@@ -5,18 +5,18 @@ xml.rss("version" => "2.0",
         "xmlns:geo" => "http://www.w3.org/2003/01/geo/wgs84_pos#",
         "xmlns:georss" => "http://www.georss.org/georss") do
   xml.channel do
         "xmlns:geo" => "http://www.w3.org/2003/01/geo/wgs84_pos#",
         "xmlns:georss" => "http://www.georss.org/georss") do
   xml.channel do
-    xml.title t("notes.rss.title")
-    xml.description t("notes.rss.description_area", :min_lat => @min_lat, :min_lon => @min_lon, :max_lat => @max_lat, :max_lon => @max_lon)
-    xml.link url_for(:controller => "site", :action => "index", :only_path => false)
+    xml.title t("api.notes.rss.title")
+    xml.description t("api.notes.rss.description_area", :min_lat => @min_lat, :min_lon => @min_lon, :max_lat => @max_lat, :max_lon => @max_lon)
+    xml.link url_for(:controller => "/site", :action => "index", :only_path => false)
 
     @comments.each do |comment|
       location = describe_location(comment.note.lat, comment.note.lon, 14, locale)
 
       xml.item do
 
     @comments.each do |comment|
       location = describe_location(comment.note.lat, comment.note.lon, 14, locale)
 
       xml.item do
-        xml.title t("notes.rss.#{comment.event}", :place => location)
+        xml.title t("api.notes.rss.#{comment.event}", :place => location)
 
 
-        xml.link url_for(:controller => "browse", :action => "note", :id => comment.note.id, :anchor => "c#{comment.id}", :only_path => false)
-        xml.guid url_for(:controller => "browse", :action => "note", :id => comment.note.id, :anchor => "c#{comment.id}", :only_path => false)
+        xml.link url_for(:controller => "/browse", :action => "note", :id => comment.note.id, :anchor => "c#{comment.id}", :only_path => false)
+        xml.guid url_for(:controller => "/browse", :action => "note", :id => comment.note.id, :anchor => "c#{comment.id}", :only_path => false)
 
         xml.description do
           xml.cdata! render(:partial => "entry", :object => comment, :formats => [:html])
 
         xml.description do
           xml.cdata! render(:partial => "entry", :object => comment, :formats => [:html])
similarity index 54%
rename from app/views/notes/index.rss.builder
rename to app/views/api/notes/index.rss.builder
index d28efa1942da01a918fb9f38cea6b508b3f2eae5..96ba4b73412cb5ddc3bd496ce01b442607150d0c 100644 (file)
@@ -5,9 +5,9 @@ xml.rss("version" => "2.0",
         "xmlns:geo" => "http://www.w3.org/2003/01/geo/wgs84_pos#",
         "xmlns:georss" => "http://www.georss.org/georss") do
   xml.channel do
         "xmlns:geo" => "http://www.w3.org/2003/01/geo/wgs84_pos#",
         "xmlns:georss" => "http://www.georss.org/georss") do
   xml.channel do
-    xml.title t("notes.rss.title")
-    xml.description t("notes.rss.description_area", :min_lat => @min_lat, :min_lon => @min_lon, :max_lat => @max_lat, :max_lon => @max_lon)
-    xml.link url_for(:controller => "site", :action => "index", :only_path => false)
+    xml.title t("api.notes.rss.title")
+    xml.description t("api.notes.rss.description_area", :min_lat => @min_lat, :min_lon => @min_lon, :max_lat => @max_lat, :max_lon => @max_lon)
+    xml.link url_for(:controller => "/site", :action => "index", :only_path => false)
 
     xml << (render(:partial => "note", :collection => @notes) || "")
   end
 
     xml << (render(:partial => "note", :collection => @notes) || "")
   end
similarity index 56%
rename from app/views/notes/show.rss.builder
rename to app/views/api/notes/show.rss.builder
index f406479008471afa2eaeaba5ad3a516afb84e1a0..61ee0857a456562052e1186f75d53ed10e81f8df 100644 (file)
@@ -4,9 +4,9 @@ xml.rss("version" => "2.0",
         "xmlns:geo" => "http://www.w3.org/2003/01/geo/wgs84_pos#",
         "xmlns:georss" => "http://www.georss.org/georss") do
   xml.channel do
         "xmlns:geo" => "http://www.w3.org/2003/01/geo/wgs84_pos#",
         "xmlns:georss" => "http://www.georss.org/georss") do
   xml.channel do
-    xml.title t("notes.rss.title")
-    xml.description t("notes.rss.description_item", :id => @note.id)
-    xml.link url_for(:controller => "site", :action => "index", :only_path => false)
+    xml.title t("api.notes.rss.title")
+    xml.description t("api.notes.rss.description_item", :id => @note.id)
+    xml.link url_for(:controller => "/site", :action => "index", :only_path => false)
 
     xml << render(:partial => "note", :object => @note)
   end
 
     xml << render(:partial => "note", :object => @note)
   end
index 577efd1743eaea53b8e412a6aeb17c6c3dcc9dc2..1d51c2d15745540f540801d58326d3d7ab034050 100644 (file)
@@ -98,6 +98,28 @@ en:
     remote:
       name: "Remote Control"
       description: "Remote Control (JOSM or Merkaartor)"
     remote:
       name: "Remote Control"
       description: "Remote Control (JOSM or Merkaartor)"
+  api:
+    notes:
+      comment:
+        opened_at_html: "Created %{when} ago"
+        opened_at_by_html: "Created %{when} ago by %{user}"
+        commented_at_html: "Updated %{when} ago"
+        commented_at_by_html: "Updated %{when} ago by %{user}"
+        closed_at_html: "Resolved %{when} ago"
+        closed_at_by_html: "Resolved %{when} ago by %{user}"
+        reopened_at_html: "Reactivated %{when} ago"
+        reopened_at_by_html: "Reactivated %{when} ago by %{user}"
+      rss:
+        title: "OpenStreetMap Notes"
+        description_area: "A list of notes, reported, commented on or closed in your area [(%{min_lat}|%{min_lon}) -- (%{max_lat}|%{max_lon})]"
+        description_item: "An rss feed for note %{id}"
+        opened: "new note (near %{place})"
+        commented: "new comment (near %{place})"
+        closed: "closed note (near %{place})"
+        reopened: "reactivated note (near %{place})"
+      entry:
+        comment: Comment
+        full: Full note
   browse:
     created: "Created"
     closed: "Closed"
   browse:
     created: "Created"
     closed: "Closed"
@@ -2331,26 +2353,6 @@ en:
       next: "Next Â»"
       previous: "« Previous"
   notes:
       next: "Next Â»"
       previous: "« Previous"
   notes:
-    comment:
-      opened_at_html: "Created %{when} ago"
-      opened_at_by_html: "Created %{when} ago by %{user}"
-      commented_at_html: "Updated %{when} ago"
-      commented_at_by_html: "Updated %{when} ago by %{user}"
-      closed_at_html: "Resolved %{when} ago"
-      closed_at_by_html: "Resolved %{when} ago by %{user}"
-      reopened_at_html: "Reactivated %{when} ago"
-      reopened_at_by_html: "Reactivated %{when} ago by %{user}"
-    rss:
-      title: "OpenStreetMap Notes"
-      description_area: "A list of notes, reported, commented on or closed in your area [(%{min_lat}|%{min_lon}) -- (%{max_lat}|%{max_lon})]"
-      description_item: "An rss feed for note %{id}"
-      opened: "new note (near %{place})"
-      commented: "new comment (near %{place})"
-      closed: "closed note (near %{place})"
-      reopened: "reactivated note (near %{place})"
-    entry:
-      comment: Comment
-      full: Full note
     mine:
       title: "Notes submitted or commented on by %{user}"
       heading: "%{user}'s notes"
     mine:
       title: "Notes submitted or commented on by %{user}"
       heading: "%{user}'s notes"
index d6e28b7367a2c52450e01f6146f218a398ed4524..6f152eb474b35b12998982dca1a166342b3ef73c 100644 (file)
@@ -90,7 +90,7 @@ OpenStreetMap::Application.routes.draw do
     get "swf/trackpoints" => "swf#trackpoints"
 
     # Map notes API
     get "swf/trackpoints" => "swf#trackpoints"
 
     # Map notes API
-    resources :notes, :except => [:new, :edit, :update], :constraints => { :id => /\d+/ }, :defaults => { :format => "xml" } do
+    resources :notes, :except => [:new, :edit, :update], :constraints => { :id => /\d+/ }, :defaults => { :format => "xml" }, :controller => "api/notes" do
       collection do
         get "search"
         get "feed", :defaults => { :format => "rss" }
       collection do
         get "search"
         get "feed", :defaults => { :format => "rss" }
@@ -103,11 +103,11 @@ OpenStreetMap::Application.routes.draw do
       end
     end
 
       end
     end
 
-    post "notes/addPOIexec" => "notes#create"
-    post "notes/closePOIexec" => "notes#close"
-    post "notes/editPOIexec" => "notes#comment"
-    get "notes/getGPX" => "notes#index", :format => "gpx"
-    get "notes/getRSSfeed" => "notes#feed", :format => "rss"
+    post "notes/addPOIexec" => "api/notes#create"
+    post "notes/closePOIexec" => "api/notes#close"
+    post "notes/editPOIexec" => "api/notes#comment"
+    get "notes/getGPX" => "api/notes#index", :format => "gpx"
+    get "notes/getRSSfeed" => "api/notes#feed", :format => "rss"
   end
 
   # Data browsing
   end
 
   # Data browsing
diff --git a/test/controllers/api/notes_controller_test.rb b/test/controllers/api/notes_controller_test.rb
new file mode 100644 (file)
index 0000000..1254c4f
--- /dev/null
@@ -0,0 +1,1090 @@
+require "test_helper"
+
+module Api
+  class NotesControllerTest < ActionController::TestCase
+    def setup
+      # Stub nominatim response for note locations
+      stub_request(:get, %r{^https://nominatim\.openstreetmap\.org/reverse\?})
+        .to_return(:status => 404)
+    end
+
+    ##
+    # test all routes which lead to this controller
+    def test_routes
+      assert_routing(
+        { :path => "/api/0.6/notes", :method => :post },
+        { :controller => "api/notes", :action => "create", :format => "xml" }
+      )
+      assert_routing(
+        { :path => "/api/0.6/notes/1", :method => :get },
+        { :controller => "api/notes", :action => "show", :id => "1", :format => "xml" }
+      )
+      assert_recognizes(
+        { :controller => "api/notes", :action => "show", :id => "1", :format => "xml" },
+        { :path => "/api/0.6/notes/1.xml", :method => :get }
+      )
+      assert_routing(
+        { :path => "/api/0.6/notes/1.rss", :method => :get },
+        { :controller => "api/notes", :action => "show", :id => "1", :format => "rss" }
+      )
+      assert_routing(
+        { :path => "/api/0.6/notes/1.json", :method => :get },
+        { :controller => "api/notes", :action => "show", :id => "1", :format => "json" }
+      )
+      assert_routing(
+        { :path => "/api/0.6/notes/1.gpx", :method => :get },
+        { :controller => "api/notes", :action => "show", :id => "1", :format => "gpx" }
+      )
+      assert_routing(
+        { :path => "/api/0.6/notes/1/comment", :method => :post },
+        { :controller => "api/notes", :action => "comment", :id => "1", :format => "xml" }
+      )
+      assert_routing(
+        { :path => "/api/0.6/notes/1/close", :method => :post },
+        { :controller => "api/notes", :action => "close", :id => "1", :format => "xml" }
+      )
+      assert_routing(
+        { :path => "/api/0.6/notes/1/reopen", :method => :post },
+        { :controller => "api/notes", :action => "reopen", :id => "1", :format => "xml" }
+      )
+      assert_routing(
+        { :path => "/api/0.6/notes/1", :method => :delete },
+        { :controller => "api/notes", :action => "destroy", :id => "1", :format => "xml" }
+      )
+
+      assert_routing(
+        { :path => "/api/0.6/notes", :method => :get },
+        { :controller => "api/notes", :action => "index", :format => "xml" }
+      )
+      assert_recognizes(
+        { :controller => "api/notes", :action => "index", :format => "xml" },
+        { :path => "/api/0.6/notes.xml", :method => :get }
+      )
+      assert_routing(
+        { :path => "/api/0.6/notes.rss", :method => :get },
+        { :controller => "api/notes", :action => "index", :format => "rss" }
+      )
+      assert_routing(
+        { :path => "/api/0.6/notes.json", :method => :get },
+        { :controller => "api/notes", :action => "index", :format => "json" }
+      )
+      assert_routing(
+        { :path => "/api/0.6/notes.gpx", :method => :get },
+        { :controller => "api/notes", :action => "index", :format => "gpx" }
+      )
+
+      assert_routing(
+        { :path => "/api/0.6/notes/search", :method => :get },
+        { :controller => "api/notes", :action => "search", :format => "xml" }
+      )
+      assert_recognizes(
+        { :controller => "api/notes", :action => "search", :format => "xml" },
+        { :path => "/api/0.6/notes/search.xml", :method => :get }
+      )
+      assert_routing(
+        { :path => "/api/0.6/notes/search.rss", :method => :get },
+        { :controller => "api/notes", :action => "search", :format => "rss" }
+      )
+      assert_routing(
+        { :path => "/api/0.6/notes/search.json", :method => :get },
+        { :controller => "api/notes", :action => "search", :format => "json" }
+      )
+      assert_routing(
+        { :path => "/api/0.6/notes/search.gpx", :method => :get },
+        { :controller => "api/notes", :action => "search", :format => "gpx" }
+      )
+
+      assert_routing(
+        { :path => "/api/0.6/notes/feed", :method => :get },
+        { :controller => "api/notes", :action => "feed", :format => "rss" }
+      )
+
+      assert_recognizes(
+        { :controller => "api/notes", :action => "create" },
+        { :path => "/api/0.6/notes/addPOIexec", :method => :post }
+      )
+      assert_recognizes(
+        { :controller => "api/notes", :action => "close" },
+        { :path => "/api/0.6/notes/closePOIexec", :method => :post }
+      )
+      assert_recognizes(
+        { :controller => "api/notes", :action => "comment" },
+        { :path => "/api/0.6/notes/editPOIexec", :method => :post }
+      )
+      assert_recognizes(
+        { :controller => "api/notes", :action => "index", :format => "gpx" },
+        { :path => "/api/0.6/notes/getGPX", :method => :get }
+      )
+      assert_recognizes(
+        { :controller => "api/notes", :action => "feed", :format => "rss" },
+        { :path => "/api/0.6/notes/getRSSfeed", :method => :get }
+      )
+    end
+
+    def test_create_success
+      assert_difference "Note.count", 1 do
+        assert_difference "NoteComment.count", 1 do
+          post :create, :params => { :lat => -1.0, :lon => -1.0, :text => "This is a comment", :format => "json" }
+        end
+      end
+      assert_response :success
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal "Point", js["geometry"]["type"]
+      assert_equal [-1.0, -1.0], js["geometry"]["coordinates"]
+      assert_equal "open", js["properties"]["status"]
+      assert_equal 1, js["properties"]["comments"].count
+      assert_equal "opened", js["properties"]["comments"].last["action"]
+      assert_equal "This is a comment", js["properties"]["comments"].last["text"]
+      assert_nil js["properties"]["comments"].last["user"]
+      id = js["properties"]["id"]
+
+      get :show, :params => { :id => id, :format => "json" }
+      assert_response :success
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal "Point", js["geometry"]["type"]
+      assert_equal [-1.0, -1.0], js["geometry"]["coordinates"]
+      assert_equal id, js["properties"]["id"]
+      assert_equal "open", js["properties"]["status"]
+      assert_equal 1, js["properties"]["comments"].count
+      assert_equal "opened", js["properties"]["comments"].last["action"]
+      assert_equal "This is a comment", js["properties"]["comments"].last["text"]
+      assert_nil js["properties"]["comments"].last["user"]
+    end
+
+    def test_create_fail
+      assert_no_difference "Note.count" do
+        assert_no_difference "NoteComment.count" do
+          post :create, :params => { :lon => -1.0, :text => "This is a comment" }
+        end
+      end
+      assert_response :bad_request
+
+      assert_no_difference "Note.count" do
+        assert_no_difference "NoteComment.count" do
+          post :create, :params => { :lat => -1.0, :text => "This is a comment" }
+        end
+      end
+      assert_response :bad_request
+
+      assert_no_difference "Note.count" do
+        assert_no_difference "NoteComment.count" do
+          post :create, :params => { :lat => -1.0, :lon => -1.0 }
+        end
+      end
+      assert_response :bad_request
+
+      assert_no_difference "Note.count" do
+        assert_no_difference "NoteComment.count" do
+          post :create, :params => { :lat => -1.0, :lon => -1.0, :text => "" }
+        end
+      end
+      assert_response :bad_request
+
+      assert_no_difference "Note.count" do
+        assert_no_difference "NoteComment.count" do
+          post :create, :params => { :lat => -100.0, :lon => -1.0, :text => "This is a comment" }
+        end
+      end
+      assert_response :bad_request
+
+      assert_no_difference "Note.count" do
+        assert_no_difference "NoteComment.count" do
+          post :create, :params => { :lat => -1.0, :lon => -200.0, :text => "This is a comment" }
+        end
+      end
+      assert_response :bad_request
+
+      assert_no_difference "Note.count" do
+        assert_no_difference "NoteComment.count" do
+          post :create, :params => { :lat => "abc", :lon => -1.0, :text => "This is a comment" }
+        end
+      end
+      assert_response :bad_request
+
+      assert_no_difference "Note.count" do
+        assert_no_difference "NoteComment.count" do
+          post :create, :params => { :lat => -1.0, :lon => "abc", :text => "This is a comment" }
+        end
+      end
+      assert_response :bad_request
+
+      assert_no_difference "Note.count" do
+        assert_no_difference "NoteComment.count" do
+          post :create, :params => { :lat => -1.0, :lon => -1.0, :text => "x\u0000y" }
+        end
+      end
+      assert_response :bad_request
+    end
+
+    def test_comment_success
+      open_note_with_comment = create(:note_with_comments)
+      assert_difference "NoteComment.count", 1 do
+        assert_no_difference "ActionMailer::Base.deliveries.size" do
+          perform_enqueued_jobs do
+            post :comment, :params => { :id => open_note_with_comment.id, :text => "This is an additional comment", :format => "json" }
+          end
+        end
+      end
+      assert_response :success
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal open_note_with_comment.id, js["properties"]["id"]
+      assert_equal "open", js["properties"]["status"]
+      assert_equal 2, js["properties"]["comments"].count
+      assert_equal "commented", js["properties"]["comments"].last["action"]
+      assert_equal "This is an additional comment", js["properties"]["comments"].last["text"]
+      assert_nil js["properties"]["comments"].last["user"]
+
+      get :show, :params => { :id => open_note_with_comment.id, :format => "json" }
+      assert_response :success
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal open_note_with_comment.id, js["properties"]["id"]
+      assert_equal "open", js["properties"]["status"]
+      assert_equal 2, js["properties"]["comments"].count
+      assert_equal "commented", js["properties"]["comments"].last["action"]
+      assert_equal "This is an additional comment", js["properties"]["comments"].last["text"]
+      assert_nil js["properties"]["comments"].last["user"]
+
+      # Ensure that emails are sent to users
+      first_user = create(:user)
+      second_user = create(:user)
+      third_user = create(:user)
+
+      note_with_comments_by_users = create(:note) do |note|
+        create(:note_comment, :note => note, :author => first_user)
+        create(:note_comment, :note => note, :author => second_user)
+      end
+      assert_difference "NoteComment.count", 1 do
+        assert_difference "ActionMailer::Base.deliveries.size", 2 do
+          perform_enqueued_jobs do
+            post :comment, :params => { :id => note_with_comments_by_users.id, :text => "This is an additional comment", :format => "json" }
+          end
+        end
+      end
+      assert_response :success
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal note_with_comments_by_users.id, js["properties"]["id"]
+      assert_equal "open", js["properties"]["status"]
+      assert_equal 3, js["properties"]["comments"].count
+      assert_equal "commented", js["properties"]["comments"].last["action"]
+      assert_equal "This is an additional comment", js["properties"]["comments"].last["text"]
+      assert_nil js["properties"]["comments"].last["user"]
+
+      email = ActionMailer::Base.deliveries.find { |e| e.to.first == first_user.email }
+      assert_not_nil email
+      assert_equal 1, email.to.length
+      assert_equal "[OpenStreetMap] An anonymous user has commented on one of your notes", email.subject
+
+      email = ActionMailer::Base.deliveries.find { |e| e.to.first == second_user.email }
+      assert_not_nil email
+      assert_equal 1, email.to.length
+      assert_equal "[OpenStreetMap] An anonymous user has commented on a note you are interested in", email.subject
+
+      get :show, :params => { :id => note_with_comments_by_users.id, :format => "json" }
+      assert_response :success
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal note_with_comments_by_users.id, js["properties"]["id"]
+      assert_equal "open", js["properties"]["status"]
+      assert_equal 3, js["properties"]["comments"].count
+      assert_equal "commented", js["properties"]["comments"].last["action"]
+      assert_equal "This is an additional comment", js["properties"]["comments"].last["text"]
+      assert_nil js["properties"]["comments"].last["user"]
+
+      ActionMailer::Base.deliveries.clear
+
+      basic_authorization third_user.email, "test"
+
+      assert_difference "NoteComment.count", 1 do
+        assert_difference "ActionMailer::Base.deliveries.size", 2 do
+          perform_enqueued_jobs do
+            post :comment, :params => { :id => note_with_comments_by_users.id, :text => "This is an additional comment", :format => "json" }
+          end
+        end
+      end
+      assert_response :success
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal note_with_comments_by_users.id, js["properties"]["id"]
+      assert_equal "open", js["properties"]["status"]
+      assert_equal 4, js["properties"]["comments"].count
+      assert_equal "commented", js["properties"]["comments"].last["action"]
+      assert_equal "This is an additional comment", js["properties"]["comments"].last["text"]
+      assert_equal third_user.display_name, js["properties"]["comments"].last["user"]
+
+      email = ActionMailer::Base.deliveries.find { |e| e.to.first == first_user.email }
+      assert_not_nil email
+      assert_equal 1, email.to.length
+      assert_equal "[OpenStreetMap] #{third_user.display_name} has commented on one of your notes", email.subject
+      assert_equal first_user.email, email.to.first
+
+      email = ActionMailer::Base.deliveries.find { |e| e.to.first == second_user.email }
+      assert_not_nil email
+      assert_equal 1, email.to.length
+      assert_equal "[OpenStreetMap] #{third_user.display_name} has commented on a note you are interested in", email.subject
+
+      get :show, :params => { :id => note_with_comments_by_users.id, :format => "json" }
+      assert_response :success
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal note_with_comments_by_users.id, js["properties"]["id"]
+      assert_equal "open", js["properties"]["status"]
+      assert_equal 4, js["properties"]["comments"].count
+      assert_equal "commented", js["properties"]["comments"].last["action"]
+      assert_equal "This is an additional comment", js["properties"]["comments"].last["text"]
+      assert_equal third_user.display_name, js["properties"]["comments"].last["user"]
+
+      ActionMailer::Base.deliveries.clear
+    end
+
+    def test_comment_fail
+      open_note_with_comment = create(:note_with_comments)
+
+      assert_no_difference "NoteComment.count" do
+        post :comment, :params => { :text => "This is an additional comment" }
+      end
+      assert_response :bad_request
+
+      assert_no_difference "NoteComment.count" do
+        post :comment, :params => { :id => open_note_with_comment.id }
+      end
+      assert_response :bad_request
+
+      assert_no_difference "NoteComment.count" do
+        post :comment, :params => { :id => open_note_with_comment.id, :text => "" }
+      end
+      assert_response :bad_request
+
+      assert_no_difference "NoteComment.count" do
+        post :comment, :params => { :id => 12345, :text => "This is an additional comment" }
+      end
+      assert_response :not_found
+
+      hidden_note_with_comment = create(:note_with_comments, :status => "hidden")
+
+      assert_no_difference "NoteComment.count" do
+        post :comment, :params => { :id => hidden_note_with_comment.id, :text => "This is an additional comment" }
+      end
+      assert_response :gone
+
+      closed_note_with_comment = create(:note_with_comments, :status => "closed", :closed_at => Time.now)
+
+      assert_no_difference "NoteComment.count" do
+        post :comment, :params => { :id => closed_note_with_comment.id, :text => "This is an additional comment" }
+      end
+      assert_response :conflict
+
+      assert_no_difference "NoteComment.count" do
+        post :comment, :params => { :id => open_note_with_comment.id, :text => "x\u0000y" }
+      end
+      assert_response :bad_request
+    end
+
+    def test_close_success
+      open_note_with_comment = create(:note_with_comments)
+      user = create(:user)
+
+      post :close, :params => { :id => open_note_with_comment.id, :text => "This is a close comment", :format => "json" }
+      assert_response :unauthorized
+
+      basic_authorization user.email, "test"
+
+      post :close, :params => { :id => open_note_with_comment.id, :text => "This is a close comment", :format => "json" }
+      assert_response :success
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal open_note_with_comment.id, js["properties"]["id"]
+      assert_equal "closed", js["properties"]["status"]
+      assert_equal 2, js["properties"]["comments"].count
+      assert_equal "closed", js["properties"]["comments"].last["action"]
+      assert_equal "This is a close comment", js["properties"]["comments"].last["text"]
+      assert_equal user.display_name, js["properties"]["comments"].last["user"]
+
+      get :show, :params => { :id => open_note_with_comment.id, :format => "json" }
+      assert_response :success
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal open_note_with_comment.id, js["properties"]["id"]
+      assert_equal "closed", js["properties"]["status"]
+      assert_equal 2, js["properties"]["comments"].count
+      assert_equal "closed", js["properties"]["comments"].last["action"]
+      assert_equal "This is a close comment", js["properties"]["comments"].last["text"]
+      assert_equal user.display_name, js["properties"]["comments"].last["user"]
+    end
+
+    def test_close_fail
+      post :close
+      assert_response :unauthorized
+
+      basic_authorization create(:user).email, "test"
+
+      post :close
+      assert_response :bad_request
+
+      post :close, :params => { :id => 12345 }
+      assert_response :not_found
+
+      hidden_note_with_comment = create(:note_with_comments, :status => "hidden")
+
+      post :close, :params => { :id => hidden_note_with_comment.id }
+      assert_response :gone
+
+      closed_note_with_comment = create(:note_with_comments, :status => "closed", :closed_at => Time.now)
+
+      post :close, :params => { :id => closed_note_with_comment.id }
+      assert_response :conflict
+    end
+
+    def test_reopen_success
+      closed_note_with_comment = create(:note_with_comments, :status => "closed", :closed_at => Time.now)
+      user = create(:user)
+
+      post :reopen, :params => { :id => closed_note_with_comment.id, :text => "This is a reopen comment", :format => "json" }
+      assert_response :unauthorized
+
+      basic_authorization user.email, "test"
+
+      post :reopen, :params => { :id => closed_note_with_comment.id, :text => "This is a reopen comment", :format => "json" }
+      assert_response :success
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal closed_note_with_comment.id, js["properties"]["id"]
+      assert_equal "open", js["properties"]["status"]
+      assert_equal 2, js["properties"]["comments"].count
+      assert_equal "reopened", js["properties"]["comments"].last["action"]
+      assert_equal "This is a reopen comment", js["properties"]["comments"].last["text"]
+      assert_equal user.display_name, js["properties"]["comments"].last["user"]
+
+      get :show, :params => { :id => closed_note_with_comment.id, :format => "json" }
+      assert_response :success
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal closed_note_with_comment.id, js["properties"]["id"]
+      assert_equal "open", js["properties"]["status"]
+      assert_equal 2, js["properties"]["comments"].count
+      assert_equal "reopened", js["properties"]["comments"].last["action"]
+      assert_equal "This is a reopen comment", js["properties"]["comments"].last["text"]
+      assert_equal user.display_name, js["properties"]["comments"].last["user"]
+    end
+
+    def test_reopen_fail
+      hidden_note_with_comment = create(:note_with_comments, :status => "hidden")
+
+      post :reopen, :params => { :id => hidden_note_with_comment.id }
+      assert_response :unauthorized
+
+      basic_authorization create(:user).email, "test"
+
+      post :reopen, :params => { :id => 12345 }
+      assert_response :not_found
+
+      post :reopen, :params => { :id => hidden_note_with_comment.id }
+      assert_response :gone
+
+      open_note_with_comment = create(:note_with_comments)
+
+      post :reopen, :params => { :id => open_note_with_comment.id }
+      assert_response :conflict
+    end
+
+    def test_show_success
+      open_note = create(:note_with_comments)
+
+      get :show, :params => { :id => open_note.id, :format => "xml" }
+      assert_response :success
+      assert_equal "application/xml", @response.content_type
+      assert_select "osm", :count => 1 do
+        assert_select "note[lat='#{open_note.lat}'][lon='#{open_note.lon}']", :count => 1 do
+          assert_select "id", open_note.id.to_s
+          assert_select "url", note_url(open_note, :format => "xml")
+          assert_select "comment_url", comment_note_url(open_note, :format => "xml")
+          assert_select "close_url", close_note_url(open_note, :format => "xml")
+          assert_select "date_created", open_note.created_at.to_s
+          assert_select "status", open_note.status
+          assert_select "comments", :count => 1 do
+            assert_select "comment", :count => 1
+          end
+        end
+      end
+
+      get :show, :params => { :id => open_note.id, :format => "rss" }
+      assert_response :success
+      assert_equal "application/rss+xml", @response.content_type
+      assert_select "rss", :count => 1 do
+        assert_select "channel", :count => 1 do
+          assert_select "item", :count => 1 do
+            assert_select "link", browse_note_url(open_note)
+            assert_select "guid", note_url(open_note)
+            assert_select "pubDate", open_note.created_at.to_s(:rfc822)
+            #          assert_select "geo:lat", open_note.lat.to_s
+            #          assert_select "geo:long", open_note.lon
+            #          assert_select "georss:point", "#{open_note.lon} #{open_note.lon}"
+          end
+        end
+      end
+
+      get :show, :params => { :id => open_note.id, :format => "json" }
+      assert_response :success
+      assert_equal "application/json", @response.content_type
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal "Point", js["geometry"]["type"]
+      assert_equal open_note.lat, js["geometry"]["coordinates"][0]
+      assert_equal open_note.lon, js["geometry"]["coordinates"][1]
+      assert_equal open_note.id, js["properties"]["id"]
+      assert_equal note_url(open_note, :format => "json"), js["properties"]["url"]
+      assert_equal comment_note_url(open_note, :format => "json"), js["properties"]["comment_url"]
+      assert_equal close_note_url(open_note, :format => "json"), js["properties"]["close_url"]
+      assert_equal open_note.created_at.to_s, js["properties"]["date_created"]
+      assert_equal open_note.status, js["properties"]["status"]
+
+      get :show, :params => { :id => open_note.id, :format => "gpx" }
+      assert_response :success
+      assert_equal "application/gpx+xml", @response.content_type
+      assert_select "gpx", :count => 1 do
+        assert_select "wpt[lat='#{open_note.lat}'][lon='#{open_note.lon}']", :count => 1 do
+          assert_select "time", :count => 1
+          assert_select "name", "Note: #{open_note.id}"
+          assert_select "desc", :count => 1
+          assert_select "link[href='http://test.host/note/#{open_note.id}']", :count => 1
+          assert_select "extensions", :count => 1 do
+            assert_select "id", open_note.id.to_s
+            assert_select "url", note_url(open_note, :format => "gpx")
+            assert_select "comment_url", comment_note_url(open_note, :format => "gpx")
+            assert_select "close_url", close_note_url(open_note, :format => "gpx")
+          end
+        end
+      end
+    end
+
+    def test_show_hidden_comment
+      note_with_hidden_comment = create(:note) do |note|
+        create(:note_comment, :note => note, :body => "Valid comment for hidden note")
+        create(:note_comment, :note => note, :visible => false)
+        create(:note_comment, :note => note, :body => "Another valid comment for hidden note")
+      end
+
+      get :show, :params => { :id => note_with_hidden_comment.id, :format => "json" }
+      assert_response :success
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal note_with_hidden_comment.id, js["properties"]["id"]
+      assert_equal 2, js["properties"]["comments"].count
+      assert_equal "Valid comment for hidden note", js["properties"]["comments"][0]["text"]
+      assert_equal "Another valid comment for hidden note", js["properties"]["comments"][1]["text"]
+    end
+
+    def test_show_fail
+      get :show, :params => { :id => 12345 }
+      assert_response :not_found
+
+      get :show, :params => { :id => create(:note, :status => "hidden").id }
+      assert_response :gone
+    end
+
+    def test_destroy_success
+      open_note_with_comment = create(:note_with_comments)
+      user = create(:user)
+      moderator_user = create(:moderator_user)
+
+      delete :destroy, :params => { :id => open_note_with_comment.id, :text => "This is a hide comment", :format => "json" }
+      assert_response :unauthorized
+
+      basic_authorization user.email, "test"
+
+      delete :destroy, :params => { :id => open_note_with_comment.id, :text => "This is a hide comment", :format => "json" }
+      assert_response :forbidden
+
+      basic_authorization moderator_user.email, "test"
+
+      delete :destroy, :params => { :id => open_note_with_comment.id, :text => "This is a hide comment", :format => "json" }
+      assert_response :success
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "Feature", js["type"]
+      assert_equal open_note_with_comment.id, js["properties"]["id"]
+      assert_equal "hidden", js["properties"]["status"]
+      assert_equal 2, js["properties"]["comments"].count
+      assert_equal "hidden", js["properties"]["comments"].last["action"]
+      assert_equal "This is a hide comment", js["properties"]["comments"].last["text"]
+      assert_equal moderator_user.display_name, js["properties"]["comments"].last["user"]
+
+      get :show, :params => { :id => open_note_with_comment.id, :format => "json" }
+      assert_response :success
+
+      basic_authorization user.email, "test"
+      get :show, :params => { :id => open_note_with_comment.id, :format => "json" }
+      assert_response :gone
+    end
+
+    def test_destroy_fail
+      user = create(:user)
+      moderator_user = create(:moderator_user)
+
+      delete :destroy, :params => { :id => 12345, :format => "json" }
+      assert_response :unauthorized
+
+      basic_authorization user.email, "test"
+
+      delete :destroy, :params => { :id => 12345, :format => "json" }
+      assert_response :forbidden
+
+      basic_authorization moderator_user.email, "test"
+
+      delete :destroy, :params => { :id => 12345, :format => "json" }
+      assert_response :not_found
+
+      hidden_note_with_comment = create(:note_with_comments, :status => "hidden")
+
+      delete :destroy, :params => { :id => hidden_note_with_comment.id, :format => "json" }
+      assert_response :gone
+    end
+
+    def test_index_success
+      position = (1.1 * GeoRecord::SCALE).to_i
+      create(:note_with_comments, :latitude => position, :longitude => position)
+      create(:note_with_comments, :latitude => position, :longitude => position)
+
+      get :index, :params => { :bbox => "1,1,1.2,1.2", :format => "rss" }
+      assert_response :success
+      assert_equal "application/rss+xml", @response.content_type
+      assert_select "rss", :count => 1 do
+        assert_select "channel", :count => 1 do
+          assert_select "item", :count => 2
+        end
+      end
+
+      get :index, :params => { :bbox => "1,1,1.2,1.2", :format => "json" }
+      assert_response :success
+      assert_equal "application/json", @response.content_type
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "FeatureCollection", js["type"]
+      assert_equal 2, js["features"].count
+
+      get :index, :params => { :bbox => "1,1,1.2,1.2", :format => "xml" }
+      assert_response :success
+      assert_equal "application/xml", @response.content_type
+      assert_select "osm", :count => 1 do
+        assert_select "note", :count => 2
+      end
+
+      get :index, :params => { :bbox => "1,1,1.2,1.2", :format => "gpx" }
+      assert_response :success
+      assert_equal "application/gpx+xml", @response.content_type
+      assert_select "gpx", :count => 1 do
+        assert_select "wpt", :count => 2
+      end
+    end
+
+    def test_index_limit
+      position = (1.1 * GeoRecord::SCALE).to_i
+      create(:note_with_comments, :latitude => position, :longitude => position)
+      create(:note_with_comments, :latitude => position, :longitude => position)
+
+      get :index, :params => { :bbox => "1,1,1.2,1.2", :limit => 1, :format => "rss" }
+      assert_response :success
+      assert_equal "application/rss+xml", @response.content_type
+      assert_select "rss", :count => 1 do
+        assert_select "channel", :count => 1 do
+          assert_select "item", :count => 1
+        end
+      end
+
+      get :index, :params => { :bbox => "1,1,1.2,1.2", :limit => 1, :format => "json" }
+      assert_response :success
+      assert_equal "application/json", @response.content_type
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "FeatureCollection", js["type"]
+      assert_equal 1, js["features"].count
+
+      get :index, :params => { :bbox => "1,1,1.2,1.2", :limit => 1, :format => "xml" }
+      assert_response :success
+      assert_equal "application/xml", @response.content_type
+      assert_select "osm", :count => 1 do
+        assert_select "note", :count => 1
+      end
+
+      get :index, :params => { :bbox => "1,1,1.2,1.2", :limit => 1, :format => "gpx" }
+      assert_response :success
+      assert_equal "application/gpx+xml", @response.content_type
+      assert_select "gpx", :count => 1 do
+        assert_select "wpt", :count => 1
+      end
+    end
+
+    def test_index_empty_area
+      get :index, :params => { :bbox => "5,5,5.1,5.1", :format => "rss" }
+      assert_response :success
+      assert_equal "application/rss+xml", @response.content_type
+      assert_select "rss", :count => 1 do
+        assert_select "channel", :count => 1 do
+          assert_select "item", :count => 0
+        end
+      end
+
+      get :index, :params => { :bbox => "5,5,5.1,5.1", :format => "json" }
+      assert_response :success
+      assert_equal "application/json", @response.content_type
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "FeatureCollection", js["type"]
+      assert_equal 0, js["features"].count
+
+      get :index, :params => { :bbox => "5,5,5.1,5.1", :format => "xml" }
+      assert_response :success
+      assert_equal "application/xml", @response.content_type
+      assert_select "osm", :count => 1 do
+        assert_select "note", :count => 0
+      end
+
+      get :index, :params => { :bbox => "5,5,5.1,5.1", :format => "gpx" }
+      assert_response :success
+      assert_equal "application/gpx+xml", @response.content_type
+      assert_select "gpx", :count => 1 do
+        assert_select "wpt", :count => 0
+      end
+    end
+
+    def test_index_large_area
+      get :index, :params => { :bbox => "-2.5,-2.5,2.5,2.5", :format => :json }
+      assert_response :success
+      assert_equal "application/json", @response.content_type
+
+      get :index, :params => { :l => "-2.5", :b => "-2.5", :r => "2.5", :t => "2.5", :format => :json }
+      assert_response :success
+      assert_equal "application/json", @response.content_type
+
+      get :index, :params => { :bbox => "-10,-10,12,12", :format => :json }
+      assert_response :bad_request
+      assert_equal "application/json", @response.content_type
+
+      get :index, :params => { :l => "-10", :b => "-10", :r => "12", :t => "12", :format => :json }
+      assert_response :bad_request
+      assert_equal "application/json", @response.content_type
+    end
+
+    def test_index_closed
+      create(:note_with_comments, :status => "closed", :closed_at => Time.now - 5.days)
+      create(:note_with_comments, :status => "closed", :closed_at => Time.now - 100.days)
+      create(:note_with_comments, :status => "hidden")
+      create(:note_with_comments)
+
+      # Open notes + closed in last 7 days
+      get :index, :params => { :bbox => "1,1,1.7,1.7", :closed => "7", :format => "json" }
+      assert_response :success
+      assert_equal "application/json", @response.content_type
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "FeatureCollection", js["type"]
+      assert_equal 2, js["features"].count
+
+      # Only open notes
+      get :index, :params => { :bbox => "1,1,1.7,1.7", :closed => "0", :format => "json" }
+      assert_response :success
+      assert_equal "application/json", @response.content_type
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "FeatureCollection", js["type"]
+      assert_equal 1, js["features"].count
+
+      # Open notes + all closed notes
+      get :index, :params => { :bbox => "1,1,1.7,1.7", :closed => "-1", :format => "json" }
+      assert_response :success
+      assert_equal "application/json", @response.content_type
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "FeatureCollection", js["type"]
+      assert_equal 3, js["features"].count
+    end
+
+    def test_index_bad_params
+      get :index, :params => { :bbox => "-2.5,-2.5,2.5" }
+      assert_response :bad_request
+
+      get :index, :params => { :bbox => "-2.5,-2.5,2.5,2.5,2.5" }
+      assert_response :bad_request
+
+      get :index, :params => { :b => "-2.5", :r => "2.5", :t => "2.5" }
+      assert_response :bad_request
+
+      get :index, :params => { :l => "-2.5", :r => "2.5", :t => "2.5" }
+      assert_response :bad_request
+
+      get :index, :params => { :l => "-2.5", :b => "-2.5", :t => "2.5" }
+      assert_response :bad_request
+
+      get :index, :params => { :l => "-2.5", :b => "-2.5", :r => "2.5" }
+      assert_response :bad_request
+
+      get :index, :params => { :bbox => "1,1,1.7,1.7", :limit => "0", :format => "json" }
+      assert_response :bad_request
+
+      get :index, :params => { :bbox => "1,1,1.7,1.7", :limit => "10001", :format => "json" }
+      assert_response :bad_request
+    end
+
+    def test_search_success
+      create(:note_with_comments)
+
+      get :search, :params => { :q => "note comment", :format => "xml" }
+      assert_response :success
+      assert_equal "application/xml", @response.content_type
+      assert_select "osm", :count => 1 do
+        assert_select "note", :count => 1
+      end
+
+      get :search, :params => { :q => "note comment", :format => "json" }
+      assert_response :success
+      assert_equal "application/json", @response.content_type
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "FeatureCollection", js["type"]
+      assert_equal 1, js["features"].count
+
+      get :search, :params => { :q => "note comment", :format => "rss" }
+      assert_response :success
+      assert_equal "application/rss+xml", @response.content_type
+      assert_select "rss", :count => 1 do
+        assert_select "channel", :count => 1 do
+          assert_select "item", :count => 1
+        end
+      end
+
+      get :search, :params => { :q => "note comment", :format => "gpx" }
+      assert_response :success
+      assert_equal "application/gpx+xml", @response.content_type
+      assert_select "gpx", :count => 1 do
+        assert_select "wpt", :count => 1
+      end
+    end
+
+    def test_search_by_display_name_success
+      user = create(:user)
+
+      create(:note) do |note|
+        create(:note_comment, :note => note, :author => user)
+      end
+
+      get :search, :params => { :display_name => user.display_name, :format => "xml" }
+      assert_response :success
+      assert_equal "application/xml", @response.content_type
+      assert_select "osm", :count => 1 do
+        assert_select "note", :count => 1
+      end
+
+      get :search, :params => { :display_name => user.display_name, :format => "json" }
+      assert_response :success
+      assert_equal "application/json", @response.content_type
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "FeatureCollection", js["type"]
+      assert_equal 1, js["features"].count
+
+      get :search, :params => { :display_name => user.display_name, :format => "rss" }
+      assert_response :success
+      assert_equal "application/rss+xml", @response.content_type
+      assert_select "rss", :count => 1 do
+        assert_select "channel", :count => 1 do
+          assert_select "item", :count => 1
+        end
+      end
+
+      get :search, :params => { :display_name => user.display_name, :format => "gpx" }
+      assert_response :success
+      assert_equal "application/gpx+xml", @response.content_type
+      assert_select "gpx", :count => 1 do
+        assert_select "wpt", :count => 1
+      end
+    end
+
+    def test_search_by_user_success
+      user = create(:user)
+
+      create(:note) do |note|
+        create(:note_comment, :note => note, :author => user)
+      end
+
+      get :search, :params => { :user => user.id, :format => "xml" }
+      assert_response :success
+      assert_equal "application/xml", @response.content_type
+      assert_select "osm", :count => 1 do
+        assert_select "note", :count => 1
+      end
+
+      get :search, :params => { :user => user.id, :format => "json" }
+      assert_response :success
+      assert_equal "application/json", @response.content_type
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "FeatureCollection", js["type"]
+      assert_equal 1, js["features"].count
+
+      get :search, :params => { :user => user.id, :format => "rss" }
+      assert_response :success
+      assert_equal "application/rss+xml", @response.content_type
+      assert_select "rss", :count => 1 do
+        assert_select "channel", :count => 1 do
+          assert_select "item", :count => 1
+        end
+      end
+
+      get :search, :params => { :user => user.id, :format => "gpx" }
+      assert_response :success
+      assert_equal "application/gpx+xml", @response.content_type
+      assert_select "gpx", :count => 1 do
+        assert_select "wpt", :count => 1
+      end
+    end
+
+    def test_search_no_match
+      create(:note_with_comments)
+
+      get :search, :params => { :q => "no match", :format => "xml" }
+      assert_response :success
+      assert_equal "application/xml", @response.content_type
+      assert_select "osm", :count => 1 do
+        assert_select "note", :count => 0
+      end
+
+      get :search, :params => { :q => "no match", :format => "json" }
+      assert_response :success
+      assert_equal "application/json", @response.content_type
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "FeatureCollection", js["type"]
+      assert_equal 0, js["features"].count
+
+      get :search, :params => { :q => "no match", :format => "rss" }
+      assert_response :success
+      assert_equal "application/rss+xml", @response.content_type
+      assert_select "rss", :count => 1 do
+        assert_select "channel", :count => 1 do
+          assert_select "item", :count => 0
+        end
+      end
+
+      get :search, :params => { :q => "no match", :format => "gpx" }
+      assert_response :success
+      assert_equal "application/gpx+xml", @response.content_type
+      assert_select "gpx", :count => 1 do
+        assert_select "wpt", :count => 0
+      end
+    end
+
+    def test_search_by_time_no_match
+      create(:note_with_comments)
+
+      get :search, :params => { :from => "01.01.2010", :to => "01.10.2010", :format => "xml" }
+      assert_response :success
+      assert_equal "application/xml", @response.content_type
+      assert_select "osm", :count => 1 do
+        assert_select "note", :count => 0
+      end
+
+      get :search, :params => { :from => "01.01.2010", :to => "01.10.2010", :format => "json" }
+      assert_response :success
+      assert_equal "application/json", @response.content_type
+      js = ActiveSupport::JSON.decode(@response.body)
+      assert_not_nil js
+      assert_equal "FeatureCollection", js["type"]
+      assert_equal 0, js["features"].count
+
+      get :search, :params => { :from => "01.01.2010", :to => "01.10.2010", :format => "rss" }
+      assert_response :success
+      assert_equal "application/rss+xml", @response.content_type
+      assert_select "rss", :count => 1 do
+        assert_select "channel", :count => 1 do
+          assert_select "item", :count => 0
+        end
+      end
+
+      get :search, :params => { :from => "01.01.2010", :to => "01.10.2010", :format => "gpx" }
+      assert_response :success
+      assert_equal "application/gpx+xml", @response.content_type
+      assert_select "gpx", :count => 1 do
+        assert_select "wpt", :count => 0
+      end
+    end
+
+    def test_search_bad_params
+      get :search, :params => { :q => "no match", :limit => "0", :format => "json" }
+      assert_response :bad_request
+
+      get :search, :params => { :q => "no match", :limit => "10001", :format => "json" }
+      assert_response :bad_request
+
+      get :search, :params => { :display_name => "non-existent" }
+      assert_response :bad_request
+
+      get :search, :params => { :user => "-1" }
+      assert_response :bad_request
+
+      get :search, :params => { :from => "wrong-date", :to => "wrong-date" }
+      assert_response :bad_request
+
+      get :search, :params => { :from => "01.01.2010", :to => "2010.01.2010" }
+      assert_response :bad_request
+    end
+
+    def test_feed_success
+      position = (1.1 * GeoRecord::SCALE).to_i
+      create(:note_with_comments, :latitude => position, :longitude => position)
+      create(:note_with_comments, :latitude => position, :longitude => position)
+      position = (1.5 * GeoRecord::SCALE).to_i
+      create(:note_with_comments, :latitude => position, :longitude => position)
+      create(:note_with_comments, :latitude => position, :longitude => position)
+
+      get :feed, :params => { :format => "rss" }
+      assert_response :success
+      assert_equal "application/rss+xml", @response.content_type
+      assert_select "rss", :count => 1 do
+        assert_select "channel", :count => 1 do
+          assert_select "item", :count => 4
+        end
+      end
+
+      get :feed, :params => { :bbox => "1,1,1.2,1.2", :format => "rss" }
+      assert_response :success
+      assert_equal "application/rss+xml", @response.content_type
+      assert_select "rss", :count => 1 do
+        assert_select "channel", :count => 1 do
+          assert_select "item", :count => 2
+        end
+      end
+    end
+
+    def test_feed_fail
+      get :feed, :params => { :bbox => "1,1,1.2", :format => "rss" }
+      assert_response :bad_request
+
+      get :feed, :params => { :bbox => "1,1,1.2,1.2,1.2", :format => "rss" }
+      assert_response :bad_request
+
+      get :feed, :params => { :bbox => "1,1,1.2,1.2", :limit => "0", :format => "rss" }
+      assert_response :bad_request
+
+      get :feed, :params => { :bbox => "1,1,1.2,1.2", :limit => "10001", :format => "rss" }
+      assert_response :bad_request
+    end
+  end
+end
index a1c32333224337f95e238c024d540fa222d1fb42..82821de6c0725db74b7e0864706e79e2fd96ec78 100644 (file)
@@ -10,1087 +10,12 @@ class NotesControllerTest < ActionController::TestCase
   ##
   # test all routes which lead to this controller
   def test_routes
   ##
   # test all routes which lead to this controller
   def test_routes
-    assert_routing(
-      { :path => "/api/0.6/notes", :method => :post },
-      { :controller => "notes", :action => "create", :format => "xml" }
-    )
-    assert_routing(
-      { :path => "/api/0.6/notes/1", :method => :get },
-      { :controller => "notes", :action => "show", :id => "1", :format => "xml" }
-    )
-    assert_recognizes(
-      { :controller => "notes", :action => "show", :id => "1", :format => "xml" },
-      { :path => "/api/0.6/notes/1.xml", :method => :get }
-    )
-    assert_routing(
-      { :path => "/api/0.6/notes/1.rss", :method => :get },
-      { :controller => "notes", :action => "show", :id => "1", :format => "rss" }
-    )
-    assert_routing(
-      { :path => "/api/0.6/notes/1.json", :method => :get },
-      { :controller => "notes", :action => "show", :id => "1", :format => "json" }
-    )
-    assert_routing(
-      { :path => "/api/0.6/notes/1.gpx", :method => :get },
-      { :controller => "notes", :action => "show", :id => "1", :format => "gpx" }
-    )
-    assert_routing(
-      { :path => "/api/0.6/notes/1/comment", :method => :post },
-      { :controller => "notes", :action => "comment", :id => "1", :format => "xml" }
-    )
-    assert_routing(
-      { :path => "/api/0.6/notes/1/close", :method => :post },
-      { :controller => "notes", :action => "close", :id => "1", :format => "xml" }
-    )
-    assert_routing(
-      { :path => "/api/0.6/notes/1/reopen", :method => :post },
-      { :controller => "notes", :action => "reopen", :id => "1", :format => "xml" }
-    )
-    assert_routing(
-      { :path => "/api/0.6/notes/1", :method => :delete },
-      { :controller => "notes", :action => "destroy", :id => "1", :format => "xml" }
-    )
-
-    assert_routing(
-      { :path => "/api/0.6/notes", :method => :get },
-      { :controller => "notes", :action => "index", :format => "xml" }
-    )
-    assert_recognizes(
-      { :controller => "notes", :action => "index", :format => "xml" },
-      { :path => "/api/0.6/notes.xml", :method => :get }
-    )
-    assert_routing(
-      { :path => "/api/0.6/notes.rss", :method => :get },
-      { :controller => "notes", :action => "index", :format => "rss" }
-    )
-    assert_routing(
-      { :path => "/api/0.6/notes.json", :method => :get },
-      { :controller => "notes", :action => "index", :format => "json" }
-    )
-    assert_routing(
-      { :path => "/api/0.6/notes.gpx", :method => :get },
-      { :controller => "notes", :action => "index", :format => "gpx" }
-    )
-
-    assert_routing(
-      { :path => "/api/0.6/notes/search", :method => :get },
-      { :controller => "notes", :action => "search", :format => "xml" }
-    )
-    assert_recognizes(
-      { :controller => "notes", :action => "search", :format => "xml" },
-      { :path => "/api/0.6/notes/search.xml", :method => :get }
-    )
-    assert_routing(
-      { :path => "/api/0.6/notes/search.rss", :method => :get },
-      { :controller => "notes", :action => "search", :format => "rss" }
-    )
-    assert_routing(
-      { :path => "/api/0.6/notes/search.json", :method => :get },
-      { :controller => "notes", :action => "search", :format => "json" }
-    )
-    assert_routing(
-      { :path => "/api/0.6/notes/search.gpx", :method => :get },
-      { :controller => "notes", :action => "search", :format => "gpx" }
-    )
-
-    assert_routing(
-      { :path => "/api/0.6/notes/feed", :method => :get },
-      { :controller => "notes", :action => "feed", :format => "rss" }
-    )
-
-    assert_recognizes(
-      { :controller => "notes", :action => "create" },
-      { :path => "/api/0.6/notes/addPOIexec", :method => :post }
-    )
-    assert_recognizes(
-      { :controller => "notes", :action => "close" },
-      { :path => "/api/0.6/notes/closePOIexec", :method => :post }
-    )
-    assert_recognizes(
-      { :controller => "notes", :action => "comment" },
-      { :path => "/api/0.6/notes/editPOIexec", :method => :post }
-    )
-    assert_recognizes(
-      { :controller => "notes", :action => "index", :format => "gpx" },
-      { :path => "/api/0.6/notes/getGPX", :method => :get }
-    )
-    assert_recognizes(
-      { :controller => "notes", :action => "feed", :format => "rss" },
-      { :path => "/api/0.6/notes/getRSSfeed", :method => :get }
-    )
-
     assert_routing(
       { :path => "/user/username/notes", :method => :get },
       { :controller => "notes", :action => "mine", :display_name => "username" }
     )
   end
 
     assert_routing(
       { :path => "/user/username/notes", :method => :get },
       { :controller => "notes", :action => "mine", :display_name => "username" }
     )
   end
 
-  def test_create_success
-    assert_difference "Note.count", 1 do
-      assert_difference "NoteComment.count", 1 do
-        post :create, :params => { :lat => -1.0, :lon => -1.0, :text => "This is a comment", :format => "json" }
-      end
-    end
-    assert_response :success
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "Feature", js["type"]
-    assert_equal "Point", js["geometry"]["type"]
-    assert_equal [-1.0, -1.0], js["geometry"]["coordinates"]
-    assert_equal "open", js["properties"]["status"]
-    assert_equal 1, js["properties"]["comments"].count
-    assert_equal "opened", js["properties"]["comments"].last["action"]
-    assert_equal "This is a comment", js["properties"]["comments"].last["text"]
-    assert_nil js["properties"]["comments"].last["user"]
-    id = js["properties"]["id"]
-
-    get :show, :params => { :id => id, :format => "json" }
-    assert_response :success
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "Feature", js["type"]
-    assert_equal "Point", js["geometry"]["type"]
-    assert_equal [-1.0, -1.0], js["geometry"]["coordinates"]
-    assert_equal id, js["properties"]["id"]
-    assert_equal "open", js["properties"]["status"]
-    assert_equal 1, js["properties"]["comments"].count
-    assert_equal "opened", js["properties"]["comments"].last["action"]
-    assert_equal "This is a comment", js["properties"]["comments"].last["text"]
-    assert_nil js["properties"]["comments"].last["user"]
-  end
-
-  def test_create_fail
-    assert_no_difference "Note.count" do
-      assert_no_difference "NoteComment.count" do
-        post :create, :params => { :lon => -1.0, :text => "This is a comment" }
-      end
-    end
-    assert_response :bad_request
-
-    assert_no_difference "Note.count" do
-      assert_no_difference "NoteComment.count" do
-        post :create, :params => { :lat => -1.0, :text => "This is a comment" }
-      end
-    end
-    assert_response :bad_request
-
-    assert_no_difference "Note.count" do
-      assert_no_difference "NoteComment.count" do
-        post :create, :params => { :lat => -1.0, :lon => -1.0 }
-      end
-    end
-    assert_response :bad_request
-
-    assert_no_difference "Note.count" do
-      assert_no_difference "NoteComment.count" do
-        post :create, :params => { :lat => -1.0, :lon => -1.0, :text => "" }
-      end
-    end
-    assert_response :bad_request
-
-    assert_no_difference "Note.count" do
-      assert_no_difference "NoteComment.count" do
-        post :create, :params => { :lat => -100.0, :lon => -1.0, :text => "This is a comment" }
-      end
-    end
-    assert_response :bad_request
-
-    assert_no_difference "Note.count" do
-      assert_no_difference "NoteComment.count" do
-        post :create, :params => { :lat => -1.0, :lon => -200.0, :text => "This is a comment" }
-      end
-    end
-    assert_response :bad_request
-
-    assert_no_difference "Note.count" do
-      assert_no_difference "NoteComment.count" do
-        post :create, :params => { :lat => "abc", :lon => -1.0, :text => "This is a comment" }
-      end
-    end
-    assert_response :bad_request
-
-    assert_no_difference "Note.count" do
-      assert_no_difference "NoteComment.count" do
-        post :create, :params => { :lat => -1.0, :lon => "abc", :text => "This is a comment" }
-      end
-    end
-    assert_response :bad_request
-
-    assert_no_difference "Note.count" do
-      assert_no_difference "NoteComment.count" do
-        post :create, :params => { :lat => -1.0, :lon => -1.0, :text => "x\u0000y" }
-      end
-    end
-    assert_response :bad_request
-  end
-
-  def test_comment_success
-    open_note_with_comment = create(:note_with_comments)
-    assert_difference "NoteComment.count", 1 do
-      assert_no_difference "ActionMailer::Base.deliveries.size" do
-        perform_enqueued_jobs do
-          post :comment, :params => { :id => open_note_with_comment.id, :text => "This is an additional comment", :format => "json" }
-        end
-      end
-    end
-    assert_response :success
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "Feature", js["type"]
-    assert_equal open_note_with_comment.id, js["properties"]["id"]
-    assert_equal "open", js["properties"]["status"]
-    assert_equal 2, js["properties"]["comments"].count
-    assert_equal "commented", js["properties"]["comments"].last["action"]
-    assert_equal "This is an additional comment", js["properties"]["comments"].last["text"]
-    assert_nil js["properties"]["comments"].last["user"]
-
-    get :show, :params => { :id => open_note_with_comment.id, :format => "json" }
-    assert_response :success
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "Feature", js["type"]
-    assert_equal open_note_with_comment.id, js["properties"]["id"]
-    assert_equal "open", js["properties"]["status"]
-    assert_equal 2, js["properties"]["comments"].count
-    assert_equal "commented", js["properties"]["comments"].last["action"]
-    assert_equal "This is an additional comment", js["properties"]["comments"].last["text"]
-    assert_nil js["properties"]["comments"].last["user"]
-
-    # Ensure that emails are sent to users
-    first_user = create(:user)
-    second_user = create(:user)
-    third_user = create(:user)
-
-    note_with_comments_by_users = create(:note) do |note|
-      create(:note_comment, :note => note, :author => first_user)
-      create(:note_comment, :note => note, :author => second_user)
-    end
-    assert_difference "NoteComment.count", 1 do
-      assert_difference "ActionMailer::Base.deliveries.size", 2 do
-        perform_enqueued_jobs do
-          post :comment, :params => { :id => note_with_comments_by_users.id, :text => "This is an additional comment", :format => "json" }
-        end
-      end
-    end
-    assert_response :success
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "Feature", js["type"]
-    assert_equal note_with_comments_by_users.id, js["properties"]["id"]
-    assert_equal "open", js["properties"]["status"]
-    assert_equal 3, js["properties"]["comments"].count
-    assert_equal "commented", js["properties"]["comments"].last["action"]
-    assert_equal "This is an additional comment", js["properties"]["comments"].last["text"]
-    assert_nil js["properties"]["comments"].last["user"]
-
-    email = ActionMailer::Base.deliveries.find { |e| e.to.first == first_user.email }
-    assert_not_nil email
-    assert_equal 1, email.to.length
-    assert_equal "[OpenStreetMap] An anonymous user has commented on one of your notes", email.subject
-
-    email = ActionMailer::Base.deliveries.find { |e| e.to.first == second_user.email }
-    assert_not_nil email
-    assert_equal 1, email.to.length
-    assert_equal "[OpenStreetMap] An anonymous user has commented on a note you are interested in", email.subject
-
-    get :show, :params => { :id => note_with_comments_by_users.id, :format => "json" }
-    assert_response :success
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "Feature", js["type"]
-    assert_equal note_with_comments_by_users.id, js["properties"]["id"]
-    assert_equal "open", js["properties"]["status"]
-    assert_equal 3, js["properties"]["comments"].count
-    assert_equal "commented", js["properties"]["comments"].last["action"]
-    assert_equal "This is an additional comment", js["properties"]["comments"].last["text"]
-    assert_nil js["properties"]["comments"].last["user"]
-
-    ActionMailer::Base.deliveries.clear
-
-    basic_authorization third_user.email, "test"
-
-    assert_difference "NoteComment.count", 1 do
-      assert_difference "ActionMailer::Base.deliveries.size", 2 do
-        perform_enqueued_jobs do
-          post :comment, :params => { :id => note_with_comments_by_users.id, :text => "This is an additional comment", :format => "json" }
-        end
-      end
-    end
-    assert_response :success
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "Feature", js["type"]
-    assert_equal note_with_comments_by_users.id, js["properties"]["id"]
-    assert_equal "open", js["properties"]["status"]
-    assert_equal 4, js["properties"]["comments"].count
-    assert_equal "commented", js["properties"]["comments"].last["action"]
-    assert_equal "This is an additional comment", js["properties"]["comments"].last["text"]
-    assert_equal third_user.display_name, js["properties"]["comments"].last["user"]
-
-    email = ActionMailer::Base.deliveries.find { |e| e.to.first == first_user.email }
-    assert_not_nil email
-    assert_equal 1, email.to.length
-    assert_equal "[OpenStreetMap] #{third_user.display_name} has commented on one of your notes", email.subject
-    assert_equal first_user.email, email.to.first
-
-    email = ActionMailer::Base.deliveries.find { |e| e.to.first == second_user.email }
-    assert_not_nil email
-    assert_equal 1, email.to.length
-    assert_equal "[OpenStreetMap] #{third_user.display_name} has commented on a note you are interested in", email.subject
-
-    get :show, :params => { :id => note_with_comments_by_users.id, :format => "json" }
-    assert_response :success
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "Feature", js["type"]
-    assert_equal note_with_comments_by_users.id, js["properties"]["id"]
-    assert_equal "open", js["properties"]["status"]
-    assert_equal 4, js["properties"]["comments"].count
-    assert_equal "commented", js["properties"]["comments"].last["action"]
-    assert_equal "This is an additional comment", js["properties"]["comments"].last["text"]
-    assert_equal third_user.display_name, js["properties"]["comments"].last["user"]
-
-    ActionMailer::Base.deliveries.clear
-  end
-
-  def test_comment_fail
-    open_note_with_comment = create(:note_with_comments)
-
-    assert_no_difference "NoteComment.count" do
-      post :comment, :params => { :text => "This is an additional comment" }
-    end
-    assert_response :bad_request
-
-    assert_no_difference "NoteComment.count" do
-      post :comment, :params => { :id => open_note_with_comment.id }
-    end
-    assert_response :bad_request
-
-    assert_no_difference "NoteComment.count" do
-      post :comment, :params => { :id => open_note_with_comment.id, :text => "" }
-    end
-    assert_response :bad_request
-
-    assert_no_difference "NoteComment.count" do
-      post :comment, :params => { :id => 12345, :text => "This is an additional comment" }
-    end
-    assert_response :not_found
-
-    hidden_note_with_comment = create(:note_with_comments, :status => "hidden")
-
-    assert_no_difference "NoteComment.count" do
-      post :comment, :params => { :id => hidden_note_with_comment.id, :text => "This is an additional comment" }
-    end
-    assert_response :gone
-
-    closed_note_with_comment = create(:note_with_comments, :status => "closed", :closed_at => Time.now)
-
-    assert_no_difference "NoteComment.count" do
-      post :comment, :params => { :id => closed_note_with_comment.id, :text => "This is an additional comment" }
-    end
-    assert_response :conflict
-
-    assert_no_difference "NoteComment.count" do
-      post :comment, :params => { :id => open_note_with_comment.id, :text => "x\u0000y" }
-    end
-    assert_response :bad_request
-  end
-
-  def test_close_success
-    open_note_with_comment = create(:note_with_comments)
-    user = create(:user)
-
-    post :close, :params => { :id => open_note_with_comment.id, :text => "This is a close comment", :format => "json" }
-    assert_response :unauthorized
-
-    basic_authorization user.email, "test"
-
-    post :close, :params => { :id => open_note_with_comment.id, :text => "This is a close comment", :format => "json" }
-    assert_response :success
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "Feature", js["type"]
-    assert_equal open_note_with_comment.id, js["properties"]["id"]
-    assert_equal "closed", js["properties"]["status"]
-    assert_equal 2, js["properties"]["comments"].count
-    assert_equal "closed", js["properties"]["comments"].last["action"]
-    assert_equal "This is a close comment", js["properties"]["comments"].last["text"]
-    assert_equal user.display_name, js["properties"]["comments"].last["user"]
-
-    get :show, :params => { :id => open_note_with_comment.id, :format => "json" }
-    assert_response :success
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "Feature", js["type"]
-    assert_equal open_note_with_comment.id, js["properties"]["id"]
-    assert_equal "closed", js["properties"]["status"]
-    assert_equal 2, js["properties"]["comments"].count
-    assert_equal "closed", js["properties"]["comments"].last["action"]
-    assert_equal "This is a close comment", js["properties"]["comments"].last["text"]
-    assert_equal user.display_name, js["properties"]["comments"].last["user"]
-  end
-
-  def test_close_fail
-    post :close
-    assert_response :unauthorized
-
-    basic_authorization create(:user).email, "test"
-
-    post :close
-    assert_response :bad_request
-
-    post :close, :params => { :id => 12345 }
-    assert_response :not_found
-
-    hidden_note_with_comment = create(:note_with_comments, :status => "hidden")
-
-    post :close, :params => { :id => hidden_note_with_comment.id }
-    assert_response :gone
-
-    closed_note_with_comment = create(:note_with_comments, :status => "closed", :closed_at => Time.now)
-
-    post :close, :params => { :id => closed_note_with_comment.id }
-    assert_response :conflict
-  end
-
-  def test_reopen_success
-    closed_note_with_comment = create(:note_with_comments, :status => "closed", :closed_at => Time.now)
-    user = create(:user)
-
-    post :reopen, :params => { :id => closed_note_with_comment.id, :text => "This is a reopen comment", :format => "json" }
-    assert_response :unauthorized
-
-    basic_authorization user.email, "test"
-
-    post :reopen, :params => { :id => closed_note_with_comment.id, :text => "This is a reopen comment", :format => "json" }
-    assert_response :success
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "Feature", js["type"]
-    assert_equal closed_note_with_comment.id, js["properties"]["id"]
-    assert_equal "open", js["properties"]["status"]
-    assert_equal 2, js["properties"]["comments"].count
-    assert_equal "reopened", js["properties"]["comments"].last["action"]
-    assert_equal "This is a reopen comment", js["properties"]["comments"].last["text"]
-    assert_equal user.display_name, js["properties"]["comments"].last["user"]
-
-    get :show, :params => { :id => closed_note_with_comment.id, :format => "json" }
-    assert_response :success
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "Feature", js["type"]
-    assert_equal closed_note_with_comment.id, js["properties"]["id"]
-    assert_equal "open", js["properties"]["status"]
-    assert_equal 2, js["properties"]["comments"].count
-    assert_equal "reopened", js["properties"]["comments"].last["action"]
-    assert_equal "This is a reopen comment", js["properties"]["comments"].last["text"]
-    assert_equal user.display_name, js["properties"]["comments"].last["user"]
-  end
-
-  def test_reopen_fail
-    hidden_note_with_comment = create(:note_with_comments, :status => "hidden")
-
-    post :reopen, :params => { :id => hidden_note_with_comment.id }
-    assert_response :unauthorized
-
-    basic_authorization create(:user).email, "test"
-
-    post :reopen, :params => { :id => 12345 }
-    assert_response :not_found
-
-    post :reopen, :params => { :id => hidden_note_with_comment.id }
-    assert_response :gone
-
-    open_note_with_comment = create(:note_with_comments)
-
-    post :reopen, :params => { :id => open_note_with_comment.id }
-    assert_response :conflict
-  end
-
-  def test_show_success
-    open_note = create(:note_with_comments)
-
-    get :show, :params => { :id => open_note.id, :format => "xml" }
-    assert_response :success
-    assert_equal "application/xml", @response.content_type
-    assert_select "osm", :count => 1 do
-      assert_select "note[lat='#{open_note.lat}'][lon='#{open_note.lon}']", :count => 1 do
-        assert_select "id", open_note.id.to_s
-        assert_select "url", note_url(open_note, :format => "xml")
-        assert_select "comment_url", comment_note_url(open_note, :format => "xml")
-        assert_select "close_url", close_note_url(open_note, :format => "xml")
-        assert_select "date_created", open_note.created_at.to_s
-        assert_select "status", open_note.status
-        assert_select "comments", :count => 1 do
-          assert_select "comment", :count => 1
-        end
-      end
-    end
-
-    get :show, :params => { :id => open_note.id, :format => "rss" }
-    assert_response :success
-    assert_equal "application/rss+xml", @response.content_type
-    assert_select "rss", :count => 1 do
-      assert_select "channel", :count => 1 do
-        assert_select "item", :count => 1 do
-          assert_select "link", browse_note_url(open_note)
-          assert_select "guid", note_url(open_note)
-          assert_select "pubDate", open_note.created_at.to_s(:rfc822)
-          #          assert_select "geo:lat", open_note.lat.to_s
-          #          assert_select "geo:long", open_note.lon
-          #          assert_select "georss:point", "#{open_note.lon} #{open_note.lon}"
-        end
-      end
-    end
-
-    get :show, :params => { :id => open_note.id, :format => "json" }
-    assert_response :success
-    assert_equal "application/json", @response.content_type
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "Feature", js["type"]
-    assert_equal "Point", js["geometry"]["type"]
-    assert_equal open_note.lat, js["geometry"]["coordinates"][0]
-    assert_equal open_note.lon, js["geometry"]["coordinates"][1]
-    assert_equal open_note.id, js["properties"]["id"]
-    assert_equal note_url(open_note, :format => "json"), js["properties"]["url"]
-    assert_equal comment_note_url(open_note, :format => "json"), js["properties"]["comment_url"]
-    assert_equal close_note_url(open_note, :format => "json"), js["properties"]["close_url"]
-    assert_equal open_note.created_at.to_s, js["properties"]["date_created"]
-    assert_equal open_note.status, js["properties"]["status"]
-
-    get :show, :params => { :id => open_note.id, :format => "gpx" }
-    assert_response :success
-    assert_equal "application/gpx+xml", @response.content_type
-    assert_select "gpx", :count => 1 do
-      assert_select "wpt[lat='#{open_note.lat}'][lon='#{open_note.lon}']", :count => 1 do
-        assert_select "time", :count => 1
-        assert_select "name", "Note: #{open_note.id}"
-        assert_select "desc", :count => 1
-        assert_select "link[href='http://test.host/note/#{open_note.id}']", :count => 1
-        assert_select "extensions", :count => 1 do
-          assert_select "id", open_note.id.to_s
-          assert_select "url", note_url(open_note, :format => "gpx")
-          assert_select "comment_url", comment_note_url(open_note, :format => "gpx")
-          assert_select "close_url", close_note_url(open_note, :format => "gpx")
-        end
-      end
-    end
-  end
-
-  def test_show_hidden_comment
-    note_with_hidden_comment = create(:note) do |note|
-      create(:note_comment, :note => note, :body => "Valid comment for hidden note")
-      create(:note_comment, :note => note, :visible => false)
-      create(:note_comment, :note => note, :body => "Another valid comment for hidden note")
-    end
-
-    get :show, :params => { :id => note_with_hidden_comment.id, :format => "json" }
-    assert_response :success
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "Feature", js["type"]
-    assert_equal note_with_hidden_comment.id, js["properties"]["id"]
-    assert_equal 2, js["properties"]["comments"].count
-    assert_equal "Valid comment for hidden note", js["properties"]["comments"][0]["text"]
-    assert_equal "Another valid comment for hidden note", js["properties"]["comments"][1]["text"]
-  end
-
-  def test_show_fail
-    get :show, :params => { :id => 12345 }
-    assert_response :not_found
-
-    get :show, :params => { :id => create(:note, :status => "hidden").id }
-    assert_response :gone
-  end
-
-  def test_destroy_success
-    open_note_with_comment = create(:note_with_comments)
-    user = create(:user)
-    moderator_user = create(:moderator_user)
-
-    delete :destroy, :params => { :id => open_note_with_comment.id, :text => "This is a hide comment", :format => "json" }
-    assert_response :unauthorized
-
-    basic_authorization user.email, "test"
-
-    delete :destroy, :params => { :id => open_note_with_comment.id, :text => "This is a hide comment", :format => "json" }
-    assert_response :forbidden
-
-    basic_authorization moderator_user.email, "test"
-
-    delete :destroy, :params => { :id => open_note_with_comment.id, :text => "This is a hide comment", :format => "json" }
-    assert_response :success
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "Feature", js["type"]
-    assert_equal open_note_with_comment.id, js["properties"]["id"]
-    assert_equal "hidden", js["properties"]["status"]
-    assert_equal 2, js["properties"]["comments"].count
-    assert_equal "hidden", js["properties"]["comments"].last["action"]
-    assert_equal "This is a hide comment", js["properties"]["comments"].last["text"]
-    assert_equal moderator_user.display_name, js["properties"]["comments"].last["user"]
-
-    get :show, :params => { :id => open_note_with_comment.id, :format => "json" }
-    assert_response :success
-
-    basic_authorization user.email, "test"
-    get :show, :params => { :id => open_note_with_comment.id, :format => "json" }
-    assert_response :gone
-  end
-
-  def test_destroy_fail
-    user = create(:user)
-    moderator_user = create(:moderator_user)
-
-    delete :destroy, :params => { :id => 12345, :format => "json" }
-    assert_response :unauthorized
-
-    basic_authorization user.email, "test"
-
-    delete :destroy, :params => { :id => 12345, :format => "json" }
-    assert_response :forbidden
-
-    basic_authorization moderator_user.email, "test"
-
-    delete :destroy, :params => { :id => 12345, :format => "json" }
-    assert_response :not_found
-
-    hidden_note_with_comment = create(:note_with_comments, :status => "hidden")
-
-    delete :destroy, :params => { :id => hidden_note_with_comment.id, :format => "json" }
-    assert_response :gone
-  end
-
-  def test_index_success
-    position = (1.1 * GeoRecord::SCALE).to_i
-    create(:note_with_comments, :latitude => position, :longitude => position)
-    create(:note_with_comments, :latitude => position, :longitude => position)
-
-    get :index, :params => { :bbox => "1,1,1.2,1.2", :format => "rss" }
-    assert_response :success
-    assert_equal "application/rss+xml", @response.content_type
-    assert_select "rss", :count => 1 do
-      assert_select "channel", :count => 1 do
-        assert_select "item", :count => 2
-      end
-    end
-
-    get :index, :params => { :bbox => "1,1,1.2,1.2", :format => "json" }
-    assert_response :success
-    assert_equal "application/json", @response.content_type
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "FeatureCollection", js["type"]
-    assert_equal 2, js["features"].count
-
-    get :index, :params => { :bbox => "1,1,1.2,1.2", :format => "xml" }
-    assert_response :success
-    assert_equal "application/xml", @response.content_type
-    assert_select "osm", :count => 1 do
-      assert_select "note", :count => 2
-    end
-
-    get :index, :params => { :bbox => "1,1,1.2,1.2", :format => "gpx" }
-    assert_response :success
-    assert_equal "application/gpx+xml", @response.content_type
-    assert_select "gpx", :count => 1 do
-      assert_select "wpt", :count => 2
-    end
-  end
-
-  def test_index_limit
-    position = (1.1 * GeoRecord::SCALE).to_i
-    create(:note_with_comments, :latitude => position, :longitude => position)
-    create(:note_with_comments, :latitude => position, :longitude => position)
-
-    get :index, :params => { :bbox => "1,1,1.2,1.2", :limit => 1, :format => "rss" }
-    assert_response :success
-    assert_equal "application/rss+xml", @response.content_type
-    assert_select "rss", :count => 1 do
-      assert_select "channel", :count => 1 do
-        assert_select "item", :count => 1
-      end
-    end
-
-    get :index, :params => { :bbox => "1,1,1.2,1.2", :limit => 1, :format => "json" }
-    assert_response :success
-    assert_equal "application/json", @response.content_type
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "FeatureCollection", js["type"]
-    assert_equal 1, js["features"].count
-
-    get :index, :params => { :bbox => "1,1,1.2,1.2", :limit => 1, :format => "xml" }
-    assert_response :success
-    assert_equal "application/xml", @response.content_type
-    assert_select "osm", :count => 1 do
-      assert_select "note", :count => 1
-    end
-
-    get :index, :params => { :bbox => "1,1,1.2,1.2", :limit => 1, :format => "gpx" }
-    assert_response :success
-    assert_equal "application/gpx+xml", @response.content_type
-    assert_select "gpx", :count => 1 do
-      assert_select "wpt", :count => 1
-    end
-  end
-
-  def test_index_empty_area
-    get :index, :params => { :bbox => "5,5,5.1,5.1", :format => "rss" }
-    assert_response :success
-    assert_equal "application/rss+xml", @response.content_type
-    assert_select "rss", :count => 1 do
-      assert_select "channel", :count => 1 do
-        assert_select "item", :count => 0
-      end
-    end
-
-    get :index, :params => { :bbox => "5,5,5.1,5.1", :format => "json" }
-    assert_response :success
-    assert_equal "application/json", @response.content_type
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "FeatureCollection", js["type"]
-    assert_equal 0, js["features"].count
-
-    get :index, :params => { :bbox => "5,5,5.1,5.1", :format => "xml" }
-    assert_response :success
-    assert_equal "application/xml", @response.content_type
-    assert_select "osm", :count => 1 do
-      assert_select "note", :count => 0
-    end
-
-    get :index, :params => { :bbox => "5,5,5.1,5.1", :format => "gpx" }
-    assert_response :success
-    assert_equal "application/gpx+xml", @response.content_type
-    assert_select "gpx", :count => 1 do
-      assert_select "wpt", :count => 0
-    end
-  end
-
-  def test_index_large_area
-    get :index, :params => { :bbox => "-2.5,-2.5,2.5,2.5", :format => :json }
-    assert_response :success
-    assert_equal "application/json", @response.content_type
-
-    get :index, :params => { :l => "-2.5", :b => "-2.5", :r => "2.5", :t => "2.5", :format => :json }
-    assert_response :success
-    assert_equal "application/json", @response.content_type
-
-    get :index, :params => { :bbox => "-10,-10,12,12", :format => :json }
-    assert_response :bad_request
-    assert_equal "application/json", @response.content_type
-
-    get :index, :params => { :l => "-10", :b => "-10", :r => "12", :t => "12", :format => :json }
-    assert_response :bad_request
-    assert_equal "application/json", @response.content_type
-  end
-
-  def test_index_closed
-    create(:note_with_comments, :status => "closed", :closed_at => Time.now - 5.days)
-    create(:note_with_comments, :status => "closed", :closed_at => Time.now - 100.days)
-    create(:note_with_comments, :status => "hidden")
-    create(:note_with_comments)
-
-    # Open notes + closed in last 7 days
-    get :index, :params => { :bbox => "1,1,1.7,1.7", :closed => "7", :format => "json" }
-    assert_response :success
-    assert_equal "application/json", @response.content_type
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "FeatureCollection", js["type"]
-    assert_equal 2, js["features"].count
-
-    # Only open notes
-    get :index, :params => { :bbox => "1,1,1.7,1.7", :closed => "0", :format => "json" }
-    assert_response :success
-    assert_equal "application/json", @response.content_type
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "FeatureCollection", js["type"]
-    assert_equal 1, js["features"].count
-
-    # Open notes + all closed notes
-    get :index, :params => { :bbox => "1,1,1.7,1.7", :closed => "-1", :format => "json" }
-    assert_response :success
-    assert_equal "application/json", @response.content_type
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "FeatureCollection", js["type"]
-    assert_equal 3, js["features"].count
-  end
-
-  def test_index_bad_params
-    get :index, :params => { :bbox => "-2.5,-2.5,2.5" }
-    assert_response :bad_request
-
-    get :index, :params => { :bbox => "-2.5,-2.5,2.5,2.5,2.5" }
-    assert_response :bad_request
-
-    get :index, :params => { :b => "-2.5", :r => "2.5", :t => "2.5" }
-    assert_response :bad_request
-
-    get :index, :params => { :l => "-2.5", :r => "2.5", :t => "2.5" }
-    assert_response :bad_request
-
-    get :index, :params => { :l => "-2.5", :b => "-2.5", :t => "2.5" }
-    assert_response :bad_request
-
-    get :index, :params => { :l => "-2.5", :b => "-2.5", :r => "2.5" }
-    assert_response :bad_request
-
-    get :index, :params => { :bbox => "1,1,1.7,1.7", :limit => "0", :format => "json" }
-    assert_response :bad_request
-
-    get :index, :params => { :bbox => "1,1,1.7,1.7", :limit => "10001", :format => "json" }
-    assert_response :bad_request
-  end
-
-  def test_search_success
-    create(:note_with_comments)
-
-    get :search, :params => { :q => "note comment", :format => "xml" }
-    assert_response :success
-    assert_equal "application/xml", @response.content_type
-    assert_select "osm", :count => 1 do
-      assert_select "note", :count => 1
-    end
-
-    get :search, :params => { :q => "note comment", :format => "json" }
-    assert_response :success
-    assert_equal "application/json", @response.content_type
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "FeatureCollection", js["type"]
-    assert_equal 1, js["features"].count
-
-    get :search, :params => { :q => "note comment", :format => "rss" }
-    assert_response :success
-    assert_equal "application/rss+xml", @response.content_type
-    assert_select "rss", :count => 1 do
-      assert_select "channel", :count => 1 do
-        assert_select "item", :count => 1
-      end
-    end
-
-    get :search, :params => { :q => "note comment", :format => "gpx" }
-    assert_response :success
-    assert_equal "application/gpx+xml", @response.content_type
-    assert_select "gpx", :count => 1 do
-      assert_select "wpt", :count => 1
-    end
-  end
-
-  def test_search_by_display_name_success
-    user = create(:user)
-
-    create(:note) do |note|
-      create(:note_comment, :note => note, :author => user)
-    end
-
-    get :search, :params => { :display_name => user.display_name, :format => "xml" }
-    assert_response :success
-    assert_equal "application/xml", @response.content_type
-    assert_select "osm", :count => 1 do
-      assert_select "note", :count => 1
-    end
-
-    get :search, :params => { :display_name => user.display_name, :format => "json" }
-    assert_response :success
-    assert_equal "application/json", @response.content_type
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "FeatureCollection", js["type"]
-    assert_equal 1, js["features"].count
-
-    get :search, :params => { :display_name => user.display_name, :format => "rss" }
-    assert_response :success
-    assert_equal "application/rss+xml", @response.content_type
-    assert_select "rss", :count => 1 do
-      assert_select "channel", :count => 1 do
-        assert_select "item", :count => 1
-      end
-    end
-
-    get :search, :params => { :display_name => user.display_name, :format => "gpx" }
-    assert_response :success
-    assert_equal "application/gpx+xml", @response.content_type
-    assert_select "gpx", :count => 1 do
-      assert_select "wpt", :count => 1
-    end
-  end
-
-  def test_search_by_user_success
-    user = create(:user)
-
-    create(:note) do |note|
-      create(:note_comment, :note => note, :author => user)
-    end
-
-    get :search, :params => { :user => user.id, :format => "xml" }
-    assert_response :success
-    assert_equal "application/xml", @response.content_type
-    assert_select "osm", :count => 1 do
-      assert_select "note", :count => 1
-    end
-
-    get :search, :params => { :user => user.id, :format => "json" }
-    assert_response :success
-    assert_equal "application/json", @response.content_type
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "FeatureCollection", js["type"]
-    assert_equal 1, js["features"].count
-
-    get :search, :params => { :user => user.id, :format => "rss" }
-    assert_response :success
-    assert_equal "application/rss+xml", @response.content_type
-    assert_select "rss", :count => 1 do
-      assert_select "channel", :count => 1 do
-        assert_select "item", :count => 1
-      end
-    end
-
-    get :search, :params => { :user => user.id, :format => "gpx" }
-    assert_response :success
-    assert_equal "application/gpx+xml", @response.content_type
-    assert_select "gpx", :count => 1 do
-      assert_select "wpt", :count => 1
-    end
-  end
-
-  def test_search_no_match
-    create(:note_with_comments)
-
-    get :search, :params => { :q => "no match", :format => "xml" }
-    assert_response :success
-    assert_equal "application/xml", @response.content_type
-    assert_select "osm", :count => 1 do
-      assert_select "note", :count => 0
-    end
-
-    get :search, :params => { :q => "no match", :format => "json" }
-    assert_response :success
-    assert_equal "application/json", @response.content_type
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "FeatureCollection", js["type"]
-    assert_equal 0, js["features"].count
-
-    get :search, :params => { :q => "no match", :format => "rss" }
-    assert_response :success
-    assert_equal "application/rss+xml", @response.content_type
-    assert_select "rss", :count => 1 do
-      assert_select "channel", :count => 1 do
-        assert_select "item", :count => 0
-      end
-    end
-
-    get :search, :params => { :q => "no match", :format => "gpx" }
-    assert_response :success
-    assert_equal "application/gpx+xml", @response.content_type
-    assert_select "gpx", :count => 1 do
-      assert_select "wpt", :count => 0
-    end
-  end
-
-  def test_search_by_time_no_match
-    create(:note_with_comments)
-
-    get :search, :params => { :from => "01.01.2010", :to => "01.10.2010", :format => "xml" }
-    assert_response :success
-    assert_equal "application/xml", @response.content_type
-    assert_select "osm", :count => 1 do
-      assert_select "note", :count => 0
-    end
-
-    get :search, :params => { :from => "01.01.2010", :to => "01.10.2010", :format => "json" }
-    assert_response :success
-    assert_equal "application/json", @response.content_type
-    js = ActiveSupport::JSON.decode(@response.body)
-    assert_not_nil js
-    assert_equal "FeatureCollection", js["type"]
-    assert_equal 0, js["features"].count
-
-    get :search, :params => { :from => "01.01.2010", :to => "01.10.2010", :format => "rss" }
-    assert_response :success
-    assert_equal "application/rss+xml", @response.content_type
-    assert_select "rss", :count => 1 do
-      assert_select "channel", :count => 1 do
-        assert_select "item", :count => 0
-      end
-    end
-
-    get :search, :params => { :from => "01.01.2010", :to => "01.10.2010", :format => "gpx" }
-    assert_response :success
-    assert_equal "application/gpx+xml", @response.content_type
-    assert_select "gpx", :count => 1 do
-      assert_select "wpt", :count => 0
-    end
-  end
-
-  def test_search_bad_params
-    get :search, :params => { :q => "no match", :limit => "0", :format => "json" }
-    assert_response :bad_request
-
-    get :search, :params => { :q => "no match", :limit => "10001", :format => "json" }
-    assert_response :bad_request
-
-    get :search, :params => { :display_name => "non-existent" }
-    assert_response :bad_request
-
-    get :search, :params => { :user => "-1" }
-    assert_response :bad_request
-
-    get :search, :params => { :from => "wrong-date", :to => "wrong-date" }
-    assert_response :bad_request
-
-    get :search, :params => { :from => "01.01.2010", :to => "2010.01.2010" }
-    assert_response :bad_request
-  end
-
-  def test_feed_success
-    position = (1.1 * GeoRecord::SCALE).to_i
-    create(:note_with_comments, :latitude => position, :longitude => position)
-    create(:note_with_comments, :latitude => position, :longitude => position)
-    position = (1.5 * GeoRecord::SCALE).to_i
-    create(:note_with_comments, :latitude => position, :longitude => position)
-    create(:note_with_comments, :latitude => position, :longitude => position)
-
-    get :feed, :params => { :format => "rss" }
-    assert_response :success
-    assert_equal "application/rss+xml", @response.content_type
-    assert_select "rss", :count => 1 do
-      assert_select "channel", :count => 1 do
-        assert_select "item", :count => 4
-      end
-    end
-
-    get :feed, :params => { :bbox => "1,1,1.2,1.2", :format => "rss" }
-    assert_response :success
-    assert_equal "application/rss+xml", @response.content_type
-    assert_select "rss", :count => 1 do
-      assert_select "channel", :count => 1 do
-        assert_select "item", :count => 2
-      end
-    end
-  end
-
-  def test_feed_fail
-    get :feed, :params => { :bbox => "1,1,1.2", :format => "rss" }
-    assert_response :bad_request
-
-    get :feed, :params => { :bbox => "1,1,1.2,1.2,1.2", :format => "rss" }
-    assert_response :bad_request
-
-    get :feed, :params => { :bbox => "1,1,1.2,1.2", :limit => "0", :format => "rss" }
-    assert_response :bad_request
-
-    get :feed, :params => { :bbox => "1,1,1.2,1.2", :limit => "10001", :format => "rss" }
-    assert_response :bad_request
-  end
-
   def test_mine_success
     first_user = create(:user)
     second_user = create(:user)
   def test_mine_success
     first_user = create(:user)
     second_user = create(:user)