Commit 100df3575f11f2c551eb1e950440e3f648f19b98

Authored by Marcos Pereira
1 parent ea20a3dc

API improvements

  - changes pagination to use api-pagination gem
  - new endpoints to boxes, activities, and profiles
  - new exposed attributes for some entities
  - other minor changes

  Signed-off-by: Leandro Nunes dos Santos <leandronunes@gmail.com>
  Signed-off-by: Marcos Ronaldo <marcos.rpj2@gmail.com>
  Signed-off-by: Michel Felipe de Oliveira Ferreira <michel.ferreira@serpro.gov.br>
  Signed-off-by: Victor Costa <vfcosta@gmail.com>
Gemfile
... ... @@ -34,10 +34,11 @@ gem &#39;slim&#39;
34 34  
35 35 # API dependencies
36 36 gem 'grape', '~> 0.12'
37   -gem 'grape-entity'
  37 +gem 'grape-entity', '0.4.8'
38 38 gem 'grape_logging'
39 39 gem 'rack-cors'
40 40 gem 'rack-contrib'
  41 +gem 'api-pagination', '~> 4.1.1'
41 42  
42 43 # asset pipeline
43 44 gem 'uglifier', '>= 1.0.3'
... ...
app/models/profile.rb
... ... @@ -388,6 +388,10 @@ class Profile &lt; ActiveRecord::Base
388 388 !profiles.exists?
389 389 end
390 390  
  391 + def self.visible_for_person(person)
  392 + self.all
  393 + end
  394 +
391 395 validates_presence_of :identifier, :name
392 396 validates_length_of :nickname, :maximum => 16, :allow_nil => true
393 397 validate :valid_template
... ...
lib/noosfero/api/api.rb
... ... @@ -52,6 +52,9 @@ module Noosfero
52 52 mount V1::Environments
53 53 mount V1::Search
54 54 mount V1::Contacts
  55 + mount V1::Boxes
  56 + mount V1::Profiles
  57 + mount V1::Activities
55 58  
56 59 mount Session
57 60  
... ...
lib/noosfero/api/entities.rb
... ... @@ -79,6 +79,18 @@ module Noosfero
79 79 expose :parent_id
80 80 end
81 81  
  82 + class Block < Entity
  83 + root 'blocks', 'block'
  84 + expose :id, :type, :settings, :position, :enabled
  85 + expose :mirror, :mirror_block_id, :title
  86 + end
  87 +
  88 + class Box < Entity
  89 + root 'boxes', 'box'
  90 + expose :id, :position
  91 + expose :blocks, :using => Block
  92 + end
  93 +
82 94 class Profile < Entity
83 95 expose :identifier, :name, :id
84 96 expose :created_at, :format_with => :timestamp
... ... @@ -99,6 +111,7 @@ module Noosfero
99 111 end
100 112 expose :image, :using => Image
101 113 expose :region, :using => Region
  114 + expose :type
102 115 end
103 116  
104 117 class UserBasic < Entity
... ... @@ -109,6 +122,16 @@ module Noosfero
109 122 class Person < Profile
110 123 root 'people', 'person'
111 124 expose :user, :using => UserBasic, documentation: {type: 'User', desc: 'The user data of a person' }
  125 + expose :vote_count
  126 + expose :comments_count do |person, options|
  127 + person.comments.count
  128 + end
  129 + expose :following_articles_count do |person, options|
  130 + person.following_articles.count
  131 + end
  132 + expose :articles_count do |person, options|
  133 + person.articles.count
  134 + end
112 135 end
113 136  
114 137 class Enterprise < Profile
... ... @@ -149,6 +172,8 @@ module Noosfero
149 172 expose :slug, :documentation => {:type => "String", :desc => "Trimmed and parsed name of a article"}
150 173 expose :path
151 174 expose :followers_count
  175 + expose :votes_count
  176 + expose :comments_count
152 177 end
153 178  
154 179 class Article < ArticleBase
... ... @@ -189,7 +214,7 @@ module Noosfero
189 214  
190 215 class UserLogin < User
191 216 root 'users', 'user'
192   - expose :private_token, documentation: {type: 'String', desc: 'A valid authentication code for post/delete api actions'}
  217 + expose :private_token, documentation: {type: 'String', desc: 'A valid authentication code for post/delete api actions'}, if: lambda {|object, options| object.activated? }
193 218 end
194 219  
195 220 class Task < Entity
... ... @@ -207,7 +232,15 @@ module Noosfero
207 232 expose :name
208 233 end
209 234  
210   -
  235 + class Activity < Entity
  236 + root 'activities', 'activity'
  237 + expose :id, :params, :verb, :created_at, :updated_at, :comments_count, :visible
  238 + expose :user, :using => Profile
  239 + expose :target do |activity, opts|
  240 + type_map = {Profile => ::Profile, ArticleBase => ::Article}.find {|h| activity.target.kind_of?(h.last)}
  241 + type_map.first.represent(activity.target) unless type_map.nil?
  242 + end
  243 + end
211 244 end
212 245 end
213 246 end
... ...
lib/noosfero/api/entity.rb
... ... @@ -22,18 +22,4 @@ class Noosfero::API::Entity &lt; Grape::Entity
22 22 end
23 23 end
24 24  
25   - def self.fields_condition(fields)
26   - lambda do |object, options|
27   - return true if options[:fields].blank?
28   - fields.map { |field| options[:fields].include?(field.to_s)}.grep(true).present?
29   - end
30   - end
31   -
32   - def self.expose(*args, &block)
33   - hash = args.last.is_a?(Hash) ? args.pop : {}
34   - hash.merge!({:if => fields_condition(args)}) if hash[:if].blank?
35   - args << hash
36   - super
37   - end
38   -
39 25 end
... ...
lib/noosfero/api/helpers.rb
... ... @@ -5,7 +5,7 @@ require_relative &#39;../../find_by_contents&#39;
5 5 module API
6 6 module APIHelpers
7 7 PRIVATE_TOKEN_PARAM = :private_token
8   - DEFAULT_ALLOWED_PARAMETERS = [:parent_id, :from, :until, :content_type, :author_id]
  8 + DEFAULT_ALLOWED_PARAMETERS = [:parent_id, :from, :until, :content_type, :author_id, :identifier]
9 9  
10 10 include SanitizeParams
11 11 include Noosfero::Plugin::HotSpot
... ... @@ -38,6 +38,20 @@ require_relative &#39;../../find_by_contents&#39;
38 38 @environment
39 39 end
40 40  
  41 + def present_partial(model, options)
  42 + if(params[:fields].present?)
  43 + begin
  44 + fields = JSON.parse(params[:fields])
  45 + if fields.present?
  46 + options.merge!(fields.symbolize_keys.slice(:only, :except))
  47 + end
  48 + rescue
  49 + options[:only] = Array.wrap(params[:fields])
  50 + end
  51 + end
  52 + present model, options
  53 + end
  54 +
41 55 include FindByContents
42 56  
43 57 ####################################################################
... ... @@ -87,7 +101,7 @@ require_relative &#39;../../find_by_contents&#39;
87 101 def post_article(asset, params)
88 102 return forbidden! unless current_person.can_post_content?(asset)
89 103  
90   - klass_type= params[:content_type].nil? ? 'TinyMceArticle' : params[:content_type]
  104 + klass_type= params[:content_type].nil? ? TinyMceArticle.name : params[:content_type]
91 105 return forbidden! unless ARTICLE_TYPES.include?(klass_type)
92 106  
93 107 article = klass_type.constantize.new(params[:article])
... ... @@ -98,12 +112,12 @@ require_relative &#39;../../find_by_contents&#39;
98 112 if !article.save
99 113 render_api_errors!(article.errors.full_messages)
100 114 end
101   - present article, :with => Entities::Article, :fields => params[:fields]
  115 + present_partial article, :with => Entities::Article
102 116 end
103 117  
104 118 def present_article(asset)
105 119 article = find_article(asset.articles, params[:id])
106   - present article, :with => Entities::Article, :fields => params[:fields]
  120 + present_partial article, :with => Entities::Article
107 121 end
108 122  
109 123 def present_articles_for_asset(asset, method = 'articles')
... ... @@ -112,7 +126,7 @@ require_relative &#39;../../find_by_contents&#39;
112 126 end
113 127  
114 128 def present_articles(articles)
115   - present articles, :with => Entities::Article, :fields => params[:fields]
  129 + present_partial paginate(articles), :with => Entities::Article
116 130 end
117 131  
118 132 def find_articles(asset, method = 'articles')
... ... @@ -142,19 +156,19 @@ require_relative &#39;../../find_by_contents&#39;
142 156 if !task.save
143 157 render_api_errors!(task.errors.full_messages)
144 158 end
145   - present task, :with => Entities::Task, :fields => params[:fields]
  159 + present_partial task, :with => Entities::Task
146 160 end
147 161  
148 162 def present_task(asset)
149 163 task = find_task(asset, params[:id])
150   - present task, :with => Entities::Task, :fields => params[:fields]
  164 + present_partial task, :with => Entities::Task
151 165 end
152 166  
153 167 def present_tasks(asset)
154 168 tasks = select_filtered_collection_of(asset, 'tasks', params)
155 169 tasks = tasks.select {|t| current_person.has_permission?(t.permission, asset)}
156 170 return forbidden! if tasks.empty? && !current_person.has_permission?(:perform_task, asset)
157   - present tasks, :with => Entities::Task, :fields => params[:fields]
  171 + present_partial tasks, :with => Entities::Task
158 172 end
159 173  
160 174 def make_conditions_with_parameter(params = {})
... ... @@ -194,15 +208,6 @@ require_relative &#39;../../find_by_contents&#39;
194 208 return order
195 209 end
196 210  
197   - def make_page_number_with_parameters(params)
198   - params[:page] || 1
199   - end
200   -
201   - def make_per_page_with_parameters(params)
202   - params[:per_page] ||= limit
203   - params[:per_page].to_i
204   - end
205   -
206 211 def make_timestamp_with_parameters_and_method(params, method)
207 212 timestamp = nil
208 213 if params[:timestamp]
... ... @@ -236,17 +241,17 @@ require_relative &#39;../../find_by_contents&#39;
236 241 def select_filtered_collection_of(object, method, params)
237 242 conditions = make_conditions_with_parameter(params)
238 243 order = make_order_with_parameters(object,method,params)
239   - page_number = make_page_number_with_parameters(params)
240   - per_page = make_per_page_with_parameters(params)
241 244 timestamp = make_timestamp_with_parameters_and_method(params, method)
242 245  
243 246 objects = object.send(method)
244 247 objects = by_reference(objects, params)
245 248 objects = by_categories(objects, params)
246 249  
247   - objects = objects.where(conditions).where(timestamp).page(page_number).per_page(per_page).reorder(order)
  250 + objects = objects.where(conditions).where(timestamp).reorder(order)
248 251  
249   - objects
  252 + params[:page] ||= 1
  253 + params[:per_page] ||= limit
  254 + paginate(objects)
250 255 end
251 256  
252 257 def authenticate!
... ...
lib/noosfero/api/session.rb
... ... @@ -24,6 +24,14 @@ module Noosfero
24 24 present user, :with => Entities::UserLogin, :current_person => current_person
25 25 end
26 26  
  27 + post "/login_from_cookie" do
  28 + return unauthorized! if cookies[:auth_token].blank?
  29 + user = User.where(remember_token: cookies[:auth_token]).first
  30 + return unauthorized! unless user && user.activated?
  31 + @current_user = user
  32 + present user, :with => Entities::UserLogin, :current_person => current_person
  33 + end
  34 +
27 35 # Create user.
28 36 #
29 37 # Parameters:
... ...
lib/noosfero/api/v1/activities.rb 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +module Noosfero
  2 + module API
  3 + module V1
  4 + class Activities < Grape::API
  5 + before { authenticate! }
  6 +
  7 + resource :profiles do
  8 +
  9 + get ':id/activities' do
  10 + profile = environment.profiles
  11 + profile = profile.visible_for_person(current_person) if profile.respond_to?(:visible_for_person)
  12 + profile = profile.find_by_id(params[:id])
  13 + activities = profile.activities.map(&:activity)
  14 + present activities, :with => Entities::Activity, :current_person => current_person
  15 + end
  16 + end
  17 + end
  18 + end
  19 + end
  20 +end
... ...
lib/noosfero/api/v1/articles.rb
... ... @@ -5,8 +5,11 @@ module Noosfero
5 5  
6 6 ARTICLE_TYPES = Article.descendants.map{|a| a.to_s}
7 7  
  8 + MAX_PER_PAGE = 50
  9 +
8 10 resource :articles do
9 11  
  12 + paginate max_per_page: MAX_PER_PAGE
10 13 # Collect articles
11 14 #
12 15 # Parameters:
... ... @@ -49,7 +52,7 @@ module Noosfero
49 52 article = environment.articles.find(params[:id])
50 53 return forbidden! unless article.allow_edit?(current_person)
51 54 article.update_attributes!(params[:article])
52   - present article, :with => Entities::Article, :fields => params[:fields]
  55 + present_partial article, :with => Entities::Article
53 56 end
54 57  
55 58 desc 'Report a abuse and/or violent content in a article by id' do
... ... @@ -93,7 +96,7 @@ module Noosfero
93 96 end
94 97 #FIXME refactor this method
95 98 get 'voted_by_me' do
96   - present_articles(current_person.votes.collect(&:voteable))
  99 + present_articles(current_person.votes.where(:voteable_type => 'Article').collect(&:voteable))
97 100 end
98 101  
99 102 desc 'Perform a vote on a article by id' do
... ... @@ -108,8 +111,12 @@ module Noosfero
108 111 # FIXME verify allowed values
109 112 render_api_error!('Vote value not allowed', 400) unless [-1, 1].include?(value)
110 113 article = find_article(environment.articles, params[:id])
111   - vote = Vote.new(:voteable => article, :voter => current_person, :vote => value)
112   - {:vote => vote.save}
  114 + begin
  115 + vote = Vote.new(:voteable => article, :voter => current_person, :vote => value)
  116 + {:vote => vote.save!}
  117 + rescue ActiveRecord::RecordInvalid => e
  118 + render_api_error!(e.message, 400)
  119 + end
113 120 end
114 121  
115 122 desc "Returns the total followers for the article" do
... ... @@ -123,6 +130,11 @@ module Noosfero
123 130 {:total_followers => total}
124 131 end
125 132  
  133 + desc "Return the articles followed by me"
  134 + get 'followed_by_me' do
  135 + present_articles_for_asset(current_person, 'following_articles')
  136 + end
  137 +
126 138 desc "Add a follower for the article" do
127 139 detail 'Add the current user identified by private token, like a follower of a article'
128 140 params Noosfero::API::Entities::UserLogin.documentation
... ... @@ -150,6 +162,7 @@ module Noosfero
150 162 named 'ArticleChildren'
151 163 end
152 164  
  165 + paginate per_page: MAX_PER_PAGE, max_per_page: MAX_PER_PAGE
153 166 get ':id/children' do
154 167 article = find_article(environment.articles, params[:id])
155 168  
... ... @@ -177,7 +190,7 @@ module Noosfero
177 190 article = find_article(environment.articles, params[:id])
178 191 child = find_article(article.children, params[:child_id])
179 192 child.hit
180   - present child, :with => Entities::Article, :fields => params[:fields]
  193 + present_partial child, :with => Entities::Article
181 194 end
182 195  
183 196 desc 'Suggest a article to another profile' do
... ... @@ -200,7 +213,7 @@ module Noosfero
200 213 unless suggest_article.save
201 214 render_api_errors!(suggest_article.article_object.errors.full_messages)
202 215 end
203   - present suggest_article, :with => Entities::Task, :fields => params[:fields]
  216 + present_partial suggest_article, :with => Entities::Task
204 217 end
205 218  
206 219 # Example Request:
... ... @@ -231,12 +244,21 @@ module Noosfero
231 244 if !article.save
232 245 render_api_errors!(article.errors.full_messages)
233 246 end
234   - present article, :with => Entities::Article, :fields => params[:fields]
  247 + present_partial article, :with => Entities::Article
235 248 end
236 249  
237 250 end
238 251  
239   - kinds = %w[community person enterprise]
  252 + resource :profiles do
  253 + get ':id/home_page' do
  254 + profiles = environment.profiles
  255 + profiles = profiles.visible_for_person(current_person)
  256 + profile = profiles.find_by_id(params[:id])
  257 + present_partial profile.home_page, :with => Entities::Article
  258 + end
  259 + end
  260 +
  261 + kinds = %w[profile community person enterprise]
240 262 kinds.each do |kind|
241 263 resource kind.pluralize.to_sym do
242 264 segment "/:#{kind}_id" do
... ... @@ -258,7 +280,7 @@ module Noosfero
258 280 article = forbidden!
259 281 end
260 282  
261   - present article, :with => Entities::Article, :fields => params[:fields]
  283 + present_partial article, :with => Entities::Article
262 284 else
263 285  
264 286 present_articles_for_asset(profile)
... ...
lib/noosfero/api/v1/boxes.rb 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +module Noosfero
  2 + module API
  3 + module V1
  4 +
  5 + class Boxes < Grape::API
  6 +
  7 + kinds = %w[profile community person enterprise]
  8 + kinds.each do |kind|
  9 +
  10 + resource kind.pluralize.to_sym do
  11 +
  12 + segment "/:#{kind}_id" do
  13 + resource :boxes do
  14 + get do
  15 + profile = environment.send(kind.pluralize).find(params["#{kind}_id"])
  16 + present profile.boxes, :with => Entities::Box
  17 + end
  18 + end
  19 + end
  20 +
  21 + end
  22 +
  23 + end
  24 + end
  25 +
  26 + end
  27 + end
  28 +end
... ...
lib/noosfero/api/v1/comments.rb
... ... @@ -31,7 +31,12 @@ module Noosfero
31 31 post ":id/comments" do
32 32 article = find_article(environment.articles, params[:id])
33 33 options = params.select { |key,v| !['id','private_token'].include?(key) }.merge(:author => current_person, :source => article)
34   - present Comment.create(options), :with => Entities::Comment, :current_person => current_person
  34 + begin
  35 + comment = Comment.create!(options)
  36 + rescue ActiveRecord::RecordInvalid => e
  37 + render_api_error!(e.message, 400)
  38 + end
  39 + present comment, :with => Entities::Comment, :current_person => current_person
35 40 end
36 41 end
37 42  
... ...
lib/noosfero/api/v1/people.rb
... ... @@ -4,9 +4,12 @@ module Noosfero
4 4 class People < Grape::API
5 5 before { authenticate! }
6 6  
  7 + MAX_PER_PAGE = 50
  8 +
7 9 desc 'API Root'
8 10  
9 11 resource :people do
  12 + paginate max_per_page: MAX_PER_PAGE
10 13  
11 14 # -- A note about privacy --
12 15 # We wold find people by location, but we must test if the related
... ... @@ -33,12 +36,12 @@ module Noosfero
33 36 get do
34 37 people = select_filtered_collection_of(environment, 'people', params)
35 38 people = people.visible_for_person(current_person)
36   - present people, :with => Entities::Person, :current_person => current_person
  39 + present_partial people, :with => Entities::Person, :current_person => current_person
37 40 end
38 41  
39 42 desc "Return the logged user information"
40 43 get "/me" do
41   - present current_person, :with => Entities::Person, :current_person => current_person
  44 + present_partial current_person, :with => Entities::Person, :current_person => current_person
42 45 end
43 46  
44 47 desc "Return the person information"
... ... @@ -105,6 +108,19 @@ module Noosfero
105 108 present output
106 109 end
107 110 end
  111 +
  112 + resource :profiles do
  113 + segment '/:profile_id' do
  114 + resource :members do
  115 + paginate max_per_page: MAX_PER_PAGE
  116 + get do
  117 + profile = environment.profiles.find_by_id(params[:profile_id])
  118 + members = select_filtered_collection_of(profile, 'members', params)
  119 + present members, :with => Entities::Person, :current_person => current_person
  120 + end
  121 + end
  122 + end
  123 + end
108 124 end
109 125 end
110 126 end
... ...
lib/noosfero/api/v1/profiles.rb 0 → 100644
... ... @@ -0,0 +1,26 @@
  1 +module Noosfero
  2 + module API
  3 + module V1
  4 + class Profiles < Grape::API
  5 + before { authenticate! }
  6 +
  7 + resource :profiles do
  8 +
  9 + get do
  10 + profiles = select_filtered_collection_of(environment, 'profiles', params)
  11 + profiles = profiles.visible_for_person(current_person)
  12 + profiles = profiles.by_location(params) # Must be the last. May return Exception obj.
  13 + present profiles, :with => Entities::Profile, :current_person => current_person
  14 + end
  15 +
  16 + get ':id' do
  17 + profiles = environment.profiles
  18 + profiles = profiles.visible_for_person(current_person)
  19 + profile = profiles.find_by_id(params[:id])
  20 + present profile, :with => Entities::Profile, :current_person => current_person
  21 + end
  22 + end
  23 + end
  24 + end
  25 + end
  26 +end
... ...
lib/noosfero/api/v1/search.rb
... ... @@ -5,6 +5,7 @@ module Noosfero
5 5  
6 6 resource :search do
7 7 resource :article do
  8 + paginate max_per_page: 200
8 9 get do
9 10 # Security checks
10 11 sanitize_params_hash(params)
... ... @@ -15,24 +16,19 @@ module Noosfero
15 16 profile = environment.profiles.find(params[:profile_id]) if params[:profile_id]
16 17 scope = profile.nil? ? environment.articles.is_public : profile.articles.is_public
17 18 scope = scope.where(:type => params[:type]) if params[:type] && !(params[:type] == 'Article')
18   - scope = scope.where(:parent_id => params[:parent_id]) if params[:parent_id].present?
  19 + scope = scope.where(make_conditions_with_parameter(params))
19 20 scope = scope.joins(:categories).where(:categories => {:id => params[:category_ids]}) if params[:category_ids].present?
  21 + scope = scope.where('articles.children_count > 0') if params[:has_children].present?
20 22 query = params[:query] || ""
21 23 order = "more_recent"
22 24  
23 25 options = {:filter => order, :template_id => params[:template_id]}
24 26  
25   - paginate_options = params.select{|k,v| [:page, :per_page].include?(k.to_sym)}.symbolize_keys
26   - paginate_options.each_pair{|k,v| v=v.to_i}
27   - paginate_options[:page]=1 if !paginate_options.keys.include?(:page)
28   -
29   - search_result = find_by_contents(asset, context, scope, query, paginate_options, options)
  27 + search_result = find_by_contents(asset, context, scope, query, {:page => 1}, options)
30 28  
31 29 articles = search_result[:results]
32 30  
33   - result = present_articles(articles)
34   -
35   - result
  31 + present_articles(articles)
36 32 end
37 33 end
38 34 end
... ...
lib/noosfero/api/v1/tasks.rb
... ... @@ -20,13 +20,13 @@ module Noosfero
20 20 get do
21 21 tasks = select_filtered_collection_of(environment, 'tasks', params)
22 22 tasks = tasks.select {|t| current_person.has_permission?(t.permission, environment)}
23   - present tasks, :with => Entities::Task, :fields => params[:fields]
  23 + present_partial tasks, :with => Entities::Task
24 24 end
25 25  
26 26 desc "Return the task id"
27 27 get ':id' do
28 28 task = find_task(environment, params[:id])
29   - present task, :with => Entities::Task, :fields => params[:fields]
  29 + present_partial task, :with => Entities::Task
30 30 end
31 31 end
32 32  
... ...
test/unit/api/activities_test.rb 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +require_relative 'test_helper'
  2 +
  3 +class ActivitiesTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + login_api
  7 + end
  8 +
  9 + should 'get activity from profile' do
  10 + person = fast_create(Person)
  11 + organization = fast_create(Organization)
  12 + assert_difference 'organization.activities_count' do
  13 + ActionTracker::Record.create! :verb => :leave_scrap, :user => person, :target => organization
  14 + organization.reload
  15 + end
  16 + get "/api/v1/profiles/#{organization.id}/activities?#{params.to_query}"
  17 + json = JSON.parse(last_response.body)
  18 + assert 1, json["activities"].count
  19 + assert_equal organization.activities.map(&:activity).first.id, json["activities"].first["id"]
  20 + end
  21 +
  22 +end
... ...
test/unit/api/articles_test.rb
... ... @@ -13,6 +13,16 @@ class ArticlesTest &lt; ActiveSupport::TestCase
13 13 assert_includes json["articles"].map { |a| a["id"] }, article.id
14 14 end
15 15  
  16 + should 'get profile homepage' do
  17 + article = fast_create(Article, :profile_id => user.person.id, :name => "Some thing")
  18 + person.home_page=article
  19 + person.save!
  20 +
  21 + get "/api/v1/profiles/#{person.id}/home_page?#{params.to_query}"
  22 + json = JSON.parse(last_response.body)
  23 + assert_equal article.id, json["article"]["id"]
  24 + end
  25 +
16 26 should 'not list forbidden article when listing articles' do
17 27 person = fast_create(Person)
18 28 article = fast_create(Article, :profile_id => person.id, :name => "Some thing", :published => false)
... ... @@ -74,6 +84,16 @@ class ArticlesTest &lt; ActiveSupport::TestCase
74 84 assert_equal 1, json['total_followers']
75 85 end
76 86  
  87 + should 'list articles followed by me' do
  88 + article1 = fast_create(Article, :profile_id => user.person.id, :name => "Some thing")
  89 + fast_create(Article, :profile_id => user.person.id, :name => "Some other thing")
  90 + article1.person_followers << @person
  91 + get "/api/v1/articles/followed_by_me?#{params.to_query}"
  92 + json = JSON.parse(last_response.body)
  93 + assert_equal [article1.id], json['articles'].map { |a| a['id'] }
  94 + end
  95 +
  96 +
77 97 should 'list article children' do
78 98 article = fast_create(Article, :profile_id => user.person.id, :name => "Some thing")
79 99 child1 = fast_create(Article, :parent_id => article.id, :profile_id => user.person.id, :name => "Some thing")
... ... @@ -148,6 +168,33 @@ class ArticlesTest &lt; ActiveSupport::TestCase
148 168 end
149 169 end
150 170  
  171 + should 'not perform a vote twice in same article' do
  172 + article = fast_create(Article, :profile_id => @person.id, :name => "Some thing")
  173 + @params[:value] = 1
  174 + ## Perform a vote twice in API should compute only one vote
  175 + post "/api/v1/articles/#{article.id}/vote?#{params.to_query}"
  176 + post "/api/v1/articles/#{article.id}/vote?#{params.to_query}"
  177 +
  178 + total = article.votes_total
  179 +
  180 + assert_equal 1, total
  181 + end
  182 +
  183 + should 'not perform a vote in favor and against a proposal' do
  184 + article = fast_create(Article, :profile_id => @person.id, :name => "Some thing")
  185 + @params[:value] = 1
  186 + ## Perform a vote in favor a proposal
  187 + post "/api/v1/articles/#{article.id}/vote?#{params.to_query}"
  188 + json = JSON.parse(last_response.body)
  189 + assert_equal 201, last_response.status
  190 + ## Perform a vote against a proposal
  191 + @params[:value] = -1
  192 + post "/api/v1/articles/#{article.id}/vote?#{params.to_query}"
  193 + json = JSON.parse(last_response.body)
  194 + ## The api should not allow to save this vote
  195 + assert_equal 400, last_response.status
  196 + end
  197 +
151 198 should "update body of article created by me" do
152 199 new_value = "Another body"
153 200 params[:article] = {:body => new_value}
... ... @@ -606,4 +653,15 @@ class ArticlesTest &lt; ActiveSupport::TestCase
606 653 assert_equal json['articles'].count, 2
607 654 end
608 655  
  656 + ARTICLE_ATTRIBUTES = %w(votes_count comments_count)
  657 +
  658 + ARTICLE_ATTRIBUTES.map do |attribute|
  659 +
  660 + define_method "test_should_expose_#{attribute}_attribute_in_article_enpoints" do
  661 + article = fast_create(Article, :profile_id => user.person.id, :name => "Some thing")
  662 + get "/api/v1/articles/#{article.id}?#{params.to_query}"
  663 + json = JSON.parse(last_response.body)
  664 + assert_not_nil json['article'][attribute]
  665 + end
  666 + end
609 667 end
... ...
test/unit/api/boxes_test.rb 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +require_relative 'test_helper'
  2 +
  3 +class BoxesTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + login_api
  7 + end
  8 +
  9 + kinds= %w[Profile Community Person Enterprise]
  10 + kinds.each do |kind|
  11 + should "get_boxes_from_#{kind.downcase.pluralize}" do
  12 + profile_obj = fast_create(kind.constantize)
  13 + box = fast_create(Box, :owner_id => profile_obj.id, :owner_type => "Profile")
  14 + get "/api/v1/#{kind.downcase.pluralize}/#{profile_obj.id}/boxes?#{params.to_query}"
  15 + json = JSON.parse(last_response.body)
  16 + assert_equal box.id, json["boxes"].first["id"]
  17 + end
  18 + end
  19 +
  20 +end
... ...
test/unit/api/helpers_test.rb
... ... @@ -25,6 +25,15 @@ class APIHelpersTest &lt; ActiveSupport::TestCase
25 25 assert_equal user, current_user
26 26 end
27 27  
  28 + should 'get the current user even with expired token' do
  29 + user = create_user('someuser')
  30 + user.generate_private_token!
  31 + user.private_token_generated_at = DateTime.now.prev_year
  32 + user.save
  33 + self.params = {:private_token => user.private_token}
  34 + assert_equal user, current_user
  35 + end
  36 +
28 37 should 'get the person of current user' do
29 38 user = create_user('someuser')
30 39 user.generate_private_token!
... ... @@ -192,6 +201,26 @@ class APIHelpersTest &lt; ActiveSupport::TestCase
192 201 filter_disabled_plugins_endpoints
193 202 end
194 203  
  204 + should 'not touch in options when no fields parameter is passed' do
  205 + model = mock
  206 + expects(:present).with(model, {})
  207 + present_partial(model, {})
  208 + end
  209 +
  210 + should 'fallback to array when fields parameter is not a json when calling present partial' do
  211 + model = mock
  212 + params[:fields] = 'name'
  213 + expects(:present).with(model, {:only => ['name']})
  214 + present_partial(model, {})
  215 + end
  216 +
  217 + should 'accept json as fields parameter when calling present partial' do
  218 + model = mock
  219 + params[:fields] = {only: [:name, {user: [:login]}]}.to_json
  220 + expects(:present).with(model, {:only => ['name', {'user' => ['login']}]})
  221 + present_partial(model, {})
  222 + end
  223 +
195 224 protected
196 225  
197 226 def error!(info, status)
... ...
test/unit/api/people_test.rb
... ... @@ -15,6 +15,19 @@ class PeopleTest &lt; ActiveSupport::TestCase
15 15 assert_equivalent [person1.id, person2.id, person.id], json['people'].map {|c| c['id']}
16 16 end
17 17  
  18 + should 'list all members of a community' do
  19 + person1 = fast_create(Person)
  20 + person2 = fast_create(Person)
  21 + community = fast_create(Community)
  22 + community.add_member(person1)
  23 + community.add_member(person2)
  24 +
  25 + get "/api/v1/profiles/#{community.id}/members?#{params.to_query}"
  26 + json = JSON.parse(last_response.body)
  27 + assert_equal 2, json["people"].count
  28 + assert_equivalent [person1.id,person2.id], json["people"].map{|p| p["id"]}
  29 + end
  30 +
18 31 should 'not list invisible people' do
19 32 invisible_person = fast_create(Person, :visible => false)
20 33  
... ... @@ -47,12 +60,34 @@ class PeopleTest &lt; ActiveSupport::TestCase
47 60 assert_equal some_person.id, json['person']['id']
48 61 end
49 62  
  63 + should 'people endpoint filter by fields parameter' do
  64 + get "/api/v1/people?#{params.to_query}&fields=name"
  65 + json = JSON.parse(last_response.body)
  66 + expected = {'people' => [{'name' => person.name}]}
  67 + assert_equal expected, json
  68 + end
  69 +
  70 + should 'people endpoint filter by fields parameter with hierarchy' do
  71 + fields = {only: [:name, {user: [:login]}]}.to_json
  72 + get "/api/v1/people?#{params.to_query}&fields=#{fields}"
  73 + json = JSON.parse(last_response.body)
  74 + expected = {'people' => [{'name' => person.name, 'user' => {'login' => 'testapi'}}]}
  75 + assert_equal expected, json
  76 + end
  77 +
50 78 should 'get logged person' do
51 79 get "/api/v1/people/me?#{params.to_query}"
52 80 json = JSON.parse(last_response.body)
53 81 assert_equal person.id, json['person']['id']
54 82 end
55 83  
  84 + should 'me endpoint filter by fields parameter' do
  85 + get "/api/v1/people/me?#{params.to_query}&fields=name"
  86 + json = JSON.parse(last_response.body)
  87 + expected = {'person' => {'name' => person.name}}
  88 + assert_equal expected, json
  89 + end
  90 +
56 91 should 'not get invisible person' do
57 92 person = fast_create(Person, :visible => false)
58 93  
... ... @@ -205,4 +240,19 @@ class PeopleTest &lt; ActiveSupport::TestCase
205 240 assert_equal "www.blog.org", json['person']['additional_data']['Custom Blog']
206 241 end
207 242  
  243 + PERSON_ATTRIBUTES = %w(vote_count comments_count articles_count)
  244 +
  245 + PERSON_ATTRIBUTES.map do |attribute|
  246 + define_method "test_should_not_expose_#{attribute}_attribute_in_person_enpoint_if_field_parameter_does_not_contain_the_attribute" do
  247 + get "/api/v1/people/me?#{params.to_query}&fields=name"
  248 + json = JSON.parse(last_response.body)
  249 + assert_nil json['person'][attribute]
  250 + end
  251 +
  252 + define_method "test_should_expose_#{attribute}_attribute_in_person_enpoints_if_field_parameter_is_passed" do
  253 + get "/api/v1/people/me?#{params.to_query}&fields=#{attribute}"
  254 + json = JSON.parse(last_response.body)
  255 + assert_not_nil json['person'][attribute]
  256 + end
  257 + end
208 258 end
... ...
test/unit/api/profiles_test.rb 0 → 100644
... ... @@ -0,0 +1,32 @@
  1 +require_relative 'test_helper'
  2 +
  3 +class ProfilesTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + Profile.delete_all
  7 + login_api
  8 + end
  9 +
  10 + should 'list all profiles' do
  11 + person1 = fast_create(Person)
  12 + person2 = fast_create(Person)
  13 + community = fast_create(Community)
  14 + get "/api/v1/profiles?#{params.to_query}"
  15 + json = JSON.parse(last_response.body)
  16 + assert_equivalent [person.id, person1.id, person2.id, community.id], json.map {|p| p['id']}
  17 + end
  18 +
  19 + should 'get person from profile id' do
  20 + some_person = fast_create(Person)
  21 + get "/api/v1/profiles/#{some_person.id}?#{params.to_query}"
  22 + json = JSON.parse(last_response.body)
  23 + assert_equal some_person.id, json['id']
  24 + end
  25 +
  26 + should 'get community from profile id' do
  27 + community = fast_create(Community)
  28 + get "/api/v1/profiles/#{community.id}?#{params.to_query}"
  29 + json = JSON.parse(last_response.body)
  30 + assert_equal community.id, json['id']
  31 + end
  32 +end
... ...
test/unit/api/search_test.rb
... ... @@ -23,6 +23,16 @@ class SearchTest &lt; ActiveSupport::TestCase
23 23 assert_not_empty json['articles']
24 24 end
25 25  
  26 + should 'list only articles that has children' do
  27 + article = fast_create(Article, :profile_id => person.id)
  28 + parent = create(Article, :profile_id => person.id, :name => 'parent article')
  29 + child = create(Article, :profile_id => person.id, :parent_id => parent.id, :name => 'child article')
  30 +
  31 + get "/api/v1/search/article?has_children=true"
  32 + json = JSON.parse(last_response.body)
  33 + assert_equal parent.id, json['articles'].first['id']
  34 + end
  35 +
26 36 should 'invalid search string articles' do
27 37 fast_create(Article, :profile_id => person.id, :name => 'some article')
28 38 get "/api/v1/search/article?query=test"
... ...
test/unit/api/session_test.rb
... ... @@ -182,4 +182,14 @@ class SessionTest &lt; ActiveSupport::TestCase
182 182 assert_equal 404, last_response.status
183 183 end
184 184  
  185 + should 'not return private token when the registered user is inactive' do
  186 + params = {:login => "newuserapi", :password => "newuserapi", :password_confirmation => "newuserapi", :email => "newuserapi@email.com" }
  187 + post "/api/v1/register?#{params.to_query}"
  188 + assert_equal 201, last_response.status
  189 + json = JSON.parse(last_response.body)
  190 + assert !User['newuserapi'].activated?
  191 + assert !json['user']['activated']
  192 + assert !json['user']['private_token'].present?
  193 + end
  194 +
185 195 end
... ...