Merged in feature/rooms-read-only (pull request #88)

Feature/rooms read only
This commit is contained in:
Ramon Wenger 2021-07-30 09:20:58 +00:00
commit b50ba068a0
27 changed files with 811 additions and 27402 deletions

View File

@ -2,6 +2,13 @@ import { GraphQLError } from 'graphql';
const schema = require('../../../fixtures/schema.json');
const redeemCoupon = coupon => {
if (coupon !== '') {
cy.get('[data-cy="coupon-input"]').type(coupon);
}
cy.get('[data-cy="coupon-button"]').click();
};
describe('Email Verifcation', () => {
beforeEach(() => {
cy.server();
@ -22,7 +29,7 @@ describe('Email Verifcation', () => {
cy.apolloLogin('rahel.cueni', 'test');
cy.visit('/license-activation');
cy.redeemCoupon('12345asfd');
redeemCoupon('12345asfd');
cy.assertStartPage();
});
@ -31,7 +38,7 @@ describe('Email Verifcation', () => {
cy.apolloLogin('rahel.cueni', 'test');
cy.visit('/license-activation');
cy.redeemCoupon('');
redeemCoupon('');
cy.get('[data-cy="coupon-local-errors"]').contains('Coupon ist ein Pflichtfeld.');
});
@ -46,7 +53,7 @@ describe('Email Verifcation', () => {
cy.apolloLogin('rahel.cueni', 'test');
cy.visit('/license-activation');
cy.redeemCoupon('12345asfd');
redeemCoupon('12345asfd');
cy.get('[data-cy="coupon-remote-errors"]').contains('Der angegebene Coupon-Code ist ungültig.');
});
@ -61,7 +68,7 @@ describe('Email Verifcation', () => {
cy.apolloLogin('rahel.cueni', 'test');
cy.visit('/license-activation');
cy.redeemCoupon('12345asfd');
redeemCoupon('12345asfd');
cy.get('[data-cy="coupon-remote-errors"]').contains('Es ist ein Fehler aufgetreten. Bitte versuchen Sie es nochmals oder kontaktieren Sie den Administrator.');
});
});

View File

@ -55,7 +55,7 @@ describe('Assignment feedback read-only - Teacher', () => {
// cy.get('@textarea').should('have.attr', 'readonly');
cy.isSubmissionReadOnly(myText);
cy.canReopen(false);
cy.getByDataCy('final-submission-reopen').should('not.exist');
});
it('can edit', () => {
cy.mockGraphqlOps({
@ -63,6 +63,7 @@ describe('Assignment feedback read-only - Teacher', () => {
});
cy.visit('submission/submission-id');
cy.canReopen(false);
cy.getByDataCy('final-submission-reopen').should('exist');
});
});

View File

@ -90,7 +90,7 @@ describe('Assignments read-only - Student', () => {
cy.visit('module/module-with-assignment');
cy.isSubmissionReadOnly(myText);
cy.canReopen(false);
cy.getByDataCy('final-submission-reopen').should('not.exist');
});
it('can revoke turn in', () => {
@ -110,6 +110,6 @@ describe('Assignments read-only - Student', () => {
cy.visit('module/module-with-assignment');
cy.getByDataCy('final-submission').should('exist');
cy.canReopen(false);
cy.getByDataCy('final-submission-reopen').should('not.exist');
});
});

View File

@ -0,0 +1,81 @@
import mocks from '../../../fixtures/mocks';
const SELECTED_CLASS_ID = 'selectedClassId';
const getOperations = ({readOnly, classReadOnly}) => ({
MeQuery: {
me: {
onboardingVisited: true,
readOnly,
isTeacher: true,
selectedClass: {
id: SELECTED_CLASS_ID,
readOnly: classReadOnly,
},
},
},
RoomEntriesQuery: {
room: {
id: 'roomId',
slug: '',
title: 'room title',
entryCount: 3,
appearance: 'blue',
description: 'room description',
schoolClass: {
id: SELECTED_CLASS_ID,
name: 'selected class',
},
roomEntries: {
edges: [
{
node: {
id: 'entryId',
slug: '',
title: 'entry title',
contents: [],
author: {
id: 'authorId',
firstName: 'first',
lastName: 'last',
avatarUrl: ''
}
},
},
],
},
},
},
});
const checkRoomReadOnly = ({editable, readOnly, classReadOnly = false}) => {
const operations = getOperations({readOnly, classReadOnly});
cy.mockGraphqlOps({
operations,
});
const exist = editable ? 'exist' : 'not.exist';
cy.visit('room/some-room');
cy.get('.room-entry').should('exist');
cy.getByDataCy('add-room-entry-button').should(exist);
cy.getByDataCy('room-actions').should(exist);
};
describe('Room Team Management - Read only', () => {
beforeEach(() => {
cy.setup();
});
it('can edit room', () => {
checkRoomReadOnly({editable: true, readOnly: false});
});
it('can not edit room', () => {
checkRoomReadOnly({editable: false, readOnly: true});
});
it('can not edit room of inactive class', () => {
checkRoomReadOnly({editable: false, readOnly: false, classReadOnly: true});
});
});

View File

@ -0,0 +1,70 @@
import mocks from '../../../fixtures/mocks';
const SELECTED_CLASS_ID = 'selectedClassId';
const getOperations = ({readOnly, classReadOnly}) => ({
MeQuery: {
me: {
onboardingVisited: true,
readOnly,
isTeacher: true,
selectedClass: {
id: SELECTED_CLASS_ID,
readOnly: classReadOnly,
},
},
},
RoomsQuery: {
rooms: {
edges: [
{
node: {
id: '',
slug: '',
title: 'some room',
entryCount: 3,
appearance: 'red',
description: 'some description',
schoolClass: {
id: SELECTED_CLASS_ID,
name: 'bla'
},
},
},
],
},
},
});
const checkRoomsReadOnly = ({editable, readOnly, classReadOnly = false}) => {
const operations = getOperations({readOnly, classReadOnly});
cy.mockGraphqlOps({
operations,
});
const exist = editable ? 'exist' : 'not.exist';
cy.visit('rooms');
cy.log('visit');
cy.get('.room-widget').should('exist');
cy.getByDataCy('add-room').should(exist);
cy.getByDataCy('widget-footer').should(exist);
};
describe('Room Team Management - Read only', () => {
beforeEach(() => {
cy.setup();
});
it('can edit room', () => {
checkRoomsReadOnly({editable: true, readOnly: false});
});
it('can not edit room', () => {
checkRoomsReadOnly({editable: false, readOnly: true});
});
it('can not edit room of inactive class', () => {
checkRoomsReadOnly({editable: false, readOnly: false, classReadOnly: true});
});
});

View File

@ -32,7 +32,7 @@ const getOperations = ({readOnly}) => ({
},
});
Cypress.Commands.add('checkSchoolClassTeamReadOnly', (readOnly) => {
const checkSchoolClassTeamReadOnly = (readOnly) => {
cy.mockGraphqlOps({
operations: getOperations({readOnly}),
});
@ -47,7 +47,7 @@ Cypress.Commands.add('checkSchoolClassTeamReadOnly', (readOnly) => {
cy.getByDataCy('current-class-name').should('exist');
cy.getByDataCy('create-class-link').should(exist);
cy.getByDataCy('my-team-link').should(exist);
});
};
describe('School Class and Team Management - Read only', () => {
beforeEach(() => {
@ -62,21 +62,10 @@ describe('School Class and Team Management - Read only', () => {
});
it('can see menu items', () => {
cy.checkSchoolClassTeamReadOnly(false);
// cy.visit('me/class');
// cy.getByDataCy('group-list-name').should('exist').should('contain', selectedClassName);
// cy.getByDataCy('show-code-button').should('exist');
// cy.getByDataCy('edit-group-name-link').should('exist');
// cy.openSidebar();
// cy.getByDataCy('class-selection').click();
// cy.getByDataCy('current-class-name').should('exist');
// cy.getByDataCy('create-class-link').should('exist');
checkSchoolClassTeamReadOnly(false);
});
it('can not see menu items', () => {
// cy.mockGraphqlOps({
// operations: getOperations({readOnly: true}),
// });
cy.checkSchoolClassTeamReadOnly(true);
checkSchoolClassTeamReadOnly(true);
});
});

View File

@ -28,6 +28,7 @@
// todo: once above issue is fixed, go back to the original repo -> npm install cypress-graphql-mock
// import 'cypress-graphql-mock';
import '@iam4x/cypress-graphql-mock';
import mocks from '../fixtures/mocks';
Cypress.Commands.add('apolloLogin', (username, password) => {
const payload = {
@ -118,51 +119,6 @@ Cypress.Commands.add('enterPassword', (password) => {
cy.get('[data-cy="login-button"]').click();
});
Cypress.Commands.add('register', (prefix, firstname, lastname, street, city, postcode, password, passwordConfirmation, acceptTerms) => {
let selection = prefix === 1 ? 'Herr' : 'Frau';
cy.get('[data-cy="prefix-selection"]').select(selection);
if (firstname !== '') {
cy.get('[data-cy="firstname-input"]').type(firstname);
}
if (lastname !== '') {
cy.get('[data-cy="lastname-input"]').type(lastname);
}
if (street !== '') {
cy.get('[data-cy="street-input"]').type(street);
}
if (city !== '') {
cy.get('[data-cy="city-input"]').type(city);
}
if (postcode !== '') {
cy.get('[data-cy="postcode-input"]').type(postcode);
}
if (password !== '') {
cy.get('[data-cy="password-input"]').type(password);
}
if (acceptTerms) {
cy.get('[data-cy="acceptedTerms-input"] > input').first().check({force: true}).then(() => {
cy.get('[data-cy="acceptedTerms-input"] > input:checkbox').should('be.checked');
});
}
cy.get('[data-cy="passwordConfirmation-input"]').type(passwordConfirmation);
cy.get('[data-cy="register-button"]').click();
});
Cypress.Commands.add('redeemCoupon', coupon => {
if (coupon !== '') {
cy.get('[data-cy="coupon-input"]').type(coupon);
}
cy.get('[data-cy="coupon-button"]').click();
});
Cypress.Commands.add('assertStartPage', (onboarding) => {
if (onboarding) {
cy.get('[data-cy=onboarding-page]').should('exist');
@ -190,11 +146,6 @@ Cypress.Commands.add('fakeLogin', () => {
cy.setCookie('loginStatus', 'true');
});
Cypress.Commands.add('canReopen', (exists) => {
let check = exists ? 'exist' : 'not.exist';
cy.getByDataCy('final-submission-reopen').should(check);
});
Cypress.Commands.add('isSubmissionReadOnly', (myText) => {
cy.get('.submission-form__textarea--readonly').as('textarea');
@ -206,3 +157,15 @@ Cypress.Commands.add('isSubmissionReadOnly', (myText) => {
Cypress.Commands.add('openSidebar', () => {
cy.getByDataCy('user-widget-avatar').click();
});
Cypress.Commands.add('setup', () => {
cy.fakeLogin('nino.teacher', 'test');
cy.server();
cy.viewport('macbook-15');
cy.task('getSchema').then(schema => {
cy.mockGraphql({
schema,
mocks,
});
});
});

View File

@ -1,3 +1,5 @@
// todo: clean up this file
const getSchoolClassNode = (id, schoolClassName) => ({
'id': btoa(`SchoolClassNode:${id}`),
'name': schoolClassName,
@ -52,43 +54,6 @@ export const getMe = ({schoolClasses, teacher}) => {
};
};
export const getAssignments = () => {
return {
'assignments': {
'edges': [
{
'node': {
'id': 'QXNzaWdubWVudE5vZGU6MQ==',
'title': 'Ein Auftragstitel',
'assignment': 'Ein Auftrag',
'solution': null,
'submission': {
'id': 'U3R1ZGVudFN1Ym1pc3Npb25Ob2RlOjE=',
'text': 'Hir ist ein Feler gewesen',
'final': false,
'document': '',
'submissionFeedback': {
'id': 'U3VibWlzc2lvbkZlZWRiYWNrTm9kZTox',
'text': '\ud83d\ude42\ud83d\ude10\ud83e\udd2c\ud83d\udc4d\ud83e\udd22\ud83e\udd22\ud83e\udd22\ud83e\udd22\ud83d\ude2e\ud83e\udd17',
'teacher': {
'firstName': 'Nico',
'lastName': 'Zickgraf',
'__typename': 'UserNode',
},
'__typename': 'SubmissionFeedbackNode',
},
'__typename': 'StudentSubmissionNode',
},
'__typename': 'AssignmentNode',
},
'__typename': 'AssignmentNodeEdge',
},
],
'__typename': 'AssignmentNodeConnection',
},
};
};
export const getModules = () => {
return {
'lohn-und-budget': {

View File

@ -24,10 +24,10 @@ declare namespace Cypress {
fakeLogin(username: string, password: string): void
canReopen(exists: boolean): void
isSubmissionReadOnly(myText: string): void
openSidebar(): void
setup(): void
}
}

27764
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -118,7 +118,7 @@
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^24.8.0",
"canvas": "^2.5.0",
"cypress": "^6.2.1",
"cypress": "^8.0.0",
"graphql-config": "^3.2.0",
"jest": "^24.8.0",
"jest-serializer-vue": "^2.0.2",

View File

@ -1,5 +1,7 @@
<template>
<div class="room-actions">
<div
class="room-actions"
data-cy="room-actions">
<a
class="room-actions__more-link"
@click="toggleMenu">
@ -68,8 +70,7 @@
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
@import "@/styles/_mixins.scss";
@import "~styles/helpers";
.room-actions {
&__more-link {

View File

@ -10,7 +10,9 @@
<room-group-widget v-bind="schoolClass"/>
<entry-count-widget :entry-count="entryCount"/>
</router-link>
<widget-footer v-if="canEditRoom">
<widget-footer
data-cy="widget-footer"
v-if="canEditRoom">
<room-actions :id="id"/>
</widget-footer>
</div>
@ -49,7 +51,7 @@
return `room-widget--${this.appearance}`;
},
canEditRoom() {
return this.me.permissions.includes('users.can_manage_school_class_content');
return this.me.isTeacher && !this.me.readOnly && !this.me.selectedClass.readOnly;
}
},

View File

@ -20,6 +20,7 @@ fragment UserParts on PrivateUserNode {
}
selectedClass {
id
readOnly
}
recentModules(orderBy: "-visited") {
edges {

View File

@ -6,7 +6,9 @@
{{ room.description }}
</p>
<div class="room__meta">
<room-actions :id="room.id"/>
<room-actions
:id="room.id"
v-if="canEdit"/>
<room-group-widget v-bind="room.schoolClass"/>
<entry-count-widget :entry-count="roomEntryCount"/>
</div>
@ -14,7 +16,7 @@
<div class="room__content">
<add-room-entry-button
:parent="room"
v-if="room.id">
v-if="room.id && canEdit">
<!--
the v-if is there for the case where the room hasn't loaded yet, but there is already an attempt to create
a new room entry. mainly happens during cypress testing, but could also happen on a very slow connection
@ -30,11 +32,18 @@
<script>
import ROOM_ENTRIES_QUERY from '@/graphql/gql/queries/roomEntriesQuery.gql';
import roomMixin from '@/mixins/room';
import room from '@/mixins/room';
import me from '@/mixins/me';
export default {
props: ['slug'],
mixins: [roomMixin],
mixins: [room, me],
computed: {
canEdit() {
return !this.me.readOnly && !this.me.selectedClass.readOnly;
}
},
apollo: {
modules: {
@ -59,5 +68,5 @@
</script>
<style scoped lang="scss">
@import "@/styles/_room.scss";
@import "~styles/room";
</style>

View File

@ -44,7 +44,7 @@
return this.me.selectedClass.id;
},
canAddRoom() {
return this.me.permissions.includes('users.can_manage_school_class_content');
return this.me.isTeacher && !this.me.readOnly && !this.me.selectedClass.readOnly;
}
},
@ -70,7 +70,7 @@
</script>
<style scoped lang="scss">
@import "@/styles/_mixins.scss";
@import "~styles/helpers";
.rooms-page {
display: flex;

View File

@ -24,7 +24,7 @@ class UpdateSolutionVisibility(relay.ClientIDMutation):
slug = args.get('slug')
enabled = args.get('enabled')
user = info.context.user
selected_class = user.selected_class()
selected_class = user.selected_class
if 'users.can_manage_school_class_content' not in user.get_role_permissions():
raise PermissionError()

View File

@ -45,7 +45,7 @@ class ModuleNode(DjangoObjectType):
return self.get_parent().specific
def resolve_solutions_enabled(self, info, **kwargs):
school_class = info.context.user.selected_class()
school_class = info.context.user.selected_class
return self.solutions_enabled_for.filter(pk=school_class.pk).exists() if school_class is not None else False
def resolve_bookmark(self, info, **kwags):

View File

@ -30,7 +30,7 @@ class ModuleSolutionVisibilityTest(TestCase):
)
self.teacher = User.objects.get(username="teacher")
self.selected_class = self.teacher.selected_class()
self.selected_class = self.teacher.selected_class
self.student = User.objects.get(username="student1")
student_request = RequestFactory().get('/')
student_request.user = self.student
@ -54,7 +54,7 @@ class ModuleSolutionVisibilityTest(TestCase):
"""
def test_hide_solutions_for_students_and_then_show_them(self):
self.assertEqual(self.student.selected_class(), self.teacher.selected_class())
self.assertEqual(self.student.selected_class, self.teacher.selected_class)
student_result = self.student_client.execute(self.query, variables={
'id': self.content_block_id

View File

@ -3,7 +3,7 @@ from users.models import User
def are_solutions_enabled_for(user: User, module: Module):
school_class = user.selected_class()
school_class = user.selected_class
return module.solutions_enabled_for.filter(pk=school_class.pk).exists()

View File

@ -370,7 +370,6 @@ type CreateTeamPayload {
}
type CustomMutation {
redeemCoupon(input: CouponInput!): CouponPayload
spellCheck(input: SpellCheckInput!): SpellCheckPayload
addNote(input: AddNoteInput!): AddNotePayload
updateNote(input: UpdateNoteInput!): UpdateNotePayload
@ -920,6 +919,7 @@ type SchoolClassNode implements Node {
rooms(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, appearance: String): RoomNodeConnection!
pk: Int
members: [ClassMemberNode]
readOnly: Boolean
}
type SchoolClassNodeConnection {

View File

@ -7,9 +7,11 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.models import AbstractUser, Permission
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.utils.functional import cached_property
from django.utils.timezone import make_aware, is_aware
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from typing import Union
from users.licenses import MYSKILLBOX_LICENSES
from users.managers import RoleManager, UserRoleManager, UserManager, LicenseManager
@ -75,7 +77,8 @@ class User(AbstractUser):
def is_teacher(self):
return self.user_roles.filter(role__key='teacher').exists()
def selected_class(self):
@cached_property
def selected_class(self) -> Union['SchoolClass', None]:
try:
settings = UserSetting.objects.get(user=self)
return settings.selected_class

View File

@ -4,7 +4,7 @@ import graphene
from django.db.models import Q
from django.utils.dateformat import format
from django_filters import FilterSet, OrderingFilter
from graphene import relay, ObjectType
from graphene import ObjectType, relay
from graphene_django import DjangoObjectType
from graphene_django.filter import DjangoFilterConnectionField
from graphql_relay import to_global_id
@ -13,13 +13,14 @@ from basicknowledge.models import BasicKnowledge
from basicknowledge.queries import InstrumentNode
from books.models import Module
from books.schema.queries import ModuleNode
from users.models import User, SchoolClass, SchoolClassMember, Team
from users.models import SchoolClass, SchoolClassMember, Team, User
class SchoolClassNode(DjangoObjectType):
pk = graphene.Int()
members = graphene.List('users.schema.ClassMemberNode')
code = graphene.String()
read_only = graphene.Boolean()
class Meta:
model = SchoolClass
@ -32,13 +33,19 @@ class SchoolClassNode(DjangoObjectType):
def resolve_members(self, info, **kwargs):
return SchoolClassMember.objects.filter(school_class=self)
def resolve_code(self, info, **kwargs):
def resolve_code(self: SchoolClass, info, **kwargs):
if not info.context.user.is_teacher():
return None
if self.code is None:
self.generate_code()
return self.code
@staticmethod
def resolve_read_only(root: SchoolClass, info, **kwargs):
user = info.context.user
member = SchoolClassMember.objects.get(user=user, school_class=root)
return not member.active
class TeamNode(DjangoObjectType):
class Meta:
@ -107,25 +114,28 @@ class PrivateUserNode(DjangoObjectType):
def resolve_permissions(self, info):
return self.get_all_permissions()
def resolve_selected_class(self, info):
return self.selected_class()
@staticmethod
def resolve_selected_class(root: User, info):
return root.selected_class
def resolve_expiry_date(self, info):
if not self.hep_id: # concerns users that already have an (old) account
@staticmethod
def resolve_expiry_date(root: User, info):
if not root.hep_id: # concerns users that already have an (old) account
return format(datetime(2020, 7, 31), 'U') # just set some expiry date
else:
return self.license_expiry_date
return root.license_expiry_date
def resolve_is_teacher(self, info):
return self.is_teacher()
def resolve_is_teacher(root: User, info):
return root.is_teacher()
def resolve_school_classes(self, info):
if self.selected_class() is None: # then we don't have any class to return
@staticmethod
def resolve_school_classes(root: User, info):
if root.selected_class is None: # then we don't have any class to return
return SchoolClass.objects.none()
return SchoolClass.objects.filter(
Q(schoolclassmember__active=True, schoolclassmember__user=self) | Q(pk=self.selected_class().pk)).distinct()
Q(schoolclassmember__active=True, schoolclassmember__user=root) | Q(pk=root.selected_class.pk)).distinct()
def resolve_old_classes(self, info):
def resolve_old_classes(self: User, info):
return SchoolClass.objects.filter(schoolclassmember__active=False, schoolclassmember__user=self)
def resolve_recent_modules(self, info, **kwargs):

View File

@ -41,7 +41,7 @@ class JoinSchoolClassTest(TestCase):
self.assertEqual(result['success'], True)
self.assertEqual(result['schoolClass']['name'], 'Klasse 2B')
self.assertEqual(self.user.school_classes.count(), 2)
self.assertEqual(self.user.selected_class().code, 'YYYY')
self.assertEqual(self.user.selected_class.code, 'YYYY')
def test_class_already_joined(self):
code = 'YYYY'

View File

@ -82,3 +82,5 @@ class MySchoolClasses(TestCase):
self.assertEqual(len(old_classes), 1)

View File

@ -6,6 +6,7 @@ from graphql_relay import to_global_id
from api.schema import schema
from api.utils import get_graphql_mutation, get_object
from core.factories import UserFactory, TeacherFactory
from core.tests.base_test import SkillboxTestCase
from users.models import SchoolClass, User
from users.services import create_users
@ -38,18 +39,14 @@ class SchoolClassesTest(TestCase):
self.assertEqual(f'{self.prefix} {user.pk}', class_name)
class ModifySchoolClassTest(TestCase):
class ModifySchoolClassTest(SkillboxTestCase):
def setUp(self):
create_users()
self.teacher = User.objects.get(username='teacher')
self.student = User.objects.get(username='student1')
request = RequestFactory().get('/')
request.user = self.teacher
student_request = RequestFactory().get('/')
student_request.user = self.student
self.client = Client(schema=schema, context_value=request)
self.student_client = Client(schema=schema, context_value=student_request)
self.client = self.get_client(user=self.teacher)
self.student_client = self.get_client(user=self.student)
def test_update_school_class(self):
class_name = 'The Colbert Show'
@ -115,7 +112,7 @@ class ModifySchoolClassTest(TestCase):
school_class = get_object(SchoolClass, id)
self.assertEqual(school_class.name, class_name)
self.assertEqual(school_class.get_teacher(), self.teacher)
self.assertEqual(self.teacher.selected_class().name, class_name)
self.assertEqual(self.teacher.selected_class.name, class_name)
def test_create_school_class_fail(self):
self.assertEqual(SchoolClass.objects.count(), 2)

View File

@ -1,29 +1,18 @@
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase, RequestFactory
from graphene.test import Client
from graphql_relay import to_global_id
from api.schema import schema
from core.factories import UserFactory
from core.tests.base_test import SkillboxTestCase
from users.factories import SchoolClassFactory
from users.models import UserSetting
from users.models import SchoolClassMember, UserSetting
class UserSettingTests(TestCase):
class UserSettingTests(SkillboxTestCase):
def setUp(self):
self.user = UserFactory(username='aschi')
self.class1 = SchoolClassFactory(users=[self.user])
self.class2 = SchoolClassFactory(users=[self.user])
self.class3 = SchoolClassFactory(users=[])
request = RequestFactory().get('/')
request.user = self.user
# adding session
middleware = SessionMiddleware()
middleware.process_request(request)
request.session.save()
self.client = Client(schema=schema, context_value=request)
def make_mutation(self, class_id):
mutation = '''
@ -37,7 +26,7 @@ class UserSettingTests(TestCase):
}
'''
return self.client.execute(mutation, variables={
return self.get_client(user=self.user).execute(mutation, variables={
'input': {
'id': to_global_id('SchoolClassNode', class_id)
}
@ -50,18 +39,20 @@ class UserSettingTests(TestCase):
selectedClass {
name
id
readOnly
}
}
}
'''
return self.client.execute(query)
return self.get_client(user=self.user).execute(query)
def test_selects_first_class_on_first_call(self):
result = self.make_query()
first_class = self.user.school_classes.first()
self.assertIsNone(result.get('errors'))
self.assertEqual(result.get('data').get('me').get('selectedClass').get('name'), first_class.name)
self.assertFalse(result.get('data').get('me').get('selectedClass').get('readOnly'))
def test_returns_selected_class(self):
selected_class = self.user.school_classes.all()[1]
@ -73,7 +64,6 @@ class UserSettingTests(TestCase):
selected_class.name)
def test_user_can_select_class(self):
selected_class = self.user.school_classes.first()
setting = UserSetting.objects.create(user=self.user, selected_class=selected_class)
setting.save()
@ -87,7 +77,6 @@ class UserSettingTests(TestCase):
selected_class.name)
def test_user_can_select_class_even_no_settings_exist(self):
selected_class = self.user.school_classes.all()[1]
mutation_result = self.make_mutation(selected_class.pk)
self.assertIsNone(mutation_result.get('errors'))
@ -106,4 +95,17 @@ class UserSettingTests(TestCase):
mutation_result = self.make_mutation(self.class3.pk)
self.assertIsNotNone(mutation_result.get('errors'))
def test_inactive_class_as_selected_class(self):
selected_class = self.class2
membership = SchoolClassMember.objects.get(user=self.user, school_class=selected_class)
self.assertTrue(membership.active)
membership.active = False
membership.save()
setting = UserSetting.objects.create(user=self.user, selected_class=selected_class)
setting.save()
result = self.make_query()
self.assertIsNone(result.get('errors'))
self.assertTrue(result.get('data').get('me').get('selectedClass').get('readOnly'))