Add tests to cover more use cases

This commit is contained in:
Ramon Wenger 2021-03-23 14:09:08 +01:00
parent 43b9f61132
commit 5e5b413afb
7 changed files with 262 additions and 56 deletions

View File

@ -47,7 +47,7 @@ describe('Apply module visibility', () => {
});
});
it('needs to be implemented', () => {
it('clicks through the UI', () => {
// Cypress.config({
// baseUrl: 'http://localhost:8080',
// });

View File

@ -7,17 +7,17 @@ from users.mutations_public import UserMutations
from registration.mutations_public import RegistrationMutations
class Mutation(UserMutations, RegistrationMutations, graphene.ObjectType):
class PublicMutation(UserMutations, RegistrationMutations, graphene.ObjectType):
if settings.DEBUG:
debug = graphene.Field(DjangoDebug, name='_debug')
class Query(AllNewsTeasersQuery, graphene.ObjectType):
class PublicQuery(AllNewsTeasersQuery, graphene.ObjectType):
node = graphene.relay.Node.Field()
if settings.DEBUG:
debug = graphene.Field(DjangoDebug, name='_debug')
schema = graphene.Schema(mutation=Mutation, query=Query)
schema = graphene.Schema(mutation=PublicMutation, query=PublicQuery)

View File

@ -37,3 +37,23 @@ class Chapter(StrictHierarchyPage):
title_hidden_for = models.ManyToManyField(SchoolClass, related_name='hidden_chapter_titles')
description_hidden_for = models.ManyToManyField(SchoolClass, related_name='hidden_chapter_descriptions')
def sync_title_visibility(self, school_class_template, school_class_to_sync):
if self.title_hidden_for.filter(id=school_class_template.id).exists() \
and not self.title_hidden_for.filter(id=school_class_to_sync.id).exists():
self.title_hidden_for.add(school_class_to_sync)
if self.title_hidden_for.filter(
id=school_class_to_sync.id).exists() \
and not self.title_hidden_for.filter(id=school_class_template.id).exists():
self.title_hidden_for.remove(school_class_to_sync)
def sync_description_visibility(self, school_class_template, school_class_to_sync):
if self.description_hidden_for.filter(id=school_class_template.id).exists() \
and not self.description_hidden_for.filter(id=school_class_to_sync.id).exists():
self.description_hidden_for.add(school_class_to_sync)
if self.description_hidden_for.filter(
id=school_class_to_sync.id).exists() \
and not self.description_hidden_for.filter(id=school_class_template.id).exists():
self.description_hidden_for.remove(school_class_to_sync)

View File

@ -86,6 +86,16 @@ class Module(StrictHierarchyPage):
# add `school_class_to_sync` to these blocks' `visible for`
content_block.visible_for.add(school_class_to_sync)
for chapter in chapters:
chapter.sync_title_visibility(school_class_template, school_class_to_sync)
chapter.sync_description_visibility(school_class_template, school_class_to_sync)
objective_groups = self.objective_groups.all()
for objective_group in objective_groups:
objective_group.sync_visibility(school_class_template, school_class_to_sync)
class RecentModule(models.Model):
module = models.ForeignKey(Module, on_delete=models.CASCADE, related_name='recent_modules')

View File

@ -1,38 +1,86 @@
import logging
from django.test import TestCase, RequestFactory
from graphene.test import Client
from graphql_relay import to_global_id
from api.schema import schema
from books.models import ContentBlock, Chapter
from books.factories import ModuleFactory
from books.models import ContentBlock, Chapter
from objectives.factories import ObjectiveGroupFactory, ObjectiveFactory
from users.factories import SchoolClassFactory
from users.models import User
from users.services import create_users
CONTENT_BLOCK_QUERY = """
query ContentBlockQuery($id: ID!) {
contentBlock(id: $id) {
hiddenFor {
edges {
node {
id
name
}
}
}
visibleFor {
edges {
node {
id
name
}
}
}
}
}
"""
TEMPLATE_CLASS_NAME = 'template-class'
SYNC_CLASS_NAME = 'class-to-be-synced'
SCHOOL_CLASS_FRAGMENT = """
fragment SchoolClassFragment on SchoolClassNode {
id
name
}
"""
EDGES_FRAGMENT = SCHOOL_CLASS_FRAGMENT + """
fragment SchoolClassNodeFragment on SchoolClassNodeConnection {
edges {
node {
...SchoolClassFragment
}
}
}
"""
CONTENT_BLOCK_QUERY = EDGES_FRAGMENT + """
query ContentBlockQuery($id: ID!) {
contentBlock(id: $id) {
hiddenFor {
...SchoolClassNodeFragment
}
visibleFor {
...SchoolClassNodeFragment
}
}
}
"""
CHAPTER_QUERY = EDGES_FRAGMENT + """
query ChapterQuery($id: ID!) {
chapter(id: $id) {
id
titleHiddenFor {
...SchoolClassNodeFragment
}
descriptionHiddenFor {
...SchoolClassNodeFragment
}
}
}
"""
OBJECTIVE_GROUP_QUERY = EDGES_FRAGMENT + """
query ObjectiveGroupQuery($id: ID!) {
objectiveGroup(id: $id) {
hiddenFor {
...SchoolClassNodeFragment
}
objectives {
edges {
node {
id
text
hiddenFor {
...SchoolClassNodeFragment
}
visibleFor {
...SchoolClassNodeFragment
}
}
}
}
}
}
"""
SYNC_MUTATION = """
mutation SyncMutationVisibility($input: SyncModuleVisibilityInput!) {
@ -60,24 +108,29 @@ class CopyVisibilityForClassesTestCase(TestCase):
"""
def setUp(self):
module = ModuleFactory(slug='some-module')
chapter = Chapter(title='Some Chapter')
module.add_child(instance=chapter)
# create users and school classes
create_users()
teacher = User.objects.get(username='teacher')
student1 = User.objects.get(username='student1')
student2 = User.objects.get(username='student2')
# school class to be used as the pattern or model
template_school_class = SchoolClassFactory(name='template-class', users=[teacher, student1])
template_school_class = SchoolClassFactory(name=TEMPLATE_CLASS_NAME, users=[teacher, student1])
# school class to be synced, e.g. adapted to be like the other
school_class_to_be_synced = SchoolClassFactory(name='class-to-be-synced', users=[teacher, student2])
school_class_to_be_synced = SchoolClassFactory(name=SYNC_CLASS_NAME, users=[teacher, student2])
# create content
module = ModuleFactory(slug='some-module')
chapter = Chapter(title='Some Chapter')
module.add_child(instance=chapter)
default_content_block = ContentBlock(title='default block', slug='default')
hidden_content_block = ContentBlock(title='hidden block', slug='hidden')
other_hidden_content_block = ContentBlock(title='other hidden block', slug='other-hidden')
custom_content_block = ContentBlock(title='custom block', slug='custom', owner=teacher)
other_custom_content_block = ContentBlock(title='other custom block', slug='other-custom', owner=teacher)
objective_group = ObjectiveGroupFactory(module=module)
objective = ObjectiveFactory(group=objective_group)
chapter.specific.add_child(instance=default_content_block)
chapter.specific.add_child(instance=hidden_content_block)
chapter.specific.add_child(instance=custom_content_block)
@ -90,6 +143,14 @@ class CopyVisibilityForClassesTestCase(TestCase):
other_hidden_content_block.hidden_for.add(school_class_to_be_synced)
other_custom_content_block.visible_for.add(school_class_to_be_synced)
# hide chapter title and description for student 1 in template-school-class
chapter.title_hidden_for.add(template_school_class)
chapter.description_hidden_for.add(template_school_class)
# hide objectives for template-school-class
objective_group.hidden_for.add(template_school_class)
objective.hidden_for.add(template_school_class)
teacher_request = RequestFactory().get('/')
teacher_request.user = teacher
self.teacher_client = Client(schema=schema, context_value=teacher_request)
@ -102,10 +163,12 @@ class CopyVisibilityForClassesTestCase(TestCase):
student2_request.user = student2
self.student2_client = Client(schema=schema, context_value=student2_request)
# set references
self.template_school_class = template_school_class
self.school_class_to_be_synced = school_class_to_be_synced
self.module = module
self.chapter = to_global_id('ChapterNode', chapter.pk)
self.objective_group_id = to_global_id('ObjectiveGroupNode', objective_group.pk)
self.default_content_block = to_global_id('ContentBlockNode', default_content_block.pk)
self.hidden_content_block = to_global_id('ContentBlockNode', hidden_content_block.pk)
self.custom_content_block = to_global_id('ContentBlockNode', custom_content_block.pk)
@ -118,12 +181,21 @@ class CopyVisibilityForClassesTestCase(TestCase):
})
return result
def _execute_sync(self):
self.teacher_client.execute(SYNC_MUTATION, variables={
'input': {
'module': self.module.slug,
'templateSchoolClass': to_global_id('SchoolClassNode', self.template_school_class.pk),
'schoolClass': to_global_id('SchoolClassNode', self.school_class_to_be_synced.pk)
}
})
def _test_in_sync(self):
# the hidden block is hidden for both now
hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.hidden_content_block)
hidden_for = hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges')
self.assertTrue('template-class' in map(lambda x: x['node']['name'], hidden_for))
self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], hidden_for))
self.assertTrue(TEMPLATE_CLASS_NAME in map(lambda x: x['node']['name'], hidden_for))
self.assertTrue(SYNC_CLASS_NAME in map(lambda x: x['node']['name'], hidden_for))
# the other hidden block is hidden for no one now
other_hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client,
@ -139,8 +211,8 @@ class CopyVisibilityForClassesTestCase(TestCase):
# the custom block is visible for both
custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.custom_content_block)
visible_for = custom_result.get('data').get('contentBlock').get('visibleFor').get('edges')
self.assertTrue('template-class' in map(lambda x: x['node']['name'], visible_for))
self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], visible_for))
self.assertTrue(TEMPLATE_CLASS_NAME in map(lambda x: x['node']['name'], visible_for))
self.assertTrue(SYNC_CLASS_NAME in map(lambda x: x['node']['name'], visible_for))
# the other custom block is visible for no one
other_custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client,
@ -153,14 +225,14 @@ class CopyVisibilityForClassesTestCase(TestCase):
hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.hidden_content_block)
hidden_for = hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges')
self.assertTrue('template-class' in map(lambda x: x['node']['name'], hidden_for))
self.assertFalse('class-to-be-synced' in map(lambda x: x['node']['name'], hidden_for))
self.assertTrue(TEMPLATE_CLASS_NAME in map(lambda x: x['node']['name'], hidden_for))
self.assertFalse(SYNC_CLASS_NAME in map(lambda x: x['node']['name'], hidden_for))
other_hidden_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client,
self.other_hidden_content_block)
hidden_for = other_hidden_result.get('data').get('contentBlock').get('hiddenFor').get('edges')
self.assertFalse('template-class' in map(lambda x: x['node']['name'], hidden_for))
self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], hidden_for))
self.assertFalse(TEMPLATE_CLASS_NAME in map(lambda x: x['node']['name'], hidden_for))
self.assertTrue(SYNC_CLASS_NAME in map(lambda x: x['node']['name'], hidden_for))
default_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.default_content_block)
hidden_for = default_result.get('data').get('contentBlock').get('hiddenFor').get('edges')
@ -168,14 +240,14 @@ class CopyVisibilityForClassesTestCase(TestCase):
custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client, self.custom_content_block)
visible_for = custom_result.get('data').get('contentBlock').get('visibleFor').get('edges')
self.assertTrue('template-class' in map(lambda x: x['node']['name'], visible_for))
self.assertFalse('class-to-be-synced' in map(lambda x: x['node']['name'], visible_for))
self.assertTrue(TEMPLATE_CLASS_NAME in map(lambda x: x['node']['name'], visible_for))
self.assertFalse(SYNC_CLASS_NAME in map(lambda x: x['node']['name'], visible_for))
other_custom_result = self._get_result(CONTENT_BLOCK_QUERY, self.teacher_client,
self.other_custom_content_block)
visible_for = other_custom_result.get('data').get('contentBlock').get('visibleFor').get('edges')
self.assertFalse('template-class' in map(lambda x: x['node']['name'], visible_for))
self.assertTrue('class-to-be-synced' in map(lambda x: x['node']['name'], visible_for))
self.assertFalse(TEMPLATE_CLASS_NAME in map(lambda x: x['node']['name'], visible_for))
self.assertTrue(SYNC_CLASS_NAME in map(lambda x: x['node']['name'], visible_for))
def test_syncs_correctly(self):
self.module.sync_from_school_class(self.template_school_class, self.school_class_to_be_synced)
@ -183,11 +255,77 @@ class CopyVisibilityForClassesTestCase(TestCase):
self._test_in_sync()
def test_mutation(self):
self.teacher_client.execute(SYNC_MUTATION, variables={
'input': {
'module': self.module.slug,
'templateSchoolClass': to_global_id('SchoolClassNode', self.template_school_class.pk),
'schoolClass': to_global_id('SchoolClassNode', self.school_class_to_be_synced.pk)
}
})
self._execute_sync()
self._test_in_sync()
def test_chapter_visibility(self):
# chapter title and description hidden for class A
# chapter title and description visible for class B
# sync module
# chapter title and description hidden for class B
query = CHAPTER_QUERY
variables = {"id": self.chapter}
result = self.student1_client.execute(query, variables=variables)
self.assertIsNone(result.get('errors'))
chapter = result.get('data').get('chapter')
title_hidden_for = chapter.get('titleHiddenFor').get('edges')
description_hidden_for = chapter.get('descriptionHiddenFor').get('edges')
self.assertTrue(TEMPLATE_CLASS_NAME in map(lambda x: x['node']['name'], title_hidden_for))
self.assertTrue(TEMPLATE_CLASS_NAME in map(lambda x: x['node']['name'], description_hidden_for))
self.assertTrue(SYNC_CLASS_NAME not in map(lambda x: x['node']['name'], title_hidden_for))
self.assertTrue(SYNC_CLASS_NAME not in map(lambda x: x['node']['name'], description_hidden_for))
self._execute_sync()
result = self.student1_client.execute(query, variables=variables)
self.assertIsNone(result.get('errors'))
chapter = result.get('data').get('chapter')
title_hidden_for = chapter.get('titleHiddenFor').get('edges')
description_hidden_for = chapter.get('descriptionHiddenFor').get('edges')
self.assertTrue(TEMPLATE_CLASS_NAME in map(lambda x: x['node']['name'], title_hidden_for))
self.assertTrue(TEMPLATE_CLASS_NAME in map(lambda x: x['node']['name'], description_hidden_for))
self.assertTrue(SYNC_CLASS_NAME in map(lambda x: x['node']['name'], title_hidden_for))
self.assertTrue(SYNC_CLASS_NAME in map(lambda x: x['node']['name'], description_hidden_for))
def _objective_group_query(self):
query = OBJECTIVE_GROUP_QUERY
variables = {"id": self.objective_group_id}
return query, variables
def _get_objective_group(self, client, query, variables):
result = client.execute(query, variables=variables)
self.assertIsNone(result.get('errors'))
objective_group = result.get('data').get('objectiveGroup')
return objective_group
def test_objective_group_visibility(self):
query, variables = self._objective_group_query()
objective_group = self._get_objective_group(self.student1_client, query, variables)
hidden_for = objective_group.get('hiddenFor').get('edges')
self.assertTrue(TEMPLATE_CLASS_NAME in map(lambda x: x['node']['name'], hidden_for))
self.assertTrue(SYNC_CLASS_NAME not in map(lambda x: x['node']['name'], hidden_for))
self._execute_sync()
objective_group = self._get_objective_group(self.student1_client, query, variables)
hidden_for = objective_group.get('hiddenFor').get('edges')
self.assertTrue(TEMPLATE_CLASS_NAME in map(lambda x: x['node']['name'], hidden_for))
self.assertTrue(SYNC_CLASS_NAME in map(lambda x: x['node']['name'], hidden_for))
def test_objective_visibility(self):
query, variables = self._objective_group_query()
objective_group = self._get_objective_group(self.student2_client, query, variables)
objective = objective_group.get('objectives').get('edges')[0]['node']
hidden_for = objective.get('hiddenFor').get('edges')
self.assertTrue(TEMPLATE_CLASS_NAME in map(lambda x: x['node']['name'], hidden_for))
self.assertTrue(SYNC_CLASS_NAME not in map(lambda x: x['node']['name'], hidden_for))
self._execute_sync()
objective_group = self._get_objective_group(self.student2_client, query, variables)
objective = objective_group.get('objectives').get('edges')[0]['node']
hidden_for = objective.get('hiddenFor').get('edges')
self.assertTrue(TEMPLATE_CLASS_NAME in map(lambda x: x['node']['name'], hidden_for))
self.assertTrue(SYNC_CLASS_NAME in map(lambda x: x['node']['name'], hidden_for))

View File

@ -37,7 +37,7 @@ def is_private_api_call_allowed(user, body):
return True
# logout, me and coupon resources are always allowed. Even if the user has no valid license
if re.search(r"mutation\s*.*\s*logout\s*{", body_unicode) or re.search(r"query\s*.*\s*me\s*{", body_unicode)\
if re.search(r"mutation\s*.*\s*logout\s*{", body_unicode) or re.search(r"query\s*.*\s*me\s*{", body_unicode) \
or re.search(r"mutation\s*Coupon", body_unicode):
return True
@ -48,3 +48,23 @@ def is_private_api_call_allowed(user, body):
return False
return True
def sync_hidden_for(model, school_class_template, school_class_to_sync):
if model.hidden_for.filter(id=school_class_template.id).exists() and not model.hidden_for.filter(
id=school_class_to_sync.id).exists():
model.hidden_for.add(school_class_to_sync)
if model.hidden_for.filter(id=school_class_to_sync.id).exists() and not model.hidden_for.filter(
id=school_class_template.id).exists():
model.hidden_for.remove(school_class_to_sync)
def sync_visible_for(model, school_class_template, school_class_to_sync):
if model.visible_for.filter(id=school_class_template.id).exists() and not model.visible_for.filter(
id=school_class_to_sync.id).exists():
model.visible_for.add(school_class_to_sync)
if model.visible_for.filter(id=school_class_template.id).exists() and not model.visible_for.filter(
id=school_class_to_sync.id).exists():
model.visible_for.add(school_class_to_sync)

View File

@ -3,6 +3,7 @@ from django.db import models
from django.db.models import F
from books.models import Module
from core.utils import sync_visible_for, sync_hidden_for
from users.models import SchoolClass
@ -21,14 +22,27 @@ class ObjectiveGroup(models.Model):
(INTERDISCIPLINARY, 'Überfachliche Lernziele'),
)
title = models.CharField('title', blank=True, null=False, max_length=255, choices=TITLE_CHOICES, default=LANGUAGE_COMMUNICATION)
module = models.ForeignKey(Module, blank=False, null=False, on_delete=models.CASCADE, related_name='objective_groups')
title = models.CharField('title', blank=True, null=False, max_length=255, choices=TITLE_CHOICES,
default=LANGUAGE_COMMUNICATION)
module = models.ForeignKey(Module, blank=False, null=False, on_delete=models.CASCADE,
related_name='objective_groups')
hidden_for = models.ManyToManyField(SchoolClass, related_name='hidden_objective_groups', blank=True)
def __str__(self):
return '{} - {}'.format(self.module, self.title)
def sync_visibility(self, school_class_template, school_class_to_sync):
# if self.hidden_for.filter(id=school_class_template.id).exists() and not self.hidden_for.filter(id=school_class_to_sync.id).exists():
# self.hidden_for.add(school_class_to_sync)
#
# if self.hidden_for.filter(id=school_class_to_sync.id).exists() and not self.hidden_for.filter(id=school_class_template.id).exists():
# self.hidden_for.remove(school_class_to_sync)
sync_hidden_for(self, school_class_template, school_class_to_sync)
for objective in self.objectives.all():
objective.sync_visibility(school_class_template, school_class_to_sync)
class Objective(models.Model):
class Meta:
@ -46,3 +60,7 @@ class Objective(models.Model):
def __str__(self):
return 'Objective {}-{}'.format(self.id, self.text)
def sync_visibility(self, school_class_template, school_class_to_sync):
sync_hidden_for(self, school_class_template, school_class_to_sync)
sync_visible_for(self, school_class_template, school_class_to_sync)