Commit 2f97ff0a by Don Mitchell Committed by Zia Fazal

Convert acceptance to opaque keys

parent b872aade
...@@ -3,22 +3,20 @@ ...@@ -3,22 +3,20 @@
from lettuce import world, step from lettuce import world, step
from lettuce.django import django_url from lettuce.django import django_url
from course_modes.models import CourseMode
from nose.tools import assert_equal from nose.tools import assert_equal
UPSELL_LINK_CSS = '.message-upsell a.action-upgrade[href*="edx/999/Certificates"]'
def create_cert_course(): def create_cert_course():
world.clear_courses() world.clear_courses()
org = 'edx' org = 'edx'
number = '999' number = '999'
name = 'Certificates' name = 'Certificates'
course_id = '{org}/{number}/{name}'.format(
org=org, number=number, name=name)
world.scenario_dict['course_id'] = course_id
world.scenario_dict['COURSE'] = world.CourseFactory.create( world.scenario_dict['COURSE'] = world.CourseFactory.create(
org=org, number=number, display_name=name) org=org, number=number, display_name=name)
world.scenario_dict['course_id'] = world.scenario_dict['COURSE'].id
world.UPSELL_LINK_CSS = u'.message-upsell a.action-upgrade[href*="{}"]'.format(
world.scenario_dict['course_id']
)
honor_mode = world.CourseModeFactory.create( honor_mode = world.CourseModeFactory.create(
course_id=world.scenario_dict['course_id'], course_id=world.scenario_dict['course_id'],
...@@ -28,7 +26,7 @@ def create_cert_course(): ...@@ -28,7 +26,7 @@ def create_cert_course():
) )
verfied_mode = world.CourseModeFactory.create( verfied_mode = world.CourseModeFactory.create(
course_id=course_id, course_id=world.scenario_dict['course_id'],
mode_slug='verified', mode_slug='verified',
mode_display_name='verified cert course', mode_display_name='verified cert course',
min_price=16, min_price=16,
...@@ -38,8 +36,7 @@ def create_cert_course(): ...@@ -38,8 +36,7 @@ def create_cert_course():
def register(): def register():
url = 'courses/{org}/{number}/{name}/about'.format( url = u'courses/{}/about'.format(world.scenario_dict['course_id'])
org='edx', number='999', name='Certificates')
world.browser.visit(django_url(url)) world.browser.visit(django_url(url))
world.css_click('section.intro a.register') world.css_click('section.intro a.register')
...@@ -147,7 +144,7 @@ def approve_my_photo(step, name): ...@@ -147,7 +144,7 @@ def approve_my_photo(step, name):
# HACK: for now don't bother clicking the approve button for # HACK: for now don't bother clicking the approve button for
# id_photo, because it is sending you back to Step 1. # id_photo, because it is sending you back to Step 1.
# Come back and figure it out later. JZ Aug 29 2013 # Come back and figure it out later. JZ Aug 29 2013
if name=='face': if name == 'face':
world.css_click(button_css[name]) world.css_click(button_css[name])
# Make sure you didn't advance the carousel # Make sure you didn't advance the carousel
...@@ -234,27 +231,27 @@ def navigate_to_my_dashboard(step): ...@@ -234,27 +231,27 @@ def navigate_to_my_dashboard(step):
@step(u'I see the course on my dashboard') @step(u'I see the course on my dashboard')
def see_the_course_on_my_dashboard(step): def see_the_course_on_my_dashboard(step):
course_link_css = 'section.my-courses a[href*="edx/999/Certificates"]' course_link_css = u'section.my-courses a[href*="{}"]'.format(world.scenario_dict['course_id'])
assert world.is_css_present(course_link_css) assert world.is_css_present(course_link_css)
@step(u'I see the upsell link on my dashboard') @step(u'I see the upsell link on my dashboard')
def see_upsell_link_on_my_dashboard(step): def see_upsell_link_on_my_dashboard(step):
course_link_css = UPSELL_LINK_CSS course_link_css = world.UPSELL_LINK_CSS
assert world.is_css_present(course_link_css) assert world.is_css_present(course_link_css)
@step(u'I do not see the upsell link on my dashboard') @step(u'I do not see the upsell link on my dashboard')
def see_upsell_link_on_my_dashboard(step): def see_no_upsell_link(step):
course_link_css = UPSELL_LINK_CSS course_link_css = world.UPSELL_LINK_CSS
assert world.is_css_not_present(course_link_css) assert world.is_css_not_present(course_link_css)
@step(u'I select the upsell link on my dashboard') @step(u'I select the upsell link on my dashboard')
def see_upsell_link_on_my_dashboard(step): def select_upsell_link_on_my_dashboard(step):
# expand the upsell section # expand the upsell section
world.css_click('.message-upsell') world.css_click('.message-upsell')
course_link_css = UPSELL_LINK_CSS course_link_css = world.UPSELL_LINK_CSS
# click the actual link # click the actual link
world.css_click(course_link_css) world.css_click(course_link_css)
...@@ -267,7 +264,7 @@ def see_that_i_am_on_the_verified_track(step): ...@@ -267,7 +264,7 @@ def see_that_i_am_on_the_verified_track(step):
@step(u'I leave the flow and return$') @step(u'I leave the flow and return$')
def leave_the_flow_and_return(step): def leave_the_flow_and_return(step):
world.visit('verify_student/verified/edx/999/Certificates/') world.visit(u'verify_student/verified/{}/'.format(world.scenario_dict['course_id']))
@step(u'I am at the verified page$') @step(u'I am at the verified page$')
......
...@@ -11,7 +11,6 @@ from django.contrib.auth.models import User ...@@ -11,7 +11,6 @@ from django.contrib.auth.models import User
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from student.models import CourseEnrollment from student.models import CourseEnrollment
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
from courseware.courses import get_course_by_id from courseware.courses import get_course_by_id
from xmodule import seq_module, vertical_module from xmodule import seq_module, vertical_module
......
...@@ -7,6 +7,7 @@ from nose.tools import assert_in, assert_true # pylint: disable=no-name-in-modu ...@@ -7,6 +7,7 @@ from nose.tools import assert_in, assert_true # pylint: disable=no-name-in-modu
from common import i_am_registered_for_the_course, visit_scenario_item from common import i_am_registered_for_the_course, visit_scenario_item
from problems_setup import add_problem_to_course, answer_problem from problems_setup import add_problem_to_course, answer_problem
@steps @steps
class ConditionalSteps(object): class ConditionalSteps(object):
COURSE_NUM = 'test_course' COURSE_NUM = 'test_course'
......
...@@ -44,7 +44,7 @@ Feature: LMS.LTI component ...@@ -44,7 +44,7 @@ Feature: LMS.LTI component
Scenario: Graded LTI component in LMS is correctly works Scenario: Graded LTI component in LMS is correctly works
Given the course has correct LTI credentials with registered Instructor Given the course has correct LTI credentials with registered Instructor
And the course has an LTI component with correct fields: And the course has an LTI component with correct fields:
| open_in_a_new_page | weight | is_graded | has_score | | open_in_a_new_page | weight | graded | has_score |
| False | 10 | True | True | | False | 10 | True | True |
And I submit answer to LTI 1 question And I submit answer to LTI 1 question
And I click on the "Progress" tab And I click on the "Progress" tab
...@@ -71,7 +71,7 @@ Feature: LMS.LTI component ...@@ -71,7 +71,7 @@ Feature: LMS.LTI component
Scenario: Graded LTI component in LMS is correctly works with beta testers Scenario: Graded LTI component in LMS is correctly works with beta testers
Given the course has correct LTI credentials with registered BetaTester Given the course has correct LTI credentials with registered BetaTester
And the course has an LTI component with correct fields: And the course has an LTI component with correct fields:
| open_in_a_new_page | weight | is_graded | has_score | | open_in_a_new_page | weight | graded | has_score |
| False | 10 | True | True | | False | 10 | True | True |
And I submit answer to LTI 1 question And I submit answer to LTI 1 question
And I click on the "Progress" tab And I click on the "Progress" tab
...@@ -82,7 +82,7 @@ Feature: LMS.LTI component ...@@ -82,7 +82,7 @@ Feature: LMS.LTI component
Scenario: Graded LTI component in LMS is correctly works with LTI2v0 PUT callback Scenario: Graded LTI component in LMS is correctly works with LTI2v0 PUT callback
Given the course has correct LTI credentials with registered Instructor Given the course has correct LTI credentials with registered Instructor
And the course has an LTI component with correct fields: And the course has an LTI component with correct fields:
| open_in_a_new_page | weight | is_graded | has_score | | open_in_a_new_page | weight | graded | has_score |
| False | 10 | True | True | | False | 10 | True | True |
And I submit answer to LTI 2 question And I submit answer to LTI 2 question
And I click on the "Progress" tab And I click on the "Progress" tab
...@@ -101,7 +101,7 @@ Feature: LMS.LTI component ...@@ -101,7 +101,7 @@ Feature: LMS.LTI component
Scenario: Graded LTI component in LMS is correctly works with LTI2v0 PUT delete callback Scenario: Graded LTI component in LMS is correctly works with LTI2v0 PUT delete callback
Given the course has correct LTI credentials with registered Instructor Given the course has correct LTI credentials with registered Instructor
And the course has an LTI component with correct fields: And the course has an LTI component with correct fields:
| open_in_a_new_page | weight | is_graded | has_score | | open_in_a_new_page | weight | graded | has_score |
| False | 10 | True | True | | False | 10 | True | True |
And I submit answer to LTI 2 question And I submit answer to LTI 2 question
And I visit the LTI component And I visit the LTI component
......
...@@ -13,7 +13,7 @@ from courseware.tests.factories import InstructorFactory, BetaTesterFactory ...@@ -13,7 +13,7 @@ from courseware.tests.factories import InstructorFactory, BetaTesterFactory
from courseware.access import has_access from courseware.access import has_access
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from common import course_id, visit_scenario_item from common import visit_scenario_item
@step('I view the LTI and error is shown$') @step('I view the LTI and error is shown$')
...@@ -29,7 +29,7 @@ def lti_is_not_rendered(_step): ...@@ -29,7 +29,7 @@ def lti_is_not_rendered(_step):
def check_lti_iframe_content(text): def check_lti_iframe_content(text):
#inside iframe test content is presented # inside iframe test content is presented
location = world.scenario_dict['LTI'].location.html_id() location = world.scenario_dict['LTI'].location.html_id()
iframe_name = 'ltiFrame-' + location iframe_name = 'ltiFrame-' + location
with world.browser.get_iframe(iframe_name) as iframe: with world.browser.get_iframe(iframe_name) as iframe:
...@@ -95,7 +95,7 @@ def incorrect_lti_is_rendered(_step): ...@@ -95,7 +95,7 @@ def incorrect_lti_is_rendered(_step):
assert not world.is_css_present('.link_lti_new_window', wait_time=0) assert not world.is_css_present('.link_lti_new_window', wait_time=0)
assert not world.is_css_present('.error_message', wait_time=0) assert not world.is_css_present('.error_message', wait_time=0)
#inside iframe test content is presented # inside iframe test content is presented
check_lti_iframe_content("Wrong LTI signature") check_lti_iframe_content("Wrong LTI signature")
...@@ -119,7 +119,7 @@ def set_incorrect_lti_passport(_step): ...@@ -119,7 +119,7 @@ def set_incorrect_lti_passport(_step):
i_am_registered_for_the_course(coursenum, metadata) i_am_registered_for_the_course(coursenum, metadata)
@step('the course has an LTI component with (.*) fields(?:\:)?$') #, new_page is(.*), is_graded is(.*) @step('the course has an LTI component with (.*) fields(?:\:)?$') # , new_page is(.*), graded is(.*)
def add_correct_lti_to_course(_step, fields): def add_correct_lti_to_course(_step, fields):
category = 'lti' category = 'lti'
metadata = { metadata = {
...@@ -176,7 +176,6 @@ def create_course_for_lti(course, metadata): ...@@ -176,7 +176,6 @@ def create_course_for_lti(course, metadata):
}, },
] ]
} }
metadata.update(grading_policy)
# Create the course # Create the course
# We always use the same org and display name, # We always use the same org and display name,
...@@ -186,17 +185,7 @@ def create_course_for_lti(course, metadata): ...@@ -186,17 +185,7 @@ def create_course_for_lti(course, metadata):
number=course, number=course,
display_name='Test Course', display_name='Test Course',
metadata=metadata, metadata=metadata,
grading_policy={ grading_policy=grading_policy,
"GRADER": [
{
"type": "Homework",
"min_count": 1,
"drop_count": 0,
"short_label": "HW",
"weight": weight
},
]
},
) )
# Add a section to the course to contain problems # Add a section to the course to contain problems
...@@ -248,7 +237,7 @@ def check_lti_popup(parent_window): ...@@ -248,7 +237,7 @@ def check_lti_popup(parent_window):
assert len(world.browser.windows) != 1 assert len(world.browser.windows) != 1
for window in world.browser.windows: for window in world.browser.windows:
world.browser.switch_to_window(window) # Switch to a different window (the pop-up) world.browser.switch_to_window(window) # Switch to a different window (the pop-up)
# Check if this is the one we want by comparing the url # Check if this is the one we want by comparing the url
url = world.browser.url url = world.browser.url
basename = os.path.basename(url) basename = os.path.basename(url)
...@@ -260,8 +249,8 @@ def check_lti_popup(parent_window): ...@@ -260,8 +249,8 @@ def check_lti_popup(parent_window):
assert result == u'This is LTI tool. Success.' assert result == u'This is LTI tool. Success.'
world.browser.driver.close() # Close the pop-up window world.browser.driver.close() # Close the pop-up window
world.browser.switch_to_window(parent_window) # Switch to the main window again world.browser.switch_to_window(parent_window) # Switch to the main window again
def click_and_check_lti_popup(): def click_and_check_lti_popup():
...@@ -314,7 +303,7 @@ def see_value_in_the_gradebook(_step, label, text): ...@@ -314,7 +303,7 @@ def see_value_in_the_gradebook(_step, label, text):
for i, element in enumerate(table_headers): for i, element in enumerate(table_headers):
if element.text.strip() == label: if element.text.strip() == label:
index = i index = i
break; break
assert_true(world.css_has_text('{0} tbody td'.format(table_selector), text, index=index)) assert_true(world.css_has_text('{0} tbody td'.format(table_selector), text, index=index))
......
...@@ -2,10 +2,9 @@ ...@@ -2,10 +2,9 @@
# pylint: disable=W0621 # pylint: disable=W0621
from lettuce import world, step from lettuce import world, step
from common import course_id, course_location from common import course_location
from problems_setup import PROBLEM_DICT from problems_setup import PROBLEM_DICT
from nose.tools import assert_in from nose.tools import assert_in
from opaque_keys.edx.locations import SlashSeparatedCourseKey
@step(u'I am viewing a course with multiple sections') @step(u'I am viewing a course with multiple sections')
...@@ -149,9 +148,9 @@ def create_course(): ...@@ -149,9 +148,9 @@ def create_course():
def create_user_and_visit_course(): def create_user_and_visit_course():
world.register_by_course_key(SlashSeparatedCourseKey('edx', '999', 'Test_Course')) world.register_by_course_key(world.scenario_dict['COURSE'].id)
world.log_in() world.log_in()
world.visit('/courses/edx/999/Test_Course/courseware/') world.visit(u'/courses/{}/courseware/'.format(world.scenario_dict['COURSE'].id))
def add_problem_to_course_section(parent_location, display_name): def add_problem_to_course_section(parent_location, display_name):
......
...@@ -6,10 +6,8 @@ Steps for problem.feature lettuce tests ...@@ -6,10 +6,8 @@ Steps for problem.feature lettuce tests
# pylint: disable=W0621 # pylint: disable=W0621
from lettuce import world, step from lettuce import world, step
from lettuce.django import django_url
from common import i_am_registered_for_the_course, visit_scenario_item from common import i_am_registered_for_the_course, visit_scenario_item
from problems_setup import PROBLEM_DICT, answer_problem, problem_has_answer, add_problem_to_course from problems_setup import PROBLEM_DICT, answer_problem, problem_has_answer, add_problem_to_course
from nose.tools import assert_equal
def _view_problem(step, problem_type, problem_settings=None): def _view_problem(step, problem_type, problem_settings=None):
......
# pylint: disable=C0111 # pylint: disable=C0111
# pylint: disable=W0621 # pylint: disable=W0621
#EVERY PROBLEM TYPE MUST HAVE THE FOLLOWING: # EVERY PROBLEM TYPE MUST HAVE THE FOLLOWING:
# -Section in Dictionary containing: # -Section in Dictionary containing:
# -factory # -factory
# -kwargs # -kwargs
...@@ -187,7 +187,9 @@ def answer_problem(course, problem_type, correctness): ...@@ -187,7 +187,9 @@ def answer_problem(course, problem_type, correctness):
section_loc = section_location(course) section_loc = section_location(course)
if problem_type == "drop down": if problem_type == "drop down":
select_name = "input_i4x-{0.org}-{0.course}-problem-drop_down_2_1".format(section_loc) select_name = "input_{}_2_1".format(
section_loc.course_key.make_usage_key('problem', 'drop_down').html_id()
)
option_text = 'Option 2' if correctness == 'correct' else 'Option 3' option_text = 'Option 2' if correctness == 'correct' else 'Option 3'
world.select_option(select_name, option_text) world.select_option(select_name, option_text)
...@@ -263,8 +265,9 @@ def answer_problem(course, problem_type, correctness): ...@@ -263,8 +265,9 @@ def answer_problem(course, problem_type, correctness):
offset = 25 if correctness == "correct" else -25 offset = 25 if correctness == "correct" else -25
def try_click(): def try_click():
image_selector = "#imageinput_i4x-{0.org}-{0.course}-problem-image_2_1".format(section_loc) problem_html_loc = section_loc.course_key.make_usage_key('problem', 'image').html_id()
input_selector = "#input_i4x-{0.org}-{0.course}-problem-image_2_1".format(section_loc) image_selector = "#imageinput_{}_2_1".format(problem_html_loc)
input_selector = "#input_{}_2_1".format(problem_html_loc)
world.browser.execute_script('$("body").on("click", function(event) {console.log(event);})') world.browser.execute_script('$("body").on("click", function(event) {console.log(event);})')
...@@ -385,16 +388,15 @@ def inputfield(course, problem_type, choice=None, input_num=1): ...@@ -385,16 +388,15 @@ def inputfield(course, problem_type, choice=None, input_num=1):
section_loc = section_location(course) section_loc = section_location(course)
ptype = problem_type.replace(" ", "_")
# this is necessary due to naming requirement for this problem type # this is necessary due to naming requirement for this problem type
if problem_type in ("radio_text", "checkbox_text"): if problem_type in ("radio_text", "checkbox_text"):
selector_template = "input#i4x-{org}-{course}-problem-{ptype}_2_{input}" selector_template = "input#{}_2_{input}"
else: else:
selector_template = "input#input_i4x-{org}-{course}-problem-{ptype}_2_{input}" selector_template = "input#input_{}_2_{input}"
sel = selector_template.format( sel = selector_template.format(
org=section_loc.org, section_loc.course_key.make_usage_key('problem', ptype).html_id(),
course=section_loc.course,
ptype=problem_type.replace(" ", "_"),
input=input_num, input=input_num,
) )
......
...@@ -7,7 +7,7 @@ Define steps for bulk email acceptance test. ...@@ -7,7 +7,7 @@ Define steps for bulk email acceptance test.
from lettuce import world, step from lettuce import world, step
from lettuce.django import mail from lettuce.django import mail
from nose.tools import assert_in, assert_true, assert_equal # pylint: disable=E0611 from nose.tools import assert_in, assert_equal # pylint: disable=E0611
from django.core.management import call_command from django.core.management import call_command
from django.conf import settings from django.conf import settings
...@@ -115,8 +115,9 @@ def when_i_send_an_email(step, recipient): # pylint: disable=unused-argument ...@@ -115,8 +115,9 @@ def when_i_send_an_email(step, recipient): # pylint: disable=unused-argument
call_command('loaddata', 'course_email_template.json') call_command('loaddata', 'course_email_template.json')
# Go to the email section of the instructor dash # Go to the email section of the instructor dash
world.visit('/courses/edx/888/Bulk_Email_Test_Course') url = '/courses/{}'.format(world.bulk_email_course_key)
world.css_click('a[href="/courses/edx/888/Bulk_Email_Test_Course/instructor"]') world.visit(url)
world.css_click('a[href="{}/instructor"]'.format(url))
world.css_click('a[data-section="send_email"]') world.css_click('a[data-section="send_email"]')
# Select the recipient # Select the recipient
......
...@@ -7,7 +7,6 @@ Define common steps for instructor dashboard acceptance tests. ...@@ -7,7 +7,6 @@ Define common steps for instructor dashboard acceptance tests.
from __future__ import absolute_import from __future__ import absolute_import
from django.conf import settings
from lettuce import world, step from lettuce import world, step
from mock import patch from mock import patch
from nose.tools import assert_in # pylint: disable=E0611 from nose.tools import assert_in # pylint: disable=E0611
...@@ -75,8 +74,8 @@ def i_am_staff_or_instructor(step, role): # pylint: disable=unused-argument ...@@ -75,8 +74,8 @@ def i_am_staff_or_instructor(step, role): # pylint: disable=unused-argument
def go_to_section(section_name): def go_to_section(section_name):
# section name should be one of # section name should be one of
# course_info, membership, student_admin, data_download, analytics, send_email # course_info, membership, student_admin, data_download, analytics, send_email
world.visit('/courses/edx/999/Test_Course') world.visit(u'/courses/{}'.format(world.course_key))
world.css_click('a[href="/courses/edx/999/Test_Course/instructor"]') world.css_click(u'a[href="/courses/{}/instructor"]'.format(world.course_key))
world.css_click('a[data-section="{0}"]'.format(section_name)) world.css_click('a[data-section="{0}"]'.format(section_name))
......
...@@ -9,7 +9,7 @@ acceptance tests. ...@@ -9,7 +9,7 @@ acceptance tests.
from lettuce import world, step from lettuce import world, step
from nose.tools import assert_in, assert_regexp_matches # pylint: disable=E0611 from nose.tools import assert_in, assert_regexp_matches # pylint: disable=E0611
from terrain.steps import reload_the_page from terrain.steps import reload_the_page
from splinter.request_handler.request_handler import RequestHandler from django.utils import http
@step(u'I see a table of student profiles') @step(u'I see a table of student profiles')
...@@ -60,11 +60,11 @@ Graded sections: ...@@ -60,11 +60,11 @@ Graded sections:
subgrader=<class 'xmodule.graders.AssignmentFormatGrader'>, type=Midterm Exam, category=Midterm Exam, weight=0.3 subgrader=<class 'xmodule.graders.AssignmentFormatGrader'>, type=Midterm Exam, category=Midterm Exam, weight=0.3
subgrader=<class 'xmodule.graders.AssignmentFormatGrader'>, type=Final Exam, category=Final Exam, weight=0.4 subgrader=<class 'xmodule.graders.AssignmentFormatGrader'>, type=Final Exam, category=Final Exam, weight=0.4
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
Listing grading context for course edx/999/Test_Course Listing grading context for course {}
graded sections: graded sections:
[] []
all descriptors: all descriptors:
length=0""" length=0""".format(world.course_key)
assert_in(expected_config, world.css_text('#data-grade-config-text')) assert_in(expected_config, world.css_text('#data-grade-config-text'))
...@@ -73,7 +73,8 @@ def verify_report_is_generated(report_name_substring): ...@@ -73,7 +73,8 @@ def verify_report_is_generated(report_name_substring):
reload_the_page(step) reload_the_page(step)
world.wait_for_visible('#report-downloads-table') world.wait_for_visible('#report-downloads-table')
# Find table and assert a .csv file is present # Find table and assert a .csv file is present
expected_file_regexp = 'edx_999_Test_Course_{0}_'.format(report_name_substring) + '\d{4}-\d{2}-\d{2}-\d{4}\.csv' quoted_id = http.urlquote(world.course_key).replace('/', '_')
expected_file_regexp = quoted_id + '_grade_report_\d{4}-\d{2}-\d{2}-\d{4}\.csv'
assert_regexp_matches( assert_regexp_matches(
world.css_html('#report-downloads-table'), expected_file_regexp, world.css_html('#report-downloads-table'), expected_file_regexp,
msg="Expected report filename was not found." msg="Expected report filename was not found."
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment