Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
bea463d9
Commit
bea463d9
authored
Feb 22, 2014
by
jsa
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add acceptance tests for forums comment deletion.
JIRA: FOR-472
parent
7be356b3
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
205 additions
and
40 deletions
+205
-40
common/djangoapps/student/views.py
+9
-0
common/djangoapps/terrain/stubs/comments.py
+24
-35
common/test/acceptance/fixtures/__init__.py
+3
-0
common/test/acceptance/fixtures/discussion.py
+89
-0
common/test/acceptance/pages/lms/discussion_single_thread.py
+23
-4
common/test/acceptance/tests/test_discussion.py
+57
-1
No files found.
common/djangoapps/student/views.py
View file @
bea463d9
...
...
@@ -57,6 +57,8 @@ from collections import namedtuple
from
courseware.courses
import
get_courses
,
sort_by_announcement
from
courseware.access
import
has_access
from
django_comment_common.models
import
Role
from
external_auth.models
import
ExternalAuthMap
import
external_auth.views
...
...
@@ -1211,6 +1213,7 @@ def auto_auth(request):
* `full_name` for the user profile (the user's full name; defaults to the username)
* `staff`: Set to "true" to make the user global staff.
* `course_id`: Enroll the student in the course with `course_id`
* `roles`: Comma-separated list of roles to grant the student in the course with `course_id`
If username, email, or password are not provided, use
randomly generated credentials.
...
...
@@ -1226,6 +1229,7 @@ def auto_auth(request):
full_name
=
request
.
GET
.
get
(
'full_name'
,
username
)
is_staff
=
request
.
GET
.
get
(
'staff'
,
None
)
course_id
=
request
.
GET
.
get
(
'course_id'
,
None
)
role_names
=
[
v
.
strip
()
for
v
in
request
.
GET
.
get
(
'roles'
,
''
)
.
split
(
','
)
if
v
.
strip
()]
# Get or create the user object
post_data
=
{
...
...
@@ -1268,6 +1272,11 @@ def auto_auth(request):
if
course_id
is
not
None
:
CourseEnrollment
.
enroll
(
user
,
course_id
)
# Apply the roles
for
role_name
in
role_names
:
role
=
Role
.
objects
.
get
(
name
=
role_name
,
course_id
=
course_id
)
user
.
roles
.
add
(
role
)
# Log in as the user
user
=
authenticate
(
username
=
username
,
password
=
password
)
login
(
request
,
user
)
...
...
common/djangoapps/terrain/stubs/comments.py
View file @
bea463d9
...
...
@@ -2,7 +2,6 @@
Stub implementation of cs_comments_service for acceptance tests
"""
from
datetime
import
datetime
import
re
import
urlparse
from
.http
import
StubHttpRequestHandler
,
StubHttpService
...
...
@@ -14,6 +13,7 @@ class StubCommentsServiceHandler(StubHttpRequestHandler):
"/api/v1/users/(?P<user_id>
\\
d+)$"
:
self
.
do_user
,
"/api/v1/threads$"
:
self
.
do_threads
,
"/api/v1/threads/(?P<thread_id>
\\
w+)$"
:
self
.
do_thread
,
"/api/v1/comments/(?P<comment_id>
\\
w+)$"
:
self
.
do_comment
,
}
path
=
urlparse
.
urlparse
(
self
.
path
)
.
path
for
pattern
in
pattern_handlers
:
...
...
@@ -25,8 +25,13 @@ class StubCommentsServiceHandler(StubHttpRequestHandler):
self
.
send_response
(
404
,
content
=
"404 Not Found"
)
def
do_PUT
(
self
):
if
self
.
path
.
startswith
(
'/set_config'
):
return
StubHttpRequestHandler
.
do_PUT
(
self
)
self
.
send_response
(
204
,
""
)
def
do_DELETE
(
self
):
self
.
send_json_response
({})
def
do_user
(
self
,
user_id
):
self
.
send_json_response
({
"id"
:
user_id
,
...
...
@@ -36,44 +41,28 @@ class StubCommentsServiceHandler(StubHttpRequestHandler):
})
def
do_thread
(
self
,
thread_id
):
match
=
re
.
search
(
"(?P<num>
\\
d+)_responses"
,
thread_id
)
resp_total
=
int
(
match
.
group
(
"num"
))
if
match
else
0
thread
=
{
"id"
:
thread_id
,
"commentable_id"
:
"dummy"
,
"type"
:
"thread"
,
"title"
:
"Thread title"
,
"body"
:
"Thread body"
,
"created_at"
:
datetime
.
utcnow
()
.
isoformat
(),
"unread_comments_count"
:
0
,
"comments_count"
:
resp_total
,
"votes"
:
{
"up_count"
:
0
},
"abuse_flaggers"
:
[],
"closed"
:
"closed"
in
thread_id
,
}
params
=
urlparse
.
parse_qs
(
urlparse
.
urlparse
(
self
.
path
)
.
query
)
if
"recursive"
in
params
and
params
[
"recursive"
][
0
]
==
"True"
:
thread
[
"resp_total"
]
=
resp_total
thread
[
"children"
]
=
[]
resp_skip
=
int
(
params
.
get
(
"resp_skip"
,
[
"0"
])[
0
])
resp_limit
=
int
(
params
.
get
(
"resp_limit"
,
[
"10000"
])[
0
])
num_responses
=
min
(
resp_limit
,
resp_total
-
resp_skip
)
self
.
log_message
(
"Generating {} children; resp_limit={} resp_total={} resp_skip={}"
.
format
(
num_responses
,
resp_limit
,
resp_total
,
resp_skip
))
for
i
in
range
(
num_responses
):
response_id
=
str
(
resp_skip
+
i
)
thread
[
"children"
]
.
append
({
"id"
:
str
(
response_id
),
"type"
:
"comment"
,
"body"
:
response_id
,
"created_at"
:
datetime
.
utcnow
()
.
isoformat
(),
"votes"
:
{
"up_count"
:
0
},
"abuse_flaggers"
:
[],
})
self
.
send_json_response
(
thread
)
if
thread_id
in
self
.
server
.
config
.
get
(
'threads'
,
{}):
thread
=
self
.
server
.
config
[
'threads'
][
thread_id
]
.
copy
()
params
=
urlparse
.
parse_qs
(
urlparse
.
urlparse
(
self
.
path
)
.
query
)
if
"recursive"
in
params
and
params
[
"recursive"
][
0
]
==
"True"
:
thread
.
setdefault
(
'children'
,
[])
resp_total
=
thread
.
setdefault
(
'resp_total'
,
len
(
thread
[
'children'
]))
resp_skip
=
int
(
params
.
get
(
"resp_skip"
,
[
"0"
])[
0
])
resp_limit
=
int
(
params
.
get
(
"resp_limit"
,
[
"10000"
])[
0
])
thread
[
'children'
]
=
thread
[
'children'
][
resp_skip
:(
resp_skip
+
resp_limit
)]
self
.
send_json_response
(
thread
)
else
:
self
.
send_response
(
404
,
content
=
"404 Not Found"
)
def
do_threads
(
self
):
self
.
send_json_response
({
"collection"
:
[],
"page"
:
1
,
"num_pages"
:
1
})
def
do_comment
(
self
,
comment_id
):
# django_comment_client calls GET comment before doing a DELETE, so that's what this is here to support.
if
comment_id
in
self
.
server
.
config
.
get
(
'comments'
,
{}):
comment
=
self
.
server
.
config
[
'comments'
][
comment_id
]
self
.
send_json_response
(
comment
)
class
StubCommentsService
(
StubHttpService
):
HANDLER_CLASS
=
StubCommentsServiceHandler
common/test/acceptance/fixtures/__init__.py
View file @
bea463d9
...
...
@@ -8,3 +8,6 @@ XQUEUE_STUB_URL = os.environ.get('xqueue_url', 'http://localhost:8040')
# Get the URL of the Ora stub used in the test
ORA_STUB_URL
=
os
.
environ
.
get
(
'ora_url'
,
'http://localhost:8041'
)
# Get the URL of the comments service stub used in the test
COMMENTS_STUB_URL
=
os
.
environ
.
get
(
'comments_url'
,
'http://localhost:4567'
)
common/test/acceptance/fixtures/discussion.py
0 → 100644
View file @
bea463d9
"""
Tools for creating discussion content fixture data.
"""
from
datetime
import
datetime
import
json
import
factory
import
requests
from
.
import
COMMENTS_STUB_URL
class
ContentFactory
(
factory
.
Factory
):
FACTORY_FOR
=
dict
id
=
None
user_id
=
"dummy-user-id"
username
=
"dummy-username"
course_id
=
"dummy-course-id"
commentable_id
=
"dummy-commentable-id"
anonymous
=
False
anonymous_to_peers
=
False
at_position_list
=
[]
abuse_flaggers
=
[]
created_at
=
datetime
.
utcnow
()
.
isoformat
()
updated_at
=
datetime
.
utcnow
()
.
isoformat
()
endorsed
=
False
closed
=
False
votes
=
{
"up_count"
:
0
}
class
Thread
(
ContentFactory
):
comments_count
=
0
unread_comments_count
=
0
title
=
"dummy thread title"
body
=
"dummy thread body"
type
=
"thread"
group_id
=
None
pinned
=
False
read
=
False
class
Comment
(
ContentFactory
):
thread_id
=
None
depth
=
0
type
=
"comment"
body
=
"dummy comment body"
class
Response
(
Comment
):
depth
=
1
body
=
"dummy response body"
class
SingleThreadViewFixture
(
object
):
def
__init__
(
self
,
thread
):
self
.
thread
=
thread
def
addResponse
(
self
,
response
,
comments
=
[]):
response
[
'children'
]
=
comments
self
.
thread
.
setdefault
(
'children'
,
[])
.
append
(
response
)
self
.
thread
[
'comments_count'
]
+=
len
(
comments
)
+
1
def
_get_comment_map
(
self
):
"""
Generate a dict mapping each response/comment in the thread
by its `id`.
"""
def
_visit
(
obj
):
res
=
[]
for
child
in
obj
.
get
(
'children'
,
[]):
res
.
append
((
child
[
'id'
],
child
))
if
'children'
in
child
:
res
+=
_visit
(
child
)
return
res
return
dict
(
_visit
(
self
.
thread
))
def
push
(
self
):
"""
Push the data to the stub comments service.
"""
requests
.
put
(
'{}/set_config'
.
format
(
COMMENTS_STUB_URL
),
data
=
{
"threads"
:
json
.
dumps
({
self
.
thread
[
'id'
]:
self
.
thread
}),
"comments"
:
json
.
dumps
(
self
.
_get_comment_map
())
}
)
common/test/acceptance/pages/lms/discussion_single_thread.py
View file @
bea463d9
...
...
@@ -53,10 +53,7 @@ class DiscussionSingleThreadPage(CoursePage):
def
has_add_response_button
(
self
):
"""Returns true if the add response button is visible, false otherwise"""
return
(
self
.
is_css_present
(
".add-response-btn"
)
and
self
.
css_map
(
".add-response-btn"
,
lambda
el
:
el
.
visible
)[
0
]
)
return
self
.
_is_element_visible
(
".add-response-btn"
)
def
click_add_response_button
(
self
):
"""
...
...
@@ -68,3 +65,25 @@ class DiscussionSingleThreadPage(CoursePage):
lambda
:
self
.
is_css_present
(
"#wmd-input-reply-body-{thread_id}:focus"
.
format
(
thread_id
=
self
.
thread_id
)),
"Response field received focus"
))
def
_is_element_visible
(
self
,
selector
):
return
(
self
.
is_css_present
(
selector
)
and
self
.
css_map
(
selector
,
lambda
el
:
el
.
visible
)[
0
]
)
def
is_comment_visible
(
self
,
comment_id
):
"""Returns true if the comment is viewable onscreen"""
return
self
.
_is_element_visible
(
"#comment_{}"
.
format
(
comment_id
))
def
is_comment_deletable
(
self
,
comment_id
):
"""Returns true if the delete comment button is present, false otherwise"""
return
self
.
_is_element_visible
(
"#comment_{} div.action-delete"
.
format
(
comment_id
))
def
delete_comment
(
self
,
comment_id
):
with
self
.
handle_alert
():
self
.
css_click
(
"#comment_{} div.action-delete"
.
format
(
comment_id
))
fulfill
(
EmptyPromise
(
lambda
:
not
self
.
is_comment_visible
(
comment_id
),
"Deleted comment was removed"
))
common/test/acceptance/tests/test_discussion.py
View file @
bea463d9
...
...
@@ -6,6 +6,7 @@ from .helpers import UniqueCourseTest
from
..pages.studio.auto_auth
import
AutoAuthPage
from
..pages.lms.discussion_single_thread
import
DiscussionSingleThreadPage
from
..fixtures.course
import
CourseFixture
from
..fixtures.discussion
import
SingleThreadViewFixture
,
Thread
,
Response
,
Comment
class
DiscussionSingleThreadTest
(
UniqueCourseTest
):
...
...
@@ -19,9 +20,16 @@ class DiscussionSingleThreadTest(UniqueCourseTest):
# Create a course to register for
CourseFixture
(
**
self
.
course_info
)
.
install
()
AutoAuthPage
(
self
.
browser
,
course_id
=
self
.
course_id
)
.
visit
()
self
.
user_id
=
AutoAuthPage
(
self
.
browser
,
course_id
=
self
.
course_id
)
.
visit
()
.
get_user_id
()
def
setup_thread
(
self
,
thread
,
num_responses
):
view
=
SingleThreadViewFixture
(
thread
=
thread
)
for
i
in
range
(
num_responses
):
view
.
addResponse
(
Response
(
id
=
str
(
i
),
body
=
str
(
i
)))
view
.
push
()
def
test_no_responses
(
self
):
self
.
setup_thread
(
Thread
(
id
=
"0_responses"
),
0
)
page
=
DiscussionSingleThreadPage
(
self
.
browser
,
self
.
course_id
,
"0_responses"
)
page
.
visit
()
self
.
assertEqual
(
page
.
get_response_total_text
(),
"0 responses"
)
...
...
@@ -31,6 +39,7 @@ class DiscussionSingleThreadTest(UniqueCourseTest):
self
.
assertIsNone
(
page
.
get_load_responses_button_text
())
def
test_few_responses
(
self
):
self
.
setup_thread
(
Thread
(
id
=
"5_responses"
),
5
)
page
=
DiscussionSingleThreadPage
(
self
.
browser
,
self
.
course_id
,
"5_responses"
)
page
.
visit
()
self
.
assertEqual
(
page
.
get_response_total_text
(),
"5 responses"
)
...
...
@@ -39,6 +48,7 @@ class DiscussionSingleThreadTest(UniqueCourseTest):
self
.
assertIsNone
(
page
.
get_load_responses_button_text
())
def
test_two_response_pages
(
self
):
self
.
setup_thread
(
Thread
(
id
=
"50_responses"
),
50
)
page
=
DiscussionSingleThreadPage
(
self
.
browser
,
self
.
course_id
,
"50_responses"
)
page
.
visit
()
self
.
assertEqual
(
page
.
get_response_total_text
(),
"50 responses"
)
...
...
@@ -52,6 +62,7 @@ class DiscussionSingleThreadTest(UniqueCourseTest):
self
.
assertEqual
(
page
.
get_load_responses_button_text
(),
None
)
def
test_three_response_pages
(
self
):
self
.
setup_thread
(
Thread
(
id
=
"150_responses"
),
150
)
page
=
DiscussionSingleThreadPage
(
self
.
browser
,
self
.
course_id
,
"150_responses"
)
page
.
visit
()
self
.
assertEqual
(
page
.
get_response_total_text
(),
"150 responses"
)
...
...
@@ -70,12 +81,57 @@ class DiscussionSingleThreadTest(UniqueCourseTest):
self
.
assertEqual
(
page
.
get_load_responses_button_text
(),
None
)
def
test_add_response_button
(
self
):
self
.
setup_thread
(
Thread
(
id
=
"5_responses"
),
5
)
page
=
DiscussionSingleThreadPage
(
self
.
browser
,
self
.
course_id
,
"5_responses"
)
page
.
visit
()
self
.
assertTrue
(
page
.
has_add_response_button
())
page
.
click_add_response_button
()
def
test_add_response_button_closed_thread
(
self
):
self
.
setup_thread
(
Thread
(
id
=
"5_responses_closed"
,
closed
=
True
),
5
)
page
=
DiscussionSingleThreadPage
(
self
.
browser
,
self
.
course_id
,
"5_responses_closed"
)
page
.
visit
()
self
.
assertFalse
(
page
.
has_add_response_button
())
class
DiscussionCommentDeletionTest
(
UniqueCourseTest
):
"""
Tests for deleting comments displayed beneath responses in the single thread view.
"""
def
setUp
(
self
):
super
(
DiscussionCommentDeletionTest
,
self
)
.
setUp
()
# Create a course to register for
CourseFixture
(
**
self
.
course_info
)
.
install
()
def
setup_user
(
self
,
roles
=
[]):
roles_str
=
','
.
join
(
roles
)
self
.
user_id
=
AutoAuthPage
(
self
.
browser
,
course_id
=
self
.
course_id
,
roles
=
roles_str
)
.
visit
()
.
get_user_id
()
def
setup_view
(
self
):
view
=
SingleThreadViewFixture
(
Thread
(
id
=
"comment_deletion_test_thread"
))
view
.
addResponse
(
Response
(
id
=
"response1"
),
[
Comment
(
id
=
"comment_other_author"
,
user_id
=
"other"
),
Comment
(
id
=
"comment_self_author"
,
user_id
=
self
.
user_id
)])
view
.
push
()
def
test_comment_deletion_as_student
(
self
):
self
.
setup_user
()
self
.
setup_view
()
page
=
DiscussionSingleThreadPage
(
self
.
browser
,
self
.
course_id
,
"comment_deletion_test_thread"
)
page
.
visit
()
self
.
assertTrue
(
page
.
is_comment_deletable
(
"comment_self_author"
))
self
.
assertTrue
(
page
.
is_comment_visible
(
"comment_other_author"
))
self
.
assertFalse
(
page
.
is_comment_deletable
(
"comment_other_author"
))
page
.
delete_comment
(
"comment_self_author"
)
def
test_comment_deletion_as_moderator
(
self
):
self
.
setup_user
(
roles
=
[
'Moderator'
])
self
.
setup_view
()
page
=
DiscussionSingleThreadPage
(
self
.
browser
,
self
.
course_id
,
"comment_deletion_test_thread"
)
page
.
visit
()
self
.
assertTrue
(
page
.
is_comment_deletable
(
"comment_self_author"
))
self
.
assertTrue
(
page
.
is_comment_deletable
(
"comment_other_author"
))
page
.
delete_comment
(
"comment_self_author"
)
page
.
delete_comment
(
"comment_other_author"
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment