From 7d407939678b87bbb0e17e36defce7f08d22982c Mon Sep 17 00:00:00 2001 From: Jansen Kantor Date: Thu, 29 Oct 2020 14:55:52 -0400 Subject: [PATCH] management command to create test users (#25449) --- .../management/commands/_create_users.py | 39 ++++ .../commands/create_random_users.py | 33 +-- .../management/commands/create_test_users.py | 82 +++++++ .../tests/test_create_test_users.py | 206 ++++++++++++++++++ 4 files changed, 334 insertions(+), 26 deletions(-) create mode 100644 common/djangoapps/student/management/commands/_create_users.py create mode 100644 common/djangoapps/student/management/commands/create_test_users.py create mode 100644 common/djangoapps/student/management/tests/test_create_test_users.py diff --git a/common/djangoapps/student/management/commands/_create_users.py b/common/djangoapps/student/management/commands/_create_users.py new file mode 100644 index 0000000000..ac450db2c1 --- /dev/null +++ b/common/djangoapps/student/management/commands/_create_users.py @@ -0,0 +1,39 @@ +""" Shared behavior between create_test_users and create_random_users """ +from xmodule.modulestore.django import modulestore + +from lms.djangoapps.instructor.access import allow_access +from openedx.core.djangoapps.user_authn.views.registration_form import AccountCreationForm +from student.helpers import do_create_account +from student.models import CourseEnrollment + + +def create_users( + course_key, + user_data, + enrollment_mode=None, + course_staff=False, + activate=False +): + """Create users, enrolling them in course_key if it's not None""" + for single_user_data in user_data: + account_creation_form = AccountCreationForm( + data=single_user_data, + tos_required=False + ) + + (user, _, _) = do_create_account(account_creation_form) + + if activate: + user.is_active = True + user.save() + + if course_key is not None: + CourseEnrollment.enroll(user, course_key, mode=enrollment_mode) + if course_staff: + course = modulestore().get_course(course_key, depth=1) + allow_access(course, user, 'staff', send_email=False) + + if course_key and course_staff: + print('Created user {} as course staff'.format(user.username)) + else: + print('Created user {}'.format(user.username)) diff --git a/common/djangoapps/student/management/commands/create_random_users.py b/common/djangoapps/student/management/commands/create_random_users.py index 9dfab7976e..d77301ddb6 100644 --- a/common/djangoapps/student/management/commands/create_random_users.py +++ b/common/djangoapps/student/management/commands/create_random_users.py @@ -1,42 +1,23 @@ """ A script to create some dummy users """ - - import uuid from django.core.management.base import BaseCommand from opaque_keys.edx.keys import CourseKey -from six.moves import range -from openedx.core.djangoapps.user_authn.views.registration_form import AccountCreationForm -from student.helpers import do_create_account -from student.models import CourseEnrollment +from common.djangoapps.student.management.commands._create_users import create_users -def make_random_form(): - """ - Generate unique user data for dummy users. - """ - identification = uuid.uuid4().hex[:8] - return AccountCreationForm( - data={ +def random_user_data_generator(num_users): + for _ in range(num_users): + identification = uuid.uuid4().hex[:8] + yield { 'username': 'user_{id}'.format(id=identification), 'email': 'email_{id}@example.com'.format(id=identification), 'password': '12345', 'name': 'User {id}'.format(id=identification), - }, - tos_required=False - ) - - -def create(num, course_key): - """Create num users, enrolling them in course_key if it's not None""" - for __ in range(num): - (user, _, _) = do_create_account(make_random_form()) - if course_key is not None: - CourseEnrollment.enroll(user, course_key) - print('Created user {}'.format(user.username)) + } class Command(BaseCommand): @@ -61,4 +42,4 @@ Examples: def handle(self, *args, **options): num = options['num_users'] course_key = CourseKey.from_string(options['course_key']) if options['course_key'] else None - create(num, course_key) + create_users(course_key, random_user_data_generator(num)) diff --git a/common/djangoapps/student/management/commands/create_test_users.py b/common/djangoapps/student/management/commands/create_test_users.py new file mode 100644 index 0000000000..6c7afddbc6 --- /dev/null +++ b/common/djangoapps/student/management/commands/create_test_users.py @@ -0,0 +1,82 @@ +""" Management command to create test users """ +from django.core.management.base import BaseCommand +from opaque_keys.edx.keys import CourseKey + +from course_modes.models import CourseMode +from common.djangoapps.student.management.commands._create_users import create_users + + +def user_info_generator(usernames, password, domain): + for username in usernames: + yield { + 'username': username, + 'email': '{username}@{domain}'.format(username=username, domain=domain), + 'password': password, + 'name': username, + } + + +class Command(BaseCommand): + """ + Create test users with the given usernames and modes and enrolls them in the given course. + + Usage: create_test_users.py username1 ... usernameN [--course] [--mode] [--password] [--domain] [--course_staff] + + Examples: + create_test_users.py + create_test_users.py user1 --course MITx/6.002x/2012_Fall --domain testuniversity.edu + create_test_users.py testmasters1 testmasters2 --course HarvardX/CS50x/2012 --mode masters + create_test_users.py testcoursestaff1 testcoursestaff2 --course DemoX/MS12/1 --course_staff --password testpassword + """ + + def add_arguments(self, parser): + parser.add_argument( + 'usernames', + help='Usernames to use for created users.', + nargs='+' + ) + parser.add_argument( + '--course', + help='Add newly created users to this course', + type=CourseKey.from_string + ) + parser.add_argument( + '--mode', + help='The enrollment mode for the test users. If --course is not provided, this is ignored', + default='audit', + choices=CourseMode.ALL_MODES + ) + parser.add_argument( + '--password', + help='Password to use for all created users.', + default='12345' + ) + parser.add_argument( + '--domain', + help='Domain for email addresses for created accounts', + default='example.com' + ) + parser.add_argument( + '--course_staff', + help=( + 'If present, users are created as course staff. --mode, if specified, is ignored. ' + 'If --course is not provided, this is ignored' + ), + action='store_true' + ) + + def handle(self, *args, **options): + course_key = options['course'] + course_staff = options['course_staff'] if course_key else None + enrollment_mode = options['mode'] if course_key and not course_staff else None + create_users( + course_key, + user_info_generator( + options['usernames'], + options['password'], + options['domain'] + ), + enrollment_mode=enrollment_mode, + course_staff=course_staff, + activate=True + ) diff --git a/common/djangoapps/student/management/tests/test_create_test_users.py b/common/djangoapps/student/management/tests/test_create_test_users.py new file mode 100644 index 0000000000..1aed1c96c4 --- /dev/null +++ b/common/djangoapps/student/management/tests/test_create_test_users.py @@ -0,0 +1,206 @@ +""" +Test the create_random_users command line script +""" + +import ddt +import pytest +from django.contrib.auth import get_user_model +from django.core.exceptions import ValidationError +from django.core.management import call_command +from django.core.management.base import CommandError +from opaque_keys import InvalidKeyError +from six import text_type + +from student.helpers import AccountValidationError +from student.models import CourseAccessRole, CourseEnrollment +from student.roles import CourseStaffRole +from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory + + +@ddt.ddt +class CreateTestUsersTestCase(SharedModuleStoreTestCase): + """ + Test creating users via command line, with various options + """ + def setUp(self): + super().setUp() + self.course = CourseFactory.create() + self.course_id = text_type(self.course.id) + self.user_model = get_user_model() + self.num_users_start = len(self.user_model.objects.all()) + + def call_command(self, users, course=None, mode=None, password=None, domain=None, course_staff=False): + """ Helper method to call the management command with various arguments """ + args = ['create_test_users'] + args.extend(users) + if course: + args.extend(['--course', course]) + if mode: + args.extend(['--mode', mode]) + if password: + args.extend(['--password', password]) + if domain: + args.extend(['--domain', domain]) + if course_staff: + args.append('--course_staff') + call_command(*args) + + def test_create_users(self): + """ + Calls the command with a list of usernames to create users. + """ + usernames = ['test_create_users_u1', 'test_create_users_u2'] + + # Check users don't already exist + self.assertEqual(self.user_model.objects.filter(username__in=usernames).count(), 0) + + self.call_command(usernames) + + # Verify users were created, are active, and were created with default settings + users = self.user_model.objects.filter(username__in=usernames).all() + self.assertEqual(len(users), len(usernames)) + for user in users: + self.assertTrue(user.is_active) + self.assertEqual(user.email, '{}@example.com'.format(user.username)) + self.assertTrue(self.client.login(username=user.username, password='12345')) + + self.assertFalse(CourseEnrollment.objects.filter(user__in=users).exists()) + + def test_create_user__username_taken(self): + """ + Try to create a user with a taken username + """ + username = 'user1' + # Create a user with the given username but a different email + self.user_model.objects.create_user(username, 'taken_email@example.com', '12345') + with self.assertRaisesMessage(AccountValidationError, "'user1' already exists"): + self.call_command([username]) + + def test_create_user_with_course(self): + """ + Create users and have them enroll in a course + """ + usernames = ['username1', 'username2'] + self.call_command(usernames, course=self.course_id) + + # Check that the users exist and were enrolled in the course with the default settings + users = self.user_model.objects.filter(username__in=usernames).all() + self.assertEqual(len(users), len(usernames)) + for user in users: + enrollment = CourseEnrollment.get_enrollment(user, self.course.id) + self.assertEqual(enrollment.mode, 'audit') + self.assertFalse(CourseStaffRole(self.course.id).has_user(user)) + + def test_create_user_with_course__bad_course(self): + """ + The test passes in a bad course id, no users or CourseEnrollments should be created + """ + with pytest.raises(InvalidKeyError): + self.call_command(['username1'], course='invalid_course_id') + + # Verify no users have been created + self.assertEqual(self.num_users_start, len(self.user_model.objects.all())) + # Verify that no one is enrolled in the course + self.assertEqual(len(CourseEnrollment.objects.filter(course__id=self.course.id)), 0) + + def test_create_user__mode(self): + """ + Create a test for a user in verified mode. + """ + # Create a user in verified rather than default audit + username = 'user1' + self.call_command([username], course=self.course_id, mode='verified') + + # Verify enrollment + user = self.user_model.objects.get(username='user1') + enrollment = CourseEnrollment.get_enrollment(user, self.course.id) + self.assertEqual(enrollment.mode, 'verified') + + def test_create_user__mode__invalid(self): + """ + Create a test for a user in an invalid mode. + """ + username = 'user1' + with self.assertRaisesMessage(CommandError, "argument --mode: invalid choice: 'cataclysmic'"): + self.call_command([username], course=self.course_id, mode='cataclysmic') + + def test_create_user__domain(self): + """ + Create a test user with a specific email domain + """ + username = 'user1' + domain = 'another-super-example.horse' + self.call_command([username], domain=domain) + + user = self.user_model.objects.get(username=username) + self.assertEqual(user.email, '{}@{}'.format(username, domain)) + + def test_create_user__email_taken(self): + """ + Try to create a user with a taken email + """ + existing_username = 'some-username' + self.user_model.objects.create_user(existing_username, 'taken_email@example.com', '12345') + with self.assertRaises(ValidationError): + self.call_command(['taken_email'], domain='example.com') + + def test_create_user__bad_domain(self): + """ + Try to create a user with a bad email domain + """ + username = 'user1' + with self.assertRaises(ValidationError): + self.call_command([username], domain='this-aint-no-domain') + self.assertFalse(self.user_model.objects.filter(username=username).exists()) + + def test_create_user__password(self): + """ + Create test user with specified password + """ + username = 'user1' + password = 'somepassword1234512341241234' + self.call_command([username], password=password) + + self.assertTrue(self.client.login(username=username, password=password)) + + def test_create_user__password__error(self): + """ + Try to create user with a password that's too short + """ + self.call_command(['user1'], password='a') + + def test_create_user__course_staff(self): + """ + Create a user and set them as course staff + """ + username = 'user1' + self.call_command([username], course=self.course_id, course_staff=True) + + user = self.user_model.objects.get(username=username) + enrollment = CourseEnrollment.get_enrollment(user, self.course.id) + self.assertEqual(enrollment.mode, 'audit') + self.assertTrue(CourseStaffRole(self.course.id).has_user(user)) + + def test_create_user__course_staff__ignore_mode(self): + """ + Test that mode is ignored when --course_staff is specified + """ + username = 'user1' + self.call_command([username], course=self.course_id, course_staff=True, mode='verified') + + user = self.user_model.objects.get(username=username) + enrollment = CourseEnrollment.get_enrollment(user, self.course.id) + self.assertEqual(enrollment.mode, 'audit') + self.assertTrue(CourseStaffRole(self.course.id).has_user(user)) + + def test_create_user__ignore_course_staff_and_mode_when_no_course(self): + """ + Test that --course_staff and --mode are ignored when there is no --course + """ + username = 'user1' + self.call_command([username], course_staff=True, mode='verified') + + user = self.user_model.objects.get(username=username) + self.assertFalse(CourseAccessRole.objects.filter(user=user).exists()) + self.assertFalse(CourseEnrollment.objects.filter(user=user).exists())