basic groups api; refs #8598

this adds show/create/edit/delete api actions for groups.  currently create
only allows creating community groups.  the changes to these controller
functions should be backwards compatible with course and student groups.
anyone should be able to create a community (for now), but only moderators or
account admins are allowed to edit/delete them.

test plan:
 - test the four api actions from the command line
 - make sure managing course and student groups still works (there should be no
   visible changes here)

Change-Id: I9cac73ab434b0ba464ecfe399ab42174eff9b48a
Reviewed-on: https://gerrit.instructure.com/11148
Reviewed-by: Brian Palmer <brianp@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
This commit is contained in:
Simon Williams 2012-05-29 16:55:40 -06:00
parent 3bf25a7013
commit 4c59a6efe7
13 changed files with 479 additions and 173 deletions

View File

@ -18,13 +18,68 @@
# @API Groups
#
# API for accessing group information.
# Groups serve as the data for a few different ideas in Canvas. The first is
# that they can be a community in the canvas network. The second is that they
# can be organized by students in a course, for study or communication (but not
# grading). The third is that they can be organized by teachers or account
# administrators for the purpose of projects, assignments, and grading. This
# last kind of group is always part of a group category, which adds the
# restriction that a user may only be a member of one group per category.
#
# All of these types of groups function similarly, and can be the parent
# context for many other types of functionality and interaction, such as
# collections, discussions, wikis, and shared files.
#
# A Group object looks like:
#
# !!!javascript
# {
# // The ID of the group.
# id: 17,
#
# // The display name of the group.
# name: "Math Group 1",
#
# // A description of the group. This is plain text.
# description: null,
#
# // Whether or not the group is public. Currently only community groups
# // can be made public. Also, once a group has been set to public, it
# // cannot be changed back to private.
# is_public: false,
#
# // How people are allowed to join the group. For all groups except for
# // community groups, the user must share the group's parent course or
# // account. For student organized or community groups, where a user
# // can be a member of as many or few as they want, the applicable
# // levels are "parent_context_auto_join", "parent_context_request", and
# // "invitation_only". For class groups, where students are divided up
# // and should only be part of one group of the category, this value
# // will always be "invitation_only", and is not relevant.
# //
# // * If "parent_context_auto_join", anyone can join and will be
# // automatically accepted.
# // * If "parent_context_request", anyone can request to join, which
# // must be approved by a group moderator.
# // * If "invitation_only", only those how have received an
# // invitation my join the group, by accepting that invitation.
# join_level: "invitation_only",
#
# // The number of members currently in the group
# members_count: 0
#
# // The ID of the group's category.
# group_category_id: 4,
# }
#
class GroupsController < ApplicationController
before_filter :get_context
before_filter :require_context, :only => [:create_category, :delete_category]
before_filter :get_group_as_context, :only => [:show]
include Api::V1::Attachment
include Api::V1::Group
SETTABLE_GROUP_ATTRIBUTES = %w(name description join_level is_public group_category)
def context_group_members
@group = @context
@ -66,38 +121,101 @@ class GroupsController < ApplicationController
@groups = @current_user ? @current_user.groups.active : []
end
def show
if @group.deleted? && @group.context
flash[:notice] = t('notices.already_deleted', "That group has been deleted")
redirect_to named_context_url(@group.context, :context_url)
return
def context_index
add_crumb (@context.is_a?(Account) ? t('#crumbs.users', "Users") : t('#crumbs.people', "People")), named_context_url(@context, :context_users_url)
add_crumb t('#crumbs.groups', "Groups"), named_context_url(@context, :context_groups_url)
@active_tab = @context.is_a?(Account) ? "users" : "people"
@groups = @context.groups.active
@categories = @context.group_categories
group_ids = @groups.map(&:id)
@user_groups = @current_user.group_memberships_for(@context).select{|g| group_ids.include?(g.id) } if @current_user
@user_groups ||= []
@available_groups = (@groups - @user_groups).select{|g| g.can_join?(@current_user) }
if !@context.grants_right?(@current_user, session, :manage_groups)
@groups = @user_groups
end
@current_conferences = @group.web_conferences.select{|c| c.active? && c.users.include?(@current_user) } rescue []
@groups = @current_user.group_memberships_for(@group.context) if @current_user
if params[:join] && @group.can_join?(@current_user)
@group.request_user(@current_user)
if !@group.grants_right?(@current_user, session, :read)
render :action => 'membership_pending'
return
else
flash[:notice] = t('notices.welcome', "Welcome to the group %{group_name}!", :group_name => @group.name)
redirect_to named_context_url(@group.context, :context_groups_url)
return
end
end
if params[:leave] && @group.can_leave?(@current_user)
membership = @group.membership_for_user(@current_user)
if membership
membership.destroy
flash[:notice] = t('notices.goodbye', "You have removed yourself from the group %{group_name}.", :group_name => @group.name)
redirect_to named_context_url(@group.context, :context_groups_url)
return
end
end
if authorized_action(@group, @current_user, :read)
@home_page = WikiNamespace.default_for_context(@group).wiki.wiki_page
# sort by name, but with the student organized category in the back
@categories = @categories.sort_by{|c| [ (c.student_organized? ? 1 : 0), c.name ] }
@groups = @groups.sort_by{ |g| [(g.name || '').downcase, g.created_at] }
if authorized_action(@context, @current_user, :read_roster)
respond_to do |format|
format.html
if @context.grants_right?(@current_user, session, :manage_groups)
format.html { render :action => 'context_manage_groups' }
else
format.html { render :action => 'context_groups' }
end
format.atom { render :xml => @groups.to_atom.to_xml }
end
end
end
# @API Get a single group
#
# Returns the data for a single group, or a 401 if the caller doesn't have
# the rights to see it.
#
# @example_request
# curl https://<canvas>/api/v1/groups/<group_id> \
# -H 'Authorization: Bearer <token>'
#
# @example_response
# {
# id: 13,
# name: "Mary's Group",
# description: "A group for my friends",
# is_public: false,
# join_level: "parent_context_request",
# members_count: 3
# group_category_id: 2
# },
def show
find_group
respond_to do |format|
format.html do
if @group && @group.context
add_crumb @group.context.short_name, named_context_url(@group.context, :context_url)
add_crumb @group.short_name, named_context_url(@group, :context_url)
elsif @group
add_crumb @group.short_name, named_context_url(@group, :context_url)
end
@context = @group
if @group.deleted? && @group.context
flash[:notice] = t('notices.already_deleted', "That group has been deleted")
redirect_to named_context_url(@group.context, :context_url)
return
end
@current_conferences = @group.web_conferences.select{|c| c.active? && c.users.include?(@current_user) } rescue []
if params[:join] && @group.can_join?(@current_user)
@group.request_user(@current_user)
if !@group.grants_right?(@current_user, session, :read)
render :action => 'membership_pending'
return
else
flash[:notice] = t('notices.welcome', "Welcome to the group %{group_name}!", :group_name => @group.name)
redirect_to named_context_url(@group.context, :context_groups_url)
return
end
end
if params[:leave] && @group.can_leave?(@current_user)
membership = @group.membership_for_user(@current_user)
if membership
membership.destroy
flash[:notice] = t('notices.goodbye', "You have removed yourself from the group %{group_name}.", :group_name => @group.name)
redirect_to named_context_url(@group.context, :context_groups_url)
return
end
end
if authorized_action(@group, @current_user, :read)
@home_page = WikiNamespace.default_for_context(@group).wiki.wiki_page
end
end
format.json do
if authorized_action(@group, @current_user, :read)
render :json => group_json(@group, @current_user, session)
end
end
end
end
@ -108,6 +226,163 @@ class GroupsController < ApplicationController
end
end
# @API Create a group
#
# Creates a new group. Right now, only community groups can be created
# through the API.
#
# @argument name
# @argument description
# @argument is_public
# @argument join_level
#
# @example_request
# curl https://<canvas>/api/v1/groups/<group_id> \
# -F 'name=Math Teachers' \
# -F 'description=A place to gather resources for our classes.' \
# -F 'is_public=true' \
# -F 'join_level=parent_context_auto_join' \
# -H 'Authorization: Bearer <token>'
#
# @example_response
# {
# id: 25,
# name: "Math Teachers",
# description: "A place to gather resources for our classes.",
# is_public: true,
# join_level: "parent_context_auto_join",
# members_count: 13
# group_category_id: 7
# }
def create
# only allow community groups from the api right now
if api_request?
@context = @domain_root_account
params[:group_category] = GroupCategory.communities_for(@context)
elsif params[:group]
group_category_id = params[:group].delete :group_category_id
if group_category_id && @context.grants_right?(@current_user, session, :manage_groups)
group_category = @context.group_categories.find_by_id(group_category_id)
return render :json => {}, :status => :bad_request unless group_category
params[:group][:group_category] = group_category
else
params[:group][:group_category] = nil
end
end
attrs = api_request? ? params : params[:group]
@group = @context.groups.new(attrs.slice(*SETTABLE_GROUP_ATTRIBUTES))
if authorized_action(@group, @current_user, :create)
respond_to do |format|
if @group.save
@group.add_user(@current_user, 'accepted', true) if @group.should_add_creator?
@group.invitees = params[:invitees]
flash[:notice] = t('notices.create_success', 'Group was successfully created.')
format.html { redirect_to group_url(@group) }
format.json { render :json => group_json(@group, @current_user, session) }
else
format.html { render :action => "new" }
format.json { render :json => @group.errors, :status => :bad_request }
end
end
end
end
def edit
@group = (@context ? @context.groups : Group).find(params[:id])
@context = @group
if authorized_action(@group, @current_user, :update)
end
end
# @API Edit a group
#
# Modifies an existing group.
#
# @argument name
# @argument description
# @argument is_public Currently you cannot set a group back to private once
# it has been made public.
# @argument join_level
#
# @example_request
# curl https://<canvas>/api/v1/groups/<group_id> \
# -X PUT \
# -F 'name=Algebra Teachers' \
# -F 'join_level=parent_context_request' \
# -H 'Authorization: Bearer <token>'
#
# @example_response
# {
# id: 25,
# name: "Algebra Teachers",
# description: "A place to gather resources for our classes.",
# is_public: true,
# join_level: "parent_context_request",
# members_count: 13
# group_category_id: 7
# }
def update
find_group
if !api_request? && params[:group] && params[:group][:group_category_id]
group_category_id = params[:group].delete :group_category_id
group_category = @context.group_categories.find_by_id(group_category_id)
return render :json => {}, :status => :bad_request unless group_category
params[:group][:group_category] = group_category
end
attrs = api_request? ? params : params[:group]
if authorized_action(@group, @current_user, :update)
respond_to do |format|
if @group.update_attributes(attrs.slice(*SETTABLE_GROUP_ATTRIBUTES))
flash[:notice] = t('notices.update_success', 'Group was successfully updated.')
format.html { redirect_to group_url(@group) }
format.json { render :json => group_json(@group, @current_user, session) }
else
format.html { render :action => "edit" }
format.json { render :json => @group.errors, :status => :bad_request }
end
end
end
end
# @API Delete a group
#
# Deletes a group and removes all members.
#
# @example_request
# curl https://<canvas>/api/v1/groups/<group_id> \
# -X DELETE \
# -H 'Authorization: Bearer <token>'
#
# @example_response
# {
# id: 144,
# name: "My Group",
# description: null,
# is_public: false,
# join_level: "invitation_only",
# members_count: 0,
# group_category_id: 9
# }
def destroy
find_group
if authorized_action(@group, @current_user, :delete)
if @group.destroy
flash[:notice] = t('notices.delete_success', "Group successfully deleted")
respond_to do |format|
format.html { redirect_to(dashboard_url) }
format.json { render :json => group_json(@group, @current_user, session) }
end
else
respond_to do |format|
format.html { redirect_to(dashboard_url) }
format.json { render :json => @group.errors, :status => :bad_request }
end
end
end
end
def create_category
if authorized_action(@context, @current_user, :manage_groups)
@group_category = @context.group_categories.build
@ -169,84 +444,6 @@ class GroupsController < ApplicationController
end
end
def create
if params[:group]
group_category_id = params[:group].delete :group_category_id
if group_category_id && @context.grants_right?(@current_user, session, :manage_groups)
group_category = @context.group_categories.find_by_id(group_category_id)
return render :json => {}, :status => :bad_request unless group_category
params[:group][:group_category] = group_category
else
params[:group][:group_category] = nil
end
end
@group = @context.groups.build(params[:group])
if authorized_action(@group, @current_user, :create)
respond_to do |format|
if @group.save
if !@context.grants_right?(@current_user, session, :manage_groups)
@group.add_user(@current_user)
end
@group.invitees = params[:invitees]
flash[:notice] = t('notices.create_success', 'Group was successfully created.')
format.html { redirect_to group_url(@group) }
format.json { render :json => @group.to_json(:methods => :participating_users_count) }
else
format.html { render :action => "new" }
format.json { render :json => @group.errors.to_json }
end
end
end
end
def edit
@group = (@context ? @context.groups : Group).find(params[:id])
@context = @group
if authorized_action(@group, @current_user, :update)
end
end
def update
@group = (@context ? @context.groups : Group).find(params[:id])
if params[:group] && params[:group][:group_category_id]
group_category_id = params[:group].delete :group_category_id
group_category = @context.group_categories.find_by_id(group_category_id)
return render :json => {}, :status => :bad_request unless group_category
params[:group][:group_category] = group_category
end
if authorized_action(@group, @current_user, :manage)
respond_to do |format|
if @group.update_attributes(params[:group])
flash[:notice] = t('notices.update_success', 'Group was successfully updated.')
format.html { redirect_to group_url(@group) }
format.json { render :json => @group.to_json(:methods => :participating_users_count) }
else
format.html { render :action => "edit" }
format.json { render :json => @group.errors.to_json }
end
end
end
end
def destroy
@group = (@context ? @context.groups : Group).find(params[:id])
if authorized_action(@group, @current_user, :manage)
begin
@group.destroy
flash[:notice] = t('notices.delete_success', "Group successfully deleted")
respond_to do |format|
format.html { redirect_to(dashboard_url) }
format.json { render :json => @group.to_json }
end
rescue Exception => e
respond_to do |format|
format.html { redirect_to(dashboard_url) }
format.json { render :json => @group.to_json, :status => :bad_request }
end
end
end
end
def public_feed
return unless get_feed_context(:only => [:group])
feed = Atom::Feed.new do |f|
@ -325,45 +522,11 @@ class GroupsController < ApplicationController
protected
def get_group_as_context
@group = (@context ? @context.groups : Group).find(params[:id])
if @group && @group.context
add_crumb @group.context.short_name, named_context_url(@group.context, :context_url)
add_crumb @group.short_name, named_context_url(@group, :context_url)
elsif @group
add_crumb @group.short_name, named_context_url(@group, :context_url)
end
@context = @group
end
def context_index
add_crumb (@context.is_a?(Account) ? t('#crumbs.users', "Users") : t('#crumbs.people', "People")), named_context_url(@context, :context_users_url)
add_crumb t('#crumbs.groups', "Groups"), named_context_url(@context, :context_groups_url)
@active_tab = @context.is_a?(Account) ? "users" : "people"
@groups = @context.groups.active
@categories = @context.group_categories
group_ids = @groups.map(&:id)
@user_groups = @current_user.group_memberships_for(@context).select{|g| group_ids.include?(g.id) } if @current_user
@user_groups ||= []
@available_groups = (@groups - @user_groups).select{|g| g.can_join?(@current_user) }
if !@context.grants_right?(@current_user, session, :manage_groups)
@groups = @user_groups
end
# sort by name, but with the student organized category in the back
@categories = @categories.sort_by{|c| [ (c.student_organized? ? 1 : 0), c.name ] }
@groups = @groups.sort_by{ |g| [(g.name || '').downcase, g.created_at] }
if authorized_action(@context, @current_user, :read_roster)
respond_to do |format|
if @context.grants_right?(@current_user, session, :manage_groups)
format.html { render :action => 'context_manage_groups' }
else
format.html { render :action => 'context_groups' }
end
format.atom { render :xml => @groups.to_atom.to_xml }
end
def find_group
if api_request?
@group = Group.active.find(params[:group_id])
else
@group = (@context ? @context.groups : Group).find(params[:id])
end
end

View File

@ -110,6 +110,7 @@ class Course < ActiveRecord::Base
has_many :all_group_categories, :class_name => 'GroupCategory', :as => :context
has_many :groups, :as => :context
has_many :active_groups, :as => :context, :class_name => 'Group', :conditions => ['groups.workflow_state != ?', 'deleted']
has_many :group_categories, :as => :context, :conditions => ['deleted_at IS NULL']
has_many :assignment_groups, :as => :context, :dependent => :destroy, :order => 'assignment_groups.position, assignment_groups.name'
has_many :assignments, :as => :context, :dependent => :destroy, :order => 'assignments.created_at'
has_many :calendar_events, :as => :context, :conditions => ['calendar_events.workflow_state != ?', 'cancelled'], :dependent => :destroy

View File

@ -19,8 +19,11 @@
class Group < ActiveRecord::Base
include Context
include Workflow
include CustomValidations
attr_accessible :name, :context, :max_membership, :group_category, :join_level, :default_view, :description, :is_public
validates_only_false_to_true :is_public
attr_accessible :name, :context, :max_membership, :group_category, :join_level, :default_view, :description
has_many :group_memberships, :dependent => :destroy, :conditions => ['group_memberships.workflow_state != ?', 'deleted']
has_many :users, :through => :group_memberships, :conditions => ['users.workflow_state != ?', 'deleted']
has_many :participating_group_memberships, :class_name => "GroupMembership", :conditions => ['group_memberships.workflow_state = ?', 'accepted']
@ -139,6 +142,10 @@ class Group < ActiveRecord::Base
self.participating_group_memberships.moderators.find_by_user_id(user && user.id)
end
def should_add_creator?
self.group_category && (self.group_category.communities? || self.group_category.student_organized?)
end
def short_name
name
end
@ -199,9 +206,9 @@ class Group < ActiveRecord::Base
end
end
def add_user(user, new_record_state=nil)
def add_user(user, new_record_state=nil, moderator=false)
return nil if !user
attrs = { :user => user }
attrs = { :user => user, :moderator => moderator }
new_record_state ||= case self.join_level
when 'invitation_only' then 'invited'
when 'parent_context_request' then 'requested'
@ -268,6 +275,7 @@ class Group < ActiveRecord::Base
self.group_category ||= GroupCategory.student_organized_for(self.context)
self.join_level ||= 'invitation_only'
self.is_public ||= false
self.is_public = false unless self.group_category.try(:communities?)
if self.context && self.context.is_a?(Course)
self.account = self.context.account
elsif self.context && self.context.is_a?(Account)
@ -291,10 +299,8 @@ class Group < ActiveRecord::Base
# permission check for efficiency -- see User#cached_contexts
set_policy do
given { |user| user && self.has_member?(user) }
can :create and
can :create_collaborations and
can :create_conferences and
can :delete and
can :manage and
can :manage_admin_users and
can :manage_calendar and
@ -306,7 +312,6 @@ class Group < ActiveRecord::Base
can :read and
can :read_roster and
can :send_messages and
can :update
# if I am a member of this group and I can moderate_forum in the group's context
# (makes it so group members cant edit each other's discussion entries)
@ -314,7 +319,12 @@ class Group < ActiveRecord::Base
can :moderate_forum
given { |user| user && self.has_moderator?(user) }
can :moderate_forum
can :delete and
can :moderate_forum and
can :update
given { |user| self.group_category.try(:communities?) }
can :create
given { |user, session| self.context && self.context.grants_right?(user, session, :participate_as_student) && self.context.allow_student_organized_groups }
can :create
@ -362,8 +372,8 @@ class Group < ActiveRecord::Base
end
end
def participating_users_count
self.participating_users.count
def members_count
self.participating_group_memberships.count
end
def quota

View File

@ -23,7 +23,7 @@ class GroupMembership < ActiveRecord::Base
belongs_to :group
belongs_to :user
attr_accessible :group, :user, :workflow_state
attr_accessible :group, :user, :workflow_state, :moderator
before_save :ensure_mutually_exclusive_membership
before_save :assign_uuid

View File

@ -1751,7 +1751,7 @@ class User < ActiveRecord::Base
# according to the set_policy block in group.rb, user u can manage group
# g if either:
# (a) g.context.grants_right?(u, :manage_groups)
# (b) g.participating_users.include(u)
# (b) g.has_member?(u)
# this is a very performance sensitive method, so we're bypassing the
# normal policy checking and somewhat duplicating auth logic here. which
# is a shame. it'd be really nice to add support to our policy framework

View File

@ -834,6 +834,7 @@ ActionController::Routing::Routes.draw do |map|
end
api.with_options(:controller => :groups) do |groups|
groups.resources :groups, :except => [:index]
groups.post 'groups/:group_id/files', :action => :create_file
groups.get 'groups/:group_id/activity_stream', :action => :activity_stream, :path_name => 'group_activity_stream'
end

View File

@ -19,9 +19,14 @@
module Api::V1::Group
include Api::V1::Json
API_GROUP_JSON_OPTS = {
:only => %w(id name description is_public join_level group_category_id),
:methods => %w(members_count),
}
def group_json(group, user, session, options = {})
hash = api_json(group, user, session, API_GROUP_JSON_OPTS)
include = options[:include] || []
hash = api_json(group, user, session, :only => %w{id name})
if include.include?('users')
hash['users'] = group.users.map{ |u| user_json(u, user, session) }
end

View File

@ -52,6 +52,15 @@ module CustomValidations
end
end
# only allow false -> true type changes
def validates_only_false_to_true(*fields)
validates_each(fields) do |record, attr, value|
if !record.new_record? && record.send("#{attr}_changed?") && !!record.send("#{attr}_was") && !value
record.errors.add attr, "cannot be changed back to false"
end
end
end
end
def self.included(klass)

View File

@ -46,11 +46,11 @@ define([
$(this).slideUp();
var $group = $("#group_blank").clone(true);
$group.fillTemplateData({
data: data.group || data.course_assigned_group,
data: data,
hrefValues: ['id']
});
$group.attr('id', "group_" + data.group.id)
$group.find(".members_count").text(I18n.t('member', 'member', { count: data.group.participating_users_count }));
$group.attr('id', "group_" + data.id)
$group.find(".members_count").text(I18n.t('member', 'member', { count: data.members_count }));
$("#group_blank").before($group.show());
$("html,body").scrollTo($group);
$group.animate({'backgroundColor': '#FFEE88'}, 1000)

View File

@ -389,20 +389,19 @@ define([
return $group;
},
success: function(data, $group) {
var group = data.group || data.course_assigned_group;
$group.loadingImage('remove');
$group.droppable(contextGroups.droppable_options);
$group.find(".name.blank_name").hide().end()
.find(".group_name").show();
$group.find(".group_user_count").show();
group.group_id = group.id;
data.group_id = data.id;
$group.fillTemplateData({
id: 'group_' + group.id,
data: group,
id: 'group_' + data.id,
data: data,
avoid: '.student_list',
hrefValues: ['id']
});
contextGroups.addGroupToSidebar(group)
contextGroups.addGroupToSidebar(data)
contextGroups.updateCategoryCounts($group.parents(".group_category"));
}
});
@ -599,6 +598,7 @@ define([
$("#group_tabs").tabs('add', '#category_' + group_category.id, group_category.name, newIndex);
$("#group_tabs").tabs('select', newIndex);
contextGroups.populateCategory($category);
contextGroups.updateCategory($category, group_category);
contextGroups.updateCategoryCounts($category);
$(window).triggerHandler('resize');
});

View File

@ -20,12 +20,112 @@ require File.expand_path(File.dirname(__FILE__) + '/../api_spec_helper')
require File.expand_path(File.dirname(__FILE__) + '/../file_uploads_spec_helper')
describe "Groups API", :type => :integration do
def group_json(group)
{
'id' => group.id,
'name' => group.name,
'description' => group.description,
'is_public' => group.is_public,
'join_level' => group.join_level,
'members_count' => group.members_count,
'group_category_id' => group.group_category_id,
}
end
before do
@moderator = user_model
@member = user_with_pseudonym
@group_category = GroupCategory.communities_for(Account.default)
group_model(:name => "Algebra Teachers", :group_category => @group_category)
@group.add_user(@member, 'accepted', false)
@group.add_user(@moderator, 'accepted', true)
@group_path = "/api/v1/groups/#{@group.id}"
@group_path_options = { :controller => "groups", :format => "json" }
@group_json = group_json(@group)
end
it "should allow a member to retrieve the group" do
@user = @member
json = api_call(:get, @group_path, @group_path_options.merge(:group_id => @group.to_param, :action => "show"))
json.should == @group_json
end
it "should allow anyone to create a new community" do
user_model
json = api_call(:post, "/api/v1/groups", @group_path_options.merge(:action => "create"), {
'name'=> "History Teachers",
'description' => "Because history is awesome!",
'is_public'=> false,
'join_level'=> "parent_context_request",
})
@group2 = Group.last(:order => :id)
@group2.group_category.should be_communities
json.should == group_json(@group2)
end
it "should allow a moderator to edit a group" do
@user = @moderator
new_attrs = {
'name' => "Algebra II Teachers",
'description' => "Math rocks!",
'is_public' => true,
'join_level' => "parent_context_auto_join",
}
json = api_call(:put, @group_path, @group_path_options.merge(:group_id => @group.to_param, :action => "update"), new_attrs)
@group.reload
@group.name.should == "Algebra II Teachers"
@group.description.should == "Math rocks!"
@group.is_public.should == true
@group.join_level.should == "parent_context_auto_join"
json.should == @group_json.merge(new_attrs)
end
it "should only allow updating a group from private to public" do
@user = @moderator
new_attrs = {
'is_public' => true,
}
json = api_call(:put, @group_path, @group_path_options.merge(:group_id => @group.to_param, :action => "update"), new_attrs)
@group.reload
@group.is_public.should == true
new_attrs = {
'is_public' => false,
}
json = api_call(:put, @group_path, @group_path_options.merge(:group_id => @group.to_param, :action => "update"), new_attrs, {}, :expected_status => 400)
@group.reload
@group.is_public.should == true
end
it "should not allow a member to edit a group" do
@user = @member
new_attrs = {
'name'=> "Algebra II Teachers",
'is_public'=> true,
'join_level'=> "parent_context_auto_join",
}
json = api_call(:put, @group_path, @group_path_options.merge(:group_id => @group.to_param, :action => "update"), new_attrs, {}, :expected_status => 401)
json['message'].should match /not authorized/
end
it "should allow a moderator to delete a group" do
@user = @moderator
json = api_call(:delete, @group_path, @group_path_options.merge(:group_id => @group.to_param, :action => "destroy"))
json.should == @group_json.merge({ 'members_count' => 0 })
end
it "should not allow a member to delete a group" do
@user = @member
json = api_call(:delete, @group_path, @group_path_options.merge(:group_id => @group.to_param, :action => "destroy"), {}, {}, :expected_status => 401)
json['message'].should match /not authorized/
end
context "group files" do
it_should_behave_like "file uploads api with folders"
before do
group_model
@group.add_user(user_with_pseudonym)
@user = @member
end
def preflight(preflight_params)

View File

@ -412,7 +412,7 @@ describe GroupsController do
describe "POST create" do
it "should require authorization" do
course_with_teacher(:active_all => true)
post 'create', :course_id => @course.id
post 'create', :course_id => @course.id, :group => {:name => "some group"}
assert_unauthorized
end

View File

@ -21,7 +21,8 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
describe Group do
before do
group_model
course_model
group_model(:context => @course)
end
context "validation" do
@ -37,6 +38,22 @@ describe Group do
it "should be private by default" do
@group.is_public.should be_false
end
it "should allow a private group to be made public" do
@communities = GroupCategory.communities_for(Account.default)
group_model(:group_category => @communities, :is_public => false)
@group.is_public = true
@group.save!
@group.reload.is_public.should be_true
end
it "should not allow a public group to be made private" do
@communities = GroupCategory.communities_for(Account.default)
group_model(:group_category => @communities, :is_public => true)
@group.is_public = false
@group.save.should be_false
@group.reload.is_public.should be_true
end
context "#peer_groups" do
it "should find all peer groups" do