Permalink
Browse files

Implement v2 of the API,

               renaming name to title for projects
  • Loading branch information...
1 parent 6fc1254 commit bd5e7a3617853eebce7134e9c3aaba96a801415a @radar radar committed Aug 15, 2012
@@ -0,0 +1,36 @@
+class Api::V2::BaseController < ActionController::Base
+ respond_to :json, :xml
+
+ before_filter :authenticate_user
+ before_filter :authorize_admin!, :except => [:index, :show]
+ before_filter :check_rate_limit
+
+ private
+ def authenticate_user
+ @current_user = User.find_by_authentication_token(params[:token])
+ unless @current_user
+ respond_with({:error => "Token is invalid." })
+ end
+ end
+
+ def current_user
+ @current_user
+ end
+
+ def check_rate_limit
+ if @current_user.request_count > 100
+ error = { :error => "Rate limit exceeded." }
+ respond_with(error, :status => 403)
+ else
+ @current_user.increment!(:request_count)
+ end
+ end
+
+ def authorize_admin!
+ if !@current_user.admin?
+ error = { :error => "You must be an admin to do that." }
+ warden.custom_failure!
+ render params[:format].to_sym => error, :status => 401
+ end
+ end
+end
@@ -0,0 +1,41 @@
+class Api::V2::ProjectsController < Api::V2::BaseController
+ before_filter :find_project, :only => [:show, :update, :destroy]
+
+ def index
+ projects = Project.for(current_user)
+ respond_with(projects, :except => :name, :methods => :title)
+ end
+
+ def create
+ project = Project.new(params[:project])
+ if project.save
+ respond_with(project, :location => api_v2_project_path(project))
+ else
+ respond_with(project)
+ end
+ end
+
+ def show
+ respond_with(@project, :methods => "last_ticket")
+ end
+
+ def update
+ @project.update_attributes(params[:project])
+ respond_with(@project)
+ end
+
+ def destroy
+ @project.destroy
+ respond_with(@project)
+ end
+
+ private
+
+ def find_project
+ @project = Project.for(current_user).find(params[:id])
+ rescue ActiveRecord::RecordNotFound
+ error = { :error => "The project you were looking for " +
+ "could not be found."}
+ respond_with(error, :status => 404)
+ end
+end
@@ -0,0 +1,16 @@
+class Api::V2::TicketsController < Api::V2::BaseController
+ before_filter :find_project
+
+ def index
+ respond_with(@project.tickets)
+ end
+
+ private
+ def find_project
+ @project = Project.for(current_user).find(params[:project_id])
+ rescue ActiveRecord::RecordNotFound
+ error = { :error => "The project you were looking for" +
+ " could not be found."}
+ respond_with(error, :status => 404)
+ end
+end
@@ -18,4 +18,8 @@ def self.for(user)
def last_ticket
tickets.last
end
+
+ def title
+ name
+ end
end
View
@@ -5,6 +5,12 @@
resources :tickets
end
end
+
+ namespace :v2 do
+ resources :projects do
+ resources :tickets
+ end
+ end
end
devise_for :users, :controllers => { :registrations => "registrations" }
@@ -132,10 +132,10 @@
user.save
end
- let(:url) { "/api/v1/projects/#{pproject.id}" }
+ let(:url) { "/api/v1/projects/#{project.id}" }
it "JSON" do
delete "#{url}.json", :token => token
- last_response.status.should eql(200)
+ last_response.status.should eql(204)
end
end
end
@@ -0,0 +1,11 @@
+require "spec_helper"
+
+describe "API errors", :type => :api do
+
+ it "making a request with no token" do
+ get "/api/v1/projects.json", :token => ""
+ error = { :error => "Token is invalid." }
+ last_response.body.should eql(error.to_json)
+ end
+
+end
@@ -0,0 +1,30 @@
+require "spec_helper"
+
+describe "Project API errors", :type => :api do
+ context "standard users" do
+ let(:user) { Factory(:user) }
+
+ it "cannot create projects" do
+ post "/api/v1/projects.json",
+ :token => user.authentication_token,
+ :project => {
+ :name => "Ticketee"
+ }
+ error = { :error => "You must be an admin to do that." }
+ last_response.body.should eql(error.to_json)
+ last_response.status.should eql(401)
+ Project.find_by_name("Ticketee").should be_nil
+ end
+
+ it "cannot view projects they do not have access to" do
+ project = Factory(:project)
+
+ get "/api/v1/projects/#{project.id}.json",
+ :token => user.authentication_token
+ error = { :error => "The project you were looking for" +
+ " could not be found." }
+ last_response.status.should eql(404)
+ last_response.body.should eql(error.to_json)
+ end
+ end
+end
@@ -0,0 +1,145 @@
+require "spec_helper"
+
+describe "/api/v2/projects", :type => :api do
+ let!(:user) { Factory(:user) }
+ let!(:token) { user.authentication_token }
+ let!(:project) { Factory(:project) }
+
+ before do
+ user.permissions.create!(:action => "view", :thing => project)
+ end
+
+ context "projects viewable by this user" do
+
+ before do
+ Factory(:project, :name => "Access Denied")
+ end
+
+ let(:url) { "/api/v2/projects" }
+ let(:options) { { :except => :name, :methods => :title } }
+ it "JSON" do
+ get "#{url}.json", :token => token
+
+ body = Project.for(user).to_json(options)
+
+ last_response.body.should eql(body)
+ last_response.status.should eql(200)
+
+ projects = JSON.parse(last_response.body)
+ projects.any? do |p|
+ p["title"] == project.title
+ end.should be_true
+
+ projects.all? do |p|
+ p["name"].blank?
+ end.should be_true
+ end
+
+ it "XML" do
+ get "#{url}.xml", :token => token
+
+ body = Project.for(user).to_xml(options)
+ last_response.body.should eql(body)
+ projects = Nokogiri::XML(last_response.body)
+ projects.css("project title").text.should eql(project.title)
+ projects.css("project name").text.should eql("")
+ end
+ end
+
+ context "creating a project" do
+ before do
+ user.admin = true
+ user.save
+ end
+
+ let(:url) { "/api/v2/projects" }
+
+ it "successful JSON" do
+ post "#{url}.json", :token => token,
+ :project => {
+ :name => "Inspector"
+ }
+
+ project = Project.find_by_name!("Inspector")
+ route = "/api/v2/projects/#{project.id}"
+
+ last_response.status.should eql(201)
+ last_response.headers["Location"].should eql(route)
+ last_response.body.should eql(project.to_json)
+ end
+
+ it "unsuccessful JSON" do
+ post "#{url}.json", :token => token,
+ :project => {}
+ last_response.status.should eql(422)
+ errors = {"errors" => {
+ "name" => ["can't be blank"]
+ }}.to_json
+ last_response.body.should eql(errors)
+ end
+ end
+
+ context "show" do
+ let(:url) { "/api/v2/projects/#{project.id}"}
+
+ before do
+ Factory(:ticket, :project => project)
+ end
+
+ it "JSON" do
+ get "#{url}.json", :token => token
+ project_json = project.to_json(:methods => "last_ticket")
+ last_response.body.should eql(project_json)
+ last_response.status.should eql(200)
+
+ project_response = JSON.parse(last_response.body)
+
+ ticket_title = project_response["last_ticket"]["title"]
+ ticket_title.should_not be_blank
+ end
+ end
+
+ context "updating a project" do
+ before do
+ user.admin = true
+ user.save
+ end
+
+ let(:url) { "/api/v2/projects/#{project.id}" }
+ it "successful JSON" do
+ put "#{url}.json", :token => token,
+ :project => {
+ :name => "Not Ticketee"
+ }
+
+ last_response.status.should eql(204)
+ last_response.body.should eql("")
+
+ project.reload
+ project.name.should eql("Not Ticketee")
+ end
+
+ it "unsuccessful JSON" do
+ put "#{url}.json", :token => token,
+ :project => {
+ :name => ""
+ }
+ last_response.status.should eql(422)
+ errors = { :errors => { :name => ["can't be blank"] } }
+ last_response.body.should eql(errors.to_json)
+ end
+ end
+
+ context "deleting a project" do
+ before do
+ user.admin = true
+ user.save
+ end
+
+ let(:url) { "/api/v2/projects/#{project.id}" }
+ it "JSON" do
+ delete "#{url}.json", :token => token
+ last_response.status.should eql(204)
+ end
+ end
+end
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe "rate limiting", :type => :api do
+ let(:user) { Factory(:user) }
+
+ it "counts the user's requests" do
+ user.request_count.should eql(0)
+ get '/api/v1/projects.json', :token => user.authentication_token
+ user.reload
+ user.request_count.should eql(1)
+ end
+
+ it "stops a user if they have exceeded the limit" do
+ user.update_attribute(:request_count, 101)
+ get '/api/v1/projects.json', :token => user.authentication_token
+ error = { :error => "Rate limit exceeded." }
+ last_response.status.should eql(403)
+ last_response.body.should eql(error.to_json)
+ end
+end
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe "/api/v1/tickets", :type => :api do
+ let!(:project) { Factory(:project, :name => "Ticketee") }
+ let!(:user) { Factory(:user) }
+
+ before do
+ user.permissions.create!(:action => "view",
+ :thing => project)
+ end
+
+ let(:token) { user.authentication_token }
+ let(:url) { "/api/v1/projects/#{project.id}/tickets" }
+
+ it "XML" do
+ get "#{url}.xml", :token => token
+ last_response.body.should eql(project.tickets.to_xml)
+ end
+
+ it "JSON" do
+ get "#{url}.json", :token => token
+ last_response.body.should eql(project.tickets.to_json)
+ end
+end
+

0 comments on commit bd5e7a3

Please sign in to comment.