Merged in feature/snapshot-title (pull request #107)

Feature/snapshot title

Approved-by: Daniel Egger
This commit is contained in:
Ramon Wenger 2022-06-20 13:54:47 +00:00
commit 7cf2d5df4b
33 changed files with 631 additions and 151 deletions

View File

@ -1,5 +1,62 @@
import module from '../../../fixtures/module.minimal'; import module from '../../../fixtures/module.minimal';
import {getMinimalMe} from '../../../support/helpers'; import {getMinimalMe} from '../../../support/helpers';
import {hasOperationName} from '../../../support/graphql';
const mockDeleteSnapshot = (success) => {
cy.intercept('POST', '/api/graphql', (req) => {
if (hasOperationName(req, 'DeleteSnapshot')) {
let result;
if (success) {
result = {
message: 'yay!',
__typename: 'Success'
};
} else {
result = {
reason: 'Not the owner',
__typename: 'NotOwner'
};
}
req.reply({
data: {
deleteSnapshot: {
result
}
},
});
}
});
};
const mockUpdateSnapshot = (title) => {
cy.intercept('POST', '/api/graphql', (req) => {
if (hasOperationName(req, 'UpdateSnapshot')) {
let snapshot;
if (title) {
snapshot = {
__typename: 'SnapshotNode',
id: 'U25hcHNob3ROb2RlOjQ=',
title,
};
} else {
snapshot = {
__typename: 'NotOwner',
reason: 'Not the owner'
};
}
req.reply({
data: {
updateSnapshot: {
snapshot,
},
},
});
}
});
};
describe('Snapshot', () => { describe('Snapshot', () => {
const operations = isTeacher => ({ const operations = isTeacher => ({
@ -25,8 +82,8 @@ describe('Snapshot', () => {
...module, ...module,
snapshots: [ snapshots: [
{ {
id: 'snapshot-id', id: 'U25hcHNob3ROb2RlOjQ=',
title: 'title', title: 'Old Title',
created: '2020-01-01', created: '2020-01-01',
mine: true, mine: true,
shared: false, shared: false,
@ -88,4 +145,26 @@ describe('Snapshot', () => {
cy.getByDataCy('module-title').should('exist'); cy.getByDataCy('module-title').should('exist');
cy.getByDataCy('snapshot-header').should('not.exist'); cy.getByDataCy('snapshot-header').should('not.exist');
}); });
it('Renames Snapshot', () => {
cy.mockGraphqlOps(operations(true));
const newTitle = 'New Title';
mockUpdateSnapshot(newTitle);
cy.visit('module/miteinander-reden/snapshots');
cy.getByDataCy('snapshot-link').should('have.text', 'Old Title');
cy.getByDataCy('rename-snapshot-button').click();
cy.getByDataCy('edit-name-input').clear().type(newTitle);
cy.getByDataCy('modal-save-button').click();
cy.getByDataCy('snapshot-link').should('have.text', 'New Title');
});
it('Deletes Snapshot', () => {
cy.mockGraphqlOps(operations(true));
mockDeleteSnapshot(true);
cy.visit('module/miteinander-reden/snapshots');
cy.getByDataCy('snapshot-entry').should('have.length', 1);
cy.getByDataCy('delete-snapshot-button').click();
cy.getByDataCy('modal-save-button').click();
cy.getByDataCy('snapshot-entry').should('have.length', 0);
});
}); });

View File

@ -36,6 +36,7 @@
const EditNoteWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/notes/EditNoteWizard'); const EditNoteWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/notes/EditNoteWizard');
const EditClassNameWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/school-class/EditClassNameWizard'); const EditClassNameWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/school-class/EditClassNameWizard');
const EditTeamNameWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/profile/EditTeamNameWizard'); const EditTeamNameWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/profile/EditTeamNameWizard');
const EditSnapshotTitleWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/snapshots/EditSnapshotTitleWizard');
const DefaultLayout = () => import(/* webpackChunkName: "layouts" */'@/layouts/DefaultLayout'); const DefaultLayout = () => import(/* webpackChunkName: "layouts" */'@/layouts/DefaultLayout');
const SimpleLayout = () => import(/* webpackChunkName: "layouts" */'@/layouts/SimpleLayout'); const SimpleLayout = () => import(/* webpackChunkName: "layouts" */'@/layouts/SimpleLayout');
const FullScreenLayout = () => import(/* webpackChunkName: "layouts" */'@/layouts/FullScreenLayout'); const FullScreenLayout = () => import(/* webpackChunkName: "layouts" */'@/layouts/FullScreenLayout');
@ -66,6 +67,7 @@
EditNoteWizard, EditNoteWizard,
EditClassNameWizard, EditClassNameWizard,
EditTeamNameWizard, EditTeamNameWizard,
EditSnapshotTitleWizard,
...modals ...modals
}, },

View File

@ -1,6 +1,9 @@
<template> <template>
<!-- eslint-disable vue/no-v-html --> <!-- eslint-disable vue/no-v-html -->
<div class="snapshot-list-item"> <div
data-cy="snapshot-entry"
class="snapshot-list-item"
>
<router-link <router-link
:to="snapshotRoute" :to="snapshotRoute"
class="snapshot-list-item__title" class="snapshot-list-item__title"
@ -11,14 +14,30 @@
class="snapshot-list-item__date" class="snapshot-list-item__date"
v-html="meta" v-html="meta"
/> />
<a <div class="snapshot-list-item__actions">
class="snapshot-list-item__link" <button
v-if="snapshot.mine" class="icon-button"
@click="share" data-cy="delete-snapshot-button"
> @click="deleteSnapshot"
<template v-if="snapshot.shared">Nicht mehr teilen</template> >
<template v-else>Mit Team teilen</template> <trash-icon class="snapshot-list-item__icon" />
</a> </button>
<button
class="icon-button"
data-cy="rename-snapshot-button"
@click="changeTitle"
>
<pen-icon class="snapshot-list-item__icon" />
</button>
<a
class="snapshot-list-item__link"
v-if="snapshot.mine"
@click="share"
>
<template v-if="snapshot.shared">Nicht mehr teilen</template>
<template v-else>Mit Team teilen</template>
</a>
</div>
</div> </div>
</template> </template>
@ -26,7 +45,13 @@
import dateformat from '@/helpers/date-format'; import dateformat from '@/helpers/date-format';
import {SNAPSHOT_DETAIL} from '@/router/module.names'; import {SNAPSHOT_DETAIL} from '@/router/module.names';
import SHARE_SNAPSHOT_MUTATION from 'gql/mutations/snapshots/share.gql'; import SHARE_SNAPSHOT_MUTATION from 'gql/mutations/snapshots/share.gql';
import UPDATE_SNAPSHOT_MUTATION from 'gql/mutations/snapshots/update.gql';
import DELETE_SNAPSHOT_MUTATION from 'gql/mutations/snapshots/delete.gql';
import SNAPSHOTS_QUERY from 'gql/queries/moduleSnapshots.gql';
import gql from 'graphql-tag'; import gql from 'graphql-tag';
import PenIcon from '@/components/icons/PenIcon';
import TrashIcon from '@/components/icons/TrashIcon';
import { removeAtIndex } from '@/graphql/immutable-operations';
export default { export default {
props: { props: {
@ -35,6 +60,10 @@
default: () => ({}), default: () => ({}),
}, },
}, },
components: {
PenIcon,
TrashIcon,
},
computed: { computed: {
meta() { meta() {
@ -55,6 +84,78 @@
}, },
methods: { methods: {
changeTitle() {
this.$modal.open('edit-snapshot-title-wizard', {name: this.snapshot.title})
.then((title) => {
console.log(title);
this.$apollo.mutate({
mutation: UPDATE_SNAPSHOT_MUTATION,
variables: {
input: {
id: this.snapshot.id,
title: title,
},
},
update(store, {data: {updateSnapshot: {snapshot}}}) {
if (snapshot.__typename === 'SnapshotNode') {
const {id, title} = snapshot;
store.writeFragment({
id,
fragment: gql`fragment SnapshotFragment on SnapshotNode {title}`,
data: {
title,
__typename: 'SnapshotNode',
},
});
}
},
});
})
.catch();
},
deleteSnapshot() {
this.$modal.open('confirm')
.then(() => {
this.$apollo.mutate({
mutation: DELETE_SNAPSHOT_MUTATION,
variables: {
input: {
id: this.snapshot.id,
},
},
update: (store, {data: {deleteSnapshot: {result}}}) => {
if (result.__typename === 'Success') {
const slug = this.$route.params.slug;
const query = SNAPSHOTS_QUERY;
const variables = {
slug,
};
const {module} = store.readQuery({
query,
variables,
});
const index = module.snapshots.findIndex(snapshot => snapshot.id === this.snapshot.id);
const snapshots = removeAtIndex(module.snapshots, index);
const data = {
module: {
...module,
snapshots
}
};
store.writeQuery({
query,
variables,
data
});
}
},
});
})
.catch();
},
share() { share() {
this.$apollo.mutate({ this.$apollo.mutate({
mutation: SHARE_SNAPSHOT_MUTATION, mutation: SHARE_SNAPSHOT_MUTATION,
@ -70,10 +171,10 @@
fragment: gql`fragment SnapshotFragment on SnapshotNode { shared }`, fragment: gql`fragment SnapshotFragment on SnapshotNode { shared }`,
data: { data: {
shared, shared,
__typename: 'SnapshotNode' __typename: 'SnapshotNode',
} },
}); });
} },
}); });
}, },
}, },
@ -100,6 +201,15 @@
&__link { &__link {
@include default-link; @include default-link;
color: $color-brand; color: $color-brand;
}
&__icon {
@include default-icon;
}
&__actions {
display: flex;
align-items: center;
margin-left: auto; margin-left: auto;
} }
} }

View File

@ -9,7 +9,7 @@
<modal-input <modal-input
:value="name" :value="name"
placeholder="Klassenname" :placeholder="placeholder"
data-cy="edit-name-input" data-cy="edit-name-input"
@input="$emit('input', $event)" @input="$emit('input', $event)"
/> />
@ -43,6 +43,10 @@
type: String, type: String,
default: '', default: '',
}, },
placeholder: {
type: String,
default: 'Namen bearbeiten'
}
}, },
components: { components: {
Modal, Modal,

View File

@ -0,0 +1,46 @@
<template>
<edit-name-wizard
:name="name"
type="Snapshot"
placeholder="Titel bearbeiten"
@input="name = $event"
@cancel="hideModal"
@save="save"
/>
</template>
<script>
import EditNameWizard from '@/components/profile/EditNameWizard';
export default {
components: {
EditNameWizard,
},
data() {
return {
name: ''
};
},
mounted() {
this.name = this.$modal.state.payload.name;
},
methods: {
save() {
this.$modal.confirm(this.name);
},
hideModal() {
this.$modal.cancel();
}
}
};
</script>
<style scoped lang="scss">
@import '~styles/helpers';
</style>

View File

@ -0,0 +1,13 @@
mutation DeleteSnapshot($input: DeleteSnapshotInput!) {
deleteSnapshot(input: $input) {
result {
__typename
...on NotOwner {
reason
}
...on Success {
message
}
}
}
}

View File

@ -0,0 +1,13 @@
mutation UpdateSnapshot($input: UpdateSnapshotInput!) {
updateSnapshot(input: $input) {
snapshot {
...on SnapshotNode {
title
id
}
...on NotOwner {
reason
}
}
}
}

View File

@ -71,6 +71,7 @@
methods: { methods: {
editTeamName() { editTeamName() {
// todo: use this.$modal
this.$store.dispatch('editTeamName'); this.$store.dispatch('editTeamName');
}, },
leaveTeam() { leaveTeam() {

9
server/api/types.py Normal file
View File

@ -0,0 +1,9 @@
import graphene
class FailureNode(graphene.Interface):
reason = graphene.String()
class SuccessNode(graphene.Interface):
message = graphene.String()

View File

@ -51,7 +51,7 @@ class SubmissionFeedbackTestCase(SkillboxTestCase):
}) })
def _fetch_assignment_student(self, user): def _fetch_assignment_student(self, user):
client = create_client(user) client = self.get_client(user)
query = ''' query = '''
query AssignmentWithSubmissions($id: ID!) { query AssignmentWithSubmissions($id: ID!) {
assignment(id: $id) { assignment(id: $id) {
@ -72,7 +72,7 @@ class SubmissionFeedbackTestCase(SkillboxTestCase):
}) })
def _fetch_assignment_teacher(self, user): def _fetch_assignment_teacher(self, user):
client = create_client(user) client = self.get_client(user)
query = ''' query = '''
query AssignmentWithSubmissions($id: ID!) { query AssignmentWithSubmissions($id: ID!) {
assignment(id: $id) { assignment(id: $id) {
@ -116,13 +116,13 @@ class SubmissionFeedbackTestCase(SkillboxTestCase):
def test_teacher_can_create_feedback(self): def test_teacher_can_create_feedback(self):
result = self._create_submission_feedback(self.teacher, False, 'Balalal', self.student_submission_id) result = self._create_submission_feedback(self.teacher, False, 'Balalal', self.student_submission_id)
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
self.assertIsNotNone( self.assertIsNotNone(
result.get('data').get('updateSubmissionFeedback').get('updatedSubmissionFeedback').get('id')) result.data.get('updateSubmissionFeedback').get('updatedSubmissionFeedback').get('id'))
def test_student_cannot_create_feedback(self): def test_student_cannot_create_feedback(self):
result = self._create_submission_feedback(self.student1, False, 'Balalal', self.student_submission_id) result = self._create_submission_feedback(self.student1, False, 'Balalal', self.student_submission_id)
self.assertIsNotNone(result.get('errors')) self.assertIsNotNone(result.errors)
def test_teacher_can_update_feedback(self): def test_teacher_can_update_feedback(self):
assignment = AssignmentFactory( assignment = AssignmentFactory(
@ -136,9 +136,9 @@ class SubmissionFeedbackTestCase(SkillboxTestCase):
result = self._create_submission_feedback(self.teacher, True, 'Some', submission_feedback_id) result = self._create_submission_feedback(self.teacher, True, 'Some', submission_feedback_id)
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
submission_feedback_response = result.get('data').get('updateSubmissionFeedback').get( submission_feedback_response = result.data.get('updateSubmissionFeedback').get(
'updatedSubmissionFeedback') 'updatedSubmissionFeedback')
self.assertTrue(submission_feedback_response.get('final')) self.assertTrue(submission_feedback_response.get('final'))
@ -156,19 +156,19 @@ class SubmissionFeedbackTestCase(SkillboxTestCase):
result = self._create_submission_feedback(self.teacher2, True, 'Some', submission_feedback_id) result = self._create_submission_feedback(self.teacher2, True, 'Some', submission_feedback_id)
self.assertIsNotNone(result.get('errors')) self.assertIsNotNone(result.errors)
def test_student_does_not_see_non_final_feedback(self): def test_student_does_not_see_non_final_feedback(self):
SubmissionFeedbackFactory(teacher=self.teacher, final=False, student_submission=self.student_submission) SubmissionFeedbackFactory(teacher=self.teacher, final=False, student_submission=self.student_submission)
result = self._fetch_assignment_student(self.student1) result = self._fetch_assignment_student(self.student1)
self.assertIsNone(result.get('data').get('submissionFeedback')) self.assertIsNone(result.data.get('submissionFeedback'))
def test_student_does_see_final_feedback(self): def test_student_does_see_final_feedback(self):
submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=True, submission_feedback = SubmissionFeedbackFactory(teacher=self.teacher, final=True,
student_submission=self.student_submission) student_submission=self.student_submission)
result = self._fetch_assignment_student(self.student1) result = self._fetch_assignment_student(self.student1)
self.assertEqual(result.get('data').get('assignment').get('submission').get('submissionFeedback') self.assertEqual(result.data.get('assignment').get('submission').get('submissionFeedback')
.get('text'), submission_feedback.text) .get('text'), submission_feedback.text)
def test_teacher_can_see_feedback_for_submission(self): def test_teacher_can_see_feedback_for_submission(self):
@ -178,7 +178,7 @@ class SubmissionFeedbackTestCase(SkillboxTestCase):
self.student_submission.save() self.student_submission.save()
result = self._fetch_assignment_teacher(self.teacher) result = self._fetch_assignment_teacher(self.teacher)
self.assertEqual(result.get('data').get('assignment').get('submissions')[0].get('submissionFeedback') self.assertEqual(result.data.get('assignment').get('submissions')[0].get('submissionFeedback')
.get('text'), submission_feedback.text) .get('text'), submission_feedback.text)
def test_rogue_teacher_cannot_see_feedback(self): def test_rogue_teacher_cannot_see_feedback(self):
@ -188,4 +188,4 @@ class SubmissionFeedbackTestCase(SkillboxTestCase):
self.student_submission.save() self.student_submission.save()
result = self._fetch_assignment_teacher(self.teacher2) result = self._fetch_assignment_teacher(self.teacher2)
self.assertIsNone(result.get('data').get('assignment').get('submissions')[0].get('submissionFeedback')) self.assertIsNone(result.data.get('assignment').get('submissions')[0].get('submissionFeedback'))

View File

@ -32,7 +32,7 @@ class AssignmentReadOnlyTestCase(SkillboxTestCase):
} }
} }
result = self.get_client(self.student1).execute(UPDATE_ASSIGNMENT_MUTATION, variables=variables) result = self.get_client(self.student1).execute(UPDATE_ASSIGNMENT_MUTATION, variables=variables)
self.assertIsNotNone(result.get('errors')) self.assertIsNotNone(result.errors)
def test_share_assignment_fails(self): def test_share_assignment_fails(self):
variables = { variables = {
@ -46,7 +46,7 @@ class AssignmentReadOnlyTestCase(SkillboxTestCase):
} }
} }
result = self.get_client(self.student1).execute(UPDATE_ASSIGNMENT_MUTATION, variables=variables) result = self.get_client(self.student1).execute(UPDATE_ASSIGNMENT_MUTATION, variables=variables)
self.assertIsNotNone(result.get('errors')) self.assertIsNotNone(result.errors)
def test_edit_feedback_fails(self): def test_edit_feedback_fails(self):
student_submission = StudentSubmissionFactory(assignment=self.assignment, student=self.student1, student_submission = StudentSubmissionFactory(assignment=self.assignment, student=self.student1,
@ -62,7 +62,7 @@ class AssignmentReadOnlyTestCase(SkillboxTestCase):
} }
}) })
self.assertIsNotNone(result.get('errors')) self.assertIsNotNone(result.errors)
def test_share_feedback_fails(self): def test_share_feedback_fails(self):
@ -79,4 +79,4 @@ class AssignmentReadOnlyTestCase(SkillboxTestCase):
} }
}) })
self.assertIsNotNone(result.get('errors')) self.assertIsNotNone(result.errors)

View File

@ -26,6 +26,6 @@ class InstrumentTypesQueryTestCase(SkillboxTestCase):
InstrumentFactory(new_type=second_type) InstrumentFactory(new_type=second_type)
def test_instrument_types_empty_not_returned(self): def test_instrument_types_empty_not_returned(self):
result = self.get_client().get_result(INSTRUMENT_TYPES_QUERY) result = self.get_client().execute(INSTRUMENT_TYPES_QUERY)
self.assertIsNone(result.errors) self.assertIsNone(result.errors)
self.assertEqual(len(result.data['instrumentTypes']), 2) self.assertEqual(len(result.data['instrumentTypes']), 2)

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.13 on 2022-05-24 19:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('books', '0032_auto_20211213_1342'),
]
operations = [
migrations.AddField(
model_name='snapshot',
name='title',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View File

@ -119,6 +119,7 @@ class Snapshot(models.Model):
'objectives.Objective', 'objectives.Objective',
related_name='hidden_for_snapshots' related_name='hidden_for_snapshots'
) )
title = models.CharField(max_length=255, blank=True, null=True)
objects = SnapshotManager() objects = SnapshotManager()

View File

@ -1,7 +1,7 @@
from books.schema.mutations.chapter import UpdateChapterVisibility from books.schema.mutations.chapter import UpdateChapterVisibility
from books.schema.mutations.contentblock import MutateContentBlock, AddContentBlock, DeleteContentBlock from books.schema.mutations.contentblock import MutateContentBlock, AddContentBlock, DeleteContentBlock
from books.schema.mutations.module import UpdateSolutionVisibility, UpdateLastModule, SyncModuleVisibility from books.schema.mutations.module import UpdateSolutionVisibility, UpdateLastModule, SyncModuleVisibility
from books.schema.mutations.snapshot import CreateSnapshot, ApplySnapshot, ShareSnapshot from books.schema.mutations.snapshot import CreateSnapshot, ApplySnapshot, ShareSnapshot, UpdateSnapshot, DeleteSnapshot
from books.schema.mutations.topic import UpdateLastTopic from books.schema.mutations.topic import UpdateLastTopic
@ -17,3 +17,5 @@ class BookMutations(object):
create_snapshot = CreateSnapshot.Field() create_snapshot = CreateSnapshot.Field()
apply_snapshot = ApplySnapshot.Field() apply_snapshot = ApplySnapshot.Field()
share_snapshot = ShareSnapshot.Field() share_snapshot = ShareSnapshot.Field()
update_snapshot = UpdateSnapshot.Field()
delete_snapshot = DeleteSnapshot.Field()

View File

@ -1,7 +1,7 @@
import graphene import graphene
from django.db.models import Q
from graphene import relay from graphene import relay
from api.types import FailureNode, SuccessNode
from api.utils import get_object from api.utils import get_object
from books.models import Module, ContentBlock, Chapter from books.models import Module, ContentBlock, Chapter
from books.models.snapshot import Snapshot from books.models.snapshot import Snapshot
@ -9,6 +9,30 @@ from books.schema.nodes import SnapshotNode, ModuleNode
from users.models import SchoolClass from users.models import SchoolClass
class NotOwner(graphene.ObjectType):
class Meta:
interfaces = (FailureNode,)
class Success(graphene.ObjectType):
class Meta:
interfaces = (SuccessNode,)
NotOwnerFailure = NotOwner(reason="Not the owner")
DeleteSnapshotSuccess = Success(message='Snapshot deleted successfully')
class UpdateSnapshotResult(graphene.Union):
class Meta:
types = (SnapshotNode, NotOwner,)
class DeleteSnapshotResult(graphene.Union):
class Meta:
types = (Success, NotOwner,)
class CreateSnapshot(relay.ClientIDMutation): class CreateSnapshot(relay.ClientIDMutation):
class Input: class Input:
module = graphene.String(required=True) module = graphene.String(required=True)
@ -30,6 +54,47 @@ class CreateSnapshot(relay.ClientIDMutation):
return cls(snapshot=snapshot, success=True) return cls(snapshot=snapshot, success=True)
class UpdateSnapshot(relay.ClientIDMutation):
class Input:
id = graphene.ID(required=True)
title = graphene.String()
snapshot = graphene.Field(UpdateSnapshotResult)
@classmethod
def mutate_and_get_payload(cls, root, info, **args):
id = args.get('id')
title = args.get('title')
user = info.context.user
snapshot = get_object(Snapshot, id)
if snapshot.creator != user:
return cls(snapshot=NotOwnerFailure)
if title is not None:
snapshot.title = title
snapshot.save()
return cls(snapshot=snapshot)
class DeleteSnapshot(relay.ClientIDMutation):
class Input:
id = graphene.ID(required=True)
result = graphene.Field(DeleteSnapshotResult)
@classmethod
def mutate_and_get_payload(cls, root, info, **args):
id = args.get('id')
user = info.context.user
snapshot = get_object(Snapshot, id)
if snapshot.creator != user:
return cls(result=NotOwnerFailure)
snapshot.delete()
return cls(result=DeleteSnapshotSuccess)
class ApplySnapshot(relay.ClientIDMutation): class ApplySnapshot(relay.ClientIDMutation):
class Input: class Input:
snapshot = graphene.ID(required=True) snapshot = graphene.ID(required=True)

View File

@ -141,7 +141,7 @@ class SnapshotNode(DjangoObjectType):
@staticmethod @staticmethod
def resolve_title(parent: Snapshot, info, **kwargs): def resolve_title(parent: Snapshot, info, **kwargs):
return f'Snapshot {parent.id}' return parent.title if parent.title is not None else f'Snapshot {parent.id}'
@staticmethod @staticmethod
def resolve_meta_title(parent, info, **kwargs): def resolve_meta_title(parent, info, **kwargs):

View File

@ -113,17 +113,6 @@ query SnapshotDetail($id: ID!) {
} }
""" """
SHARE_SNAPSHOT_MUTATION = """
mutation ShareSnapshot($input: ShareSnapshotInput!) {
shareSnapshot(input: $input) {
success
snapshot {
shared
}
}
}
"""
MODULE_SNAPSHOTS_QUERY = """ MODULE_SNAPSHOTS_QUERY = """
query SnapshotQuery($slug: String!) { query SnapshotQuery($slug: String!) {
module(slug: $slug) { module(slug: $slug) {
@ -136,3 +125,46 @@ query SnapshotQuery($slug: String!) {
} }
} }
""" """
SHARE_SNAPSHOT_MUTATION = """
mutation ShareSnapshot($input: ShareSnapshotInput!) {
shareSnapshot(input: $input) {
success
snapshot {
shared
}
}
}
"""
UPDATE_SNAPSHOT_MUTATION = """
mutation UpdateSnapshot($input: UpdateSnapshotInput!) {
updateSnapshot(input: $input) {
snapshot {
...on SnapshotNode {
title
id
}
...on NotOwner {
reason
}
}
}
}
"""
DELETE_SNAPSHOT_MUTATION = """
mutation DeleteSnapshot($input: DeleteSnapshotInput!) {
deleteSnapshot(input: $input) {
result {
__typename
...on NotOwner {
reason
}
...on Success {
message
}
}
}
}
"""

View File

@ -27,7 +27,7 @@ class ContentBlockTestCase(SkillboxTestCase):
result = self.client.execute(TOPIC_QUERY, variables={ result = self.client.execute(TOPIC_QUERY, variables={
"slug": slug "slug": slug
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
topic = result.get('data').get('topic') topic = result.data.get('topic')
self.assertEqual(topic.get('__typename'), 'NotFound') self.assertEqual(topic.get('__typename'), 'NotFound')
self.assertEqual(topic.get('reason'), 'Not Found') self.assertEqual(topic.get('reason'), 'Not Found')

View File

@ -36,8 +36,8 @@ class ContentBlockTestCase(SkillboxTestCase):
result = self.client.execute(CONTENT_BLOCK_QUERY, variables={ result = self.client.execute(CONTENT_BLOCK_QUERY, variables={
"slug": self.slug "slug": self.slug
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
module = result.get('data').get('module') module = result.data.get('module')
content_block = module['chapters'][0]['contentBlocks'][0] content_block = module['chapters'][0]['contentBlocks'][0]
self.assertEqual(content_block['title'], 'Title') self.assertEqual(content_block['title'], 'Title')
self.assertIsNotNone(content_block['type']) self.assertIsNotNone(content_block['type'])

View File

@ -45,26 +45,26 @@ class OwnContentTestCase(SkillboxTestCase):
} }
} }
""" """
result = self.client.execute(chapterQuery, variables={ result = self.get_client().execute(chapterQuery, variables={
"id": self.chapter_id "id": self.chapter_id
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
self.assertEqual(len(result.get('data').get('chapter').get('contentBlocks')), 1) self.assertEqual(len(result.data.get('chapter').get('contentBlocks')), 1)
custom_content_block = ContentBlock(title='own', slug='own', user_created=True, owner=self.user) custom_content_block = ContentBlock(title='own', slug='own', user_created=True, owner=self.user)
self.chapter.specific.add_child(instance=custom_content_block) self.chapter.specific.add_child(instance=custom_content_block)
result = self.client.execute(chapterQuery, variables={ result = self.get_client().execute(chapterQuery, variables={
"id": self.chapter_id "id": self.chapter_id
}) })
self.assertEqual(len(result.get('data').get('chapter').get('contentBlocks')), 2) self.assertEqual(len(result.data.get('chapter').get('contentBlocks')), 2)
for school_class in self.user.school_classes.all(): for school_class in self.user.school_classes.all():
custom_content_block.visible_for.add(school_class) custom_content_block.visible_for.add(school_class)
result = self.client.execute(chapterQuery, variables={ result = self.get_client().execute(chapterQuery, variables={
"id": self.chapter_id "id": self.chapter_id
}) })
self.assertEqual(len(result.get('data').get('chapter').get('contentBlocks')), 2) self.assertEqual(len(result.data.get('chapter').get('contentBlocks')), 2)
def test_mutate_own_content_block(self): def test_mutate_own_content_block(self):
query = """ query = """
@ -76,7 +76,7 @@ class OwnContentTestCase(SkillboxTestCase):
} }
""" """
res = self.get_client().get_result(query, variables={'id': self.content_block_id}) res = self.get_client().execute(query, variables={'id': self.content_block_id})
self.assertIsNone(res.errors) self.assertIsNone(res.errors)
self.assertEqual(res.data['contentBlock']['title'], 'bla') self.assertEqual(res.data['contentBlock']['title'], 'bla')
@ -112,7 +112,7 @@ class OwnContentTestCase(SkillboxTestCase):
} }
} }
mutation_result = self.get_client().get_result(mutation, variables=variables) mutation_result = self.get_client().execute(mutation, variables=variables)
self.assertIsNone(mutation_result.errors) self.assertIsNone(mutation_result.errors)
content_block = mutation_result.data['mutateContentBlock']['contentBlock'] content_block = mutation_result.data['mutateContentBlock']['contentBlock']
self.assertEqual(content_block['title'], 'new title') self.assertEqual(content_block['title'], 'new title')
@ -137,7 +137,7 @@ class OwnContentTestCase(SkillboxTestCase):
} }
} }
} }
list_mutation_result = self.get_client().get_result(mutation, variables=other_variables) list_mutation_result = self.get_client().execute(mutation, variables=other_variables)
self.assertIsNone(list_mutation_result.errors) self.assertIsNone(list_mutation_result.errors)
content_block = list_mutation_result.data['mutateContentBlock']['contentBlock'] content_block = list_mutation_result.data['mutateContentBlock']['contentBlock']
self.assertEqual(content_block['title'], 'title for list content') self.assertEqual(content_block['title'], 'title for list content')

View File

@ -7,7 +7,7 @@ from api.utils import get_object
from books.factories import ModuleFactory, ChapterFactory, ContentBlockFactory from books.factories import ModuleFactory, ChapterFactory, ContentBlockFactory
from books.models import Snapshot, ChapterSnapshot from books.models import Snapshot, ChapterSnapshot
from books.tests.queries import MODULE_QUERY, SNAPSHOT_MODULE_QUERY, CREATE_SNAPSHOT_MUTATION, APPLY_SNAPSHOT_MUTATION, \ from books.tests.queries import MODULE_QUERY, SNAPSHOT_MODULE_QUERY, CREATE_SNAPSHOT_MUTATION, APPLY_SNAPSHOT_MUTATION, \
MODULE_SNAPSHOTS_QUERY, SHARE_SNAPSHOT_MUTATION MODULE_SNAPSHOTS_QUERY, SHARE_SNAPSHOT_MUTATION, UPDATE_SNAPSHOT_MUTATION, DELETE_SNAPSHOT_MUTATION
from core.tests.base_test import SkillboxTestCase from core.tests.base_test import SkillboxTestCase
from objectives.factories import ObjectiveGroupFactory, ObjectiveFactory from objectives.factories import ObjectiveGroupFactory, ObjectiveFactory
from users.factories import SchoolClassFactory from users.factories import SchoolClassFactory
@ -89,8 +89,8 @@ class CreateSnapshotTestCase(SkillboxTestCase):
result = client.execute(MODULE_QUERY, variables={ result = client.execute(MODULE_QUERY, variables={
'slug': self.module.slug 'slug': self.module.slug
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
module = result.get('data').get('module') module = result.data.get('module')
chapter = module.get('chapters')[0] chapter = module.get('chapters')[0]
self.assertIsNotNone(chapter) self.assertIsNotNone(chapter)
content_blocks = chapter.get('contentBlocks') content_blocks = chapter.get('contentBlocks')
@ -144,8 +144,8 @@ class CreateSnapshotTestCase(SkillboxTestCase):
self._test_module_visibility(self.client, 'skillbox') self._test_module_visibility(self.client, 'skillbox')
def _test_create_snapshot(self, result, num_snapshots=1): def _test_create_snapshot(self, result, num_snapshots=1):
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
snapshot = result.get('data').get('createSnapshot').get('snapshot') snapshot = result.data.get('createSnapshot').get('snapshot')
chapter = snapshot.get('chapters')[0] chapter = snapshot.get('chapters')[0]
self.assertIsNotNone(snapshot.get('created')) self.assertIsNotNone(snapshot.get('created'))
@ -206,7 +206,7 @@ class CreateSnapshotTestCase(SkillboxTestCase):
'selectedClass': to_global_id('SchoolClassNode', self.second_class.pk), 'selectedClass': to_global_id('SchoolClassNode', self.second_class.pk),
} }
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
module = self._test_module_visibility(self.get_client(self.teacher2), self.second_class_name) module = self._test_module_visibility(self.get_client(self.teacher2), self.second_class_name)
original_creator = module['chapters'][0]['contentBlocks'][2].get('originalCreator') original_creator = module['chapters'][0]['contentBlocks'][2].get('originalCreator')
self.assertIsNotNone(original_creator) self.assertIsNotNone(original_creator)
@ -219,8 +219,8 @@ class CreateSnapshotTestCase(SkillboxTestCase):
snapshot_result = self.client.execute(SNAPSHOT_MODULE_QUERY, variables={ snapshot_result = self.client.execute(SNAPSHOT_MODULE_QUERY, variables={
'id': id 'id': id
}) })
self.assertIsNone(snapshot_result.get('errors')) self.assertIsNone(snapshot_result.errors)
snapshot = snapshot_result.get('data').get('snapshot') snapshot = snapshot_result.data.get('snapshot')
chapters = snapshot.get('chapters') chapters = snapshot.get('chapters')
self.assertEqual(len(chapters), 2) self.assertEqual(len(chapters), 2)
chapter = chapters[0] chapter = chapters[0]
@ -259,8 +259,8 @@ class CreateSnapshotTestCase(SkillboxTestCase):
'selectedClass': to_global_id('SchoolClassNode', third_class.pk), 'selectedClass': to_global_id('SchoolClassNode', third_class.pk),
} }
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
snapshot_id = result['data']['createSnapshot']['snapshot']['id'] snapshot_id = result.data['createSnapshot']['snapshot']['id']
result = self.client.execute(APPLY_SNAPSHOT_MUTATION, variables={ result = self.client.execute(APPLY_SNAPSHOT_MUTATION, variables={
'input': { 'input': {
@ -268,13 +268,13 @@ class CreateSnapshotTestCase(SkillboxTestCase):
'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk), 'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk),
} }
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
result = self.client.execute(MODULE_QUERY, variables={ result = self.client.execute(MODULE_QUERY, variables={
'slug': self.module.slug 'slug': self.module.slug
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
module = result['data']['module'] module = result.data['module']
chapter1, chapter2 = module['chapters'] chapter1, chapter2 = module['chapters']
visible, hidden, custom, custom_hidden = chapter1['contentBlocks'] visible, hidden, custom, custom_hidden = chapter1['contentBlocks']
self.assertTrue(self.skillbox_class.name not in [sc['name'] for sc in visible['hiddenFor']]) self.assertTrue(self.skillbox_class.name not in [sc['name'] for sc in visible['hiddenFor']])
@ -286,8 +286,8 @@ class CreateSnapshotTestCase(SkillboxTestCase):
result = self.graphene_client.execute(MODULE_QUERY, variables={ result = self.graphene_client.execute(MODULE_QUERY, variables={
'slug': self.module.slug 'slug': self.module.slug
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
chapter = result['data']['module']['chapters'][0] chapter = result.data['module']['chapters'][0]
self.assertEqual(len(chapter['contentBlocks']), 4) self.assertEqual(len(chapter['contentBlocks']), 4)
result = self.graphene_client.execute(CREATE_SNAPSHOT_MUTATION, variables={ result = self.graphene_client.execute(CREATE_SNAPSHOT_MUTATION, variables={
@ -296,8 +296,8 @@ class CreateSnapshotTestCase(SkillboxTestCase):
'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk), 'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk),
} }
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
snapshot_id = result['data']['createSnapshot']['snapshot']['id'] snapshot_id = result.data['createSnapshot']['snapshot']['id']
teacher2 = User.objects.get(username='teacher2') teacher2 = User.objects.get(username='teacher2')
teacher2_client = self.get_client(user=teacher2) teacher2_client = self.get_client(user=teacher2)
@ -307,16 +307,15 @@ class CreateSnapshotTestCase(SkillboxTestCase):
'selectedClass': to_global_id('SchoolClassNode', self.second_class.pk), 'selectedClass': to_global_id('SchoolClassNode', self.second_class.pk),
} }
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
result = self.graphene_client.execute(MODULE_QUERY, variables={ result = self.graphene_client.execute(MODULE_QUERY, variables={
'slug': self.module.slug 'slug': self.module.slug
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
chapter = result['data']['module']['chapters'][0] chapter = result.data['module']['chapters'][0]
self.assertEqual(len(chapter['contentBlocks']), 4) self.assertEqual(len(chapter['contentBlocks']), 4)
def test_snapshot_chapter_visibility_after_apply(self): def test_snapshot_chapter_visibility_after_apply(self):
result = self.graphene_client.execute(CREATE_SNAPSHOT_MUTATION, variables={ result = self.graphene_client.execute(CREATE_SNAPSHOT_MUTATION, variables={
'input': { 'input': {
@ -335,10 +334,11 @@ class CreateSnapshotTestCase(SkillboxTestCase):
a_result = self.get_client(self.teacher2).execute(MODULE_QUERY, variables={ a_result = self.get_client(self.teacher2).execute(MODULE_QUERY, variables={
'slug': self.module.slug 'slug': self.module.slug
}) })
self.assertIsNone(a_result.get('errors')) self.assertIsNone(a_result.errors)
a_chapter = a_result['data']['module']['chapters'][0] a_chapter = a_result.data['module']['chapters'][0]
self.assertEqual(self.second_class_name in map(lambda x: x['name'], a_chapter['titleHiddenFor']), hidden) self.assertEqual(self.second_class_name in map(lambda x: x['name'], a_chapter['titleHiddenFor']), hidden)
self.assertEqual(self.second_class_name in map(lambda x: x['name'], a_chapter['descriptionHiddenFor']), hidden) self.assertEqual(self.second_class_name in map(lambda x: x['name'], a_chapter['descriptionHiddenFor']),
hidden)
assert_chapter_hidden(True) assert_chapter_hidden(True)
@ -348,8 +348,8 @@ class CreateSnapshotTestCase(SkillboxTestCase):
'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk), 'selectedClass': to_global_id('SchoolClassNode', self.skillbox_class.pk),
} }
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
snapshot = result['data']['createSnapshot']['snapshot'] snapshot = result.data['createSnapshot']['snapshot']
snapshot_id = snapshot['id'] snapshot_id = snapshot['id']
chapter = snapshot['chapters'][0] chapter = snapshot['chapters'][0]
self.assertEqual(chapter['titleHidden'], False) self.assertEqual(chapter['titleHidden'], False)
@ -361,7 +361,7 @@ class CreateSnapshotTestCase(SkillboxTestCase):
'selectedClass': to_global_id('SchoolClassNode', self.second_class.pk), 'selectedClass': to_global_id('SchoolClassNode', self.second_class.pk),
} }
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
assert_chapter_hidden(False) assert_chapter_hidden(False)
@ -384,8 +384,8 @@ class SnapshotTestCase(SkillboxTestCase):
result = self.client.execute(MODULE_SNAPSHOTS_QUERY, variables={ result = self.client.execute(MODULE_SNAPSHOTS_QUERY, variables={
"slug": self.slug "slug": self.slug
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
snapshots = result['data']['module']['snapshots'] snapshots = result.data['module']['snapshots']
self.assertEqual(len(snapshots), 1) self.assertEqual(len(snapshots), 1)
self.assertEqual(snapshots[0]['creator'], f'{self.teacher.first_name} {self.teacher.last_name}') self.assertEqual(snapshots[0]['creator'], f'{self.teacher.first_name} {self.teacher.last_name}')
@ -397,8 +397,8 @@ class SnapshotTestCase(SkillboxTestCase):
'shared': True 'shared': True
} }
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
data = result['data']['shareSnapshot'] data = result.data['shareSnapshot']
self.assertTrue(data['success']) self.assertTrue(data['success'])
self.assertTrue(data['snapshot']['shared']) self.assertTrue(data['snapshot']['shared'])
snapshot = Snapshot.objects.get(pk=self.snapshot.pk) snapshot = Snapshot.objects.get(pk=self.snapshot.pk)
@ -413,7 +413,7 @@ class SnapshotTestCase(SkillboxTestCase):
'shared': True 'shared': True
} }
}) })
self.assertIsNotNone(result.get('errors')) self.assertIsNotNone(result.errors)
def test_snapshot_without_creator(self): def test_snapshot_without_creator(self):
self.snapshot.creator = None self.snapshot.creator = None
@ -422,5 +422,50 @@ class SnapshotTestCase(SkillboxTestCase):
result = self.client.execute(MODULE_SNAPSHOTS_QUERY, variables={ result = self.client.execute(MODULE_SNAPSHOTS_QUERY, variables={
"slug": self.slug "slug": self.slug
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
self.assertEqual(len(result.get('data').get('module').get('snapshots')), 1) self.assertEqual(len(result.data.get('module').get('snapshots')), 1)
def _setup_title_change(self):
self.assertIsNone(self.snapshot.title)
new_title = 'New Snapshot Title'
result = self.get_client().execute(UPDATE_SNAPSHOT_MUTATION, variables={
'input': {
'id': to_global_id('Snapshot', self.snapshot.id),
'title': new_title
}
})
return result
def test_update_snapshot_title(self):
result = self._setup_title_change()
self.assertIsNone(result.errors)
self.assertEqual(result.data.get('updateSnapshot').get('snapshot').get('title'), 'New Snapshot Title')
def test_update_snapshot_not_owner_fails(self):
self.snapshot.creator = self.teacher2
self.snapshot.save()
result = self._setup_title_change()
self.assertIsNone(result.errors)
self.assertEqual(result.data.get('updateSnapshot').get('snapshot').get('reason'), 'Not the owner')
def test_delete_snapshot(self):
result = self.get_client().execute(DELETE_SNAPSHOT_MUTATION, variables={
'input': {
'id': to_global_id('Snapshot', self.snapshot.id),
}
})
self.assertIsNone(result.errors)
self.assertEqual(result.data.get('deleteSnapshot').get('result').get('__typename'), 'Success')
def test_delete_snapshot_not_owner_fails(self):
self.snapshot.creator = self.teacher2
self.snapshot.save()
result = self.get_client().execute(DELETE_SNAPSHOT_MUTATION, variables={
'input': {
'id': to_global_id('Snapshot', self.snapshot.id),
}
})
self.assertIsNone(result.errors)
result = result.data.get('deleteSnapshot').get('result')
self.assertEqual(result.get('__typename'), 'NotOwner')
self.assertEqual(result.get('reason'), 'Not the owner')

View File

@ -10,6 +10,10 @@ class GQLClient(Client):
def get_result(self, *args, **kwargs): def get_result(self, *args, **kwargs):
return GQLResult(self.execute(*args, **kwargs)) return GQLResult(self.execute(*args, **kwargs))
def execute(self, *args, **kwargs):
res = super(GQLClient, self).execute(*args, **kwargs)
return GQLResult(res)
class SkillboxTestCase(TestCase): class SkillboxTestCase(TestCase):

View File

@ -87,7 +87,7 @@ mutation UpdateProjectMutation($input: UpdateProjectInput!){
'title': 'BAD! THIS IS BAD!' 'title': 'BAD! THIS IS BAD!'
} }
} }
result = self.get_client(self.student2).get_result(mutation, variables={ result = self.get_client(self.student2).execute(mutation, variables={
'input': input 'input': input
}) })
self.assertIsNotNone(result.errors) self.assertIsNotNone(result.errors)
@ -115,7 +115,7 @@ mutation UpdateProjectMutation($input: UpdateProjectInput!){
'title': 'Good! THIS IS good!' 'title': 'Good! THIS IS good!'
} }
} }
result = self.get_client(self.student).get_result(mutation, variables={ result = self.get_client(self.student).execute(mutation, variables={
'input': input 'input': input
}) })
self.assertIsNone(result.errors) self.assertIsNone(result.errors)

View File

@ -17,7 +17,7 @@ query ProjectQuery($id: ID!) {
class ProjectQueryTestCase(SkillboxTestCase): class ProjectQueryTestCase(SkillboxTestCase):
def _test_direct_project_access(self, user: User, should_have_access: bool): def _test_direct_project_access(self, user: User, should_have_access: bool):
result = self.get_client(user).get_result(project_query, variables={ result = self.get_client(user).execute(project_query, variables={
'id': self.project1.graphql_id 'id': self.project1.graphql_id
}) })
self.assertIsNone(result.errors) self.assertIsNone(result.errors)
@ -57,22 +57,22 @@ class ProjectQueryTestCase(SkillboxTestCase):
result = self.get_client(self.student1).execute(self.query) result = self.get_client(self.student1).execute(self.query)
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
self.assertEqual(result.get('data').get('projects')[0].get('title'), self.project1.title) self.assertEqual(result.data.get('projects')[0].get('title'), self.project1.title)
def test_should_not_see_other_projects(self): def test_should_not_see_other_projects(self):
self.assertEqual(Project.objects.count(), 1) self.assertEqual(Project.objects.count(), 1)
result = self.get_client(self.student2).execute(self.query) result = self.get_client(self.student2).execute(self.query)
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
self.assertEqual(len(result.get('data').get('projects')), 0) self.assertEqual(len(result.data.get('projects')), 0)
def test_teacher_should_not_see_unfinished_projects(self): def test_teacher_should_not_see_unfinished_projects(self):
result = self.get_client().execute(self.query) result = self.get_client().execute(self.query)
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
self.assertEqual(len(result.get('data').get('projects')), 0) self.assertEqual(len(result.data.get('projects')), 0)
def test_teacher_should_only_see_finished_projects(self): def test_teacher_should_only_see_finished_projects(self):
self.project1.final = True self.project1.final = True
@ -81,8 +81,8 @@ class ProjectQueryTestCase(SkillboxTestCase):
result = self.get_client().execute(self.query) result = self.get_client().execute(self.query)
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
self.assertEqual(result.get('data').get('projects')[0].get('title'), self.assertEqual(result.data.get('projects')[0].get('title'),
self.project1.title) self.project1.title)
def test_other_teacher_should_not_see_projects(self): def test_other_teacher_should_not_see_projects(self):
@ -92,8 +92,8 @@ class ProjectQueryTestCase(SkillboxTestCase):
result = self.get_client(self.teacher2).execute(self.query) result = self.get_client(self.teacher2).execute(self.query)
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
self.assertEqual(len(result.get('data').get('projects')), 0) self.assertEqual(len(result.data.get('projects')), 0)
def test_direct_project_access(self): def test_direct_project_access(self):
# student can access own project directly # student can access own project directly
@ -128,7 +128,7 @@ query ProjectQuery($id: ID!) {
} }
} }
""" """
result = self.get_client(self.student1).get_result(query, variables={ result = self.get_client(self.student1).execute(query, variables={
'id': self.project1.graphql_id 'id': self.project1.graphql_id
}) })
self.assertEqual(result.data['project']['student']['email'], self.student1.email) self.assertEqual(result.data['project']['student']['email'], self.student1.email)

View File

@ -33,7 +33,7 @@ mutation AddComment($input: AddCommentInput!) {
'comment': self.text 'comment': self.text
} }
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
self.assertEqual(room_entry.comments.count(), 1) self.assertEqual(room_entry.comments.count(), 1)
comment = room_entry.comments.first() comment = room_entry.comments.first()
self.assertEqual(comment.text, self.text) self.assertEqual(comment.text, self.text)
@ -53,8 +53,8 @@ query CommentsQuery($id: ID!) {
} }
""" """
result = self.get_client().execute(query, variables={"id": self.room_entry_id}) result = self.get_client().execute(query, variables={"id": self.room_entry_id})
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
comment_node = result.get('data').get('roomEntry').get('comments')[0] comment_node = result.data.get('roomEntry').get('comments')[0]
self.assertEqual(comment_node['text'], self.text) self.assertEqual(comment_node['text'], self.text)
def test_get_comment_for_other_user(self): def test_get_comment_for_other_user(self):
@ -70,6 +70,5 @@ query CommentsQuery($id: ID!) {
""" """
result = self.get_client(self.student_second_class).execute(query, variables={"id": self.room_entry_id}) result = self.get_client(self.student_second_class).execute(query, variables={"id": self.room_entry_id})
gql_result = GQLResult(result) self.assertIsNone(result.errors)
self.assertIsNone(gql_result.errors) self.assertIsNone(result.data.get('roomEntry'))
self.assertIsNone(gql_result.data.get('roomEntry'))

View File

@ -55,7 +55,7 @@ class NewRoomMutationTestCase(SkillboxTestCase):
self.assertEqual(Room.objects.count(), 0) self.assertEqual(Room.objects.count(), 0)
title = 'some title' title = 'some title'
appearance = 'blue' appearance = 'blue'
res = self.get_client().execute(self.mutation, variables={ result = self.get_client().execute(self.mutation, variables={
'input': { 'input': {
'room': { 'room': {
'title': title, 'title': title,
@ -64,7 +64,6 @@ class NewRoomMutationTestCase(SkillboxTestCase):
} }
} }
}) })
result = GQLResult(res)
self.assertIsNone(result.errors) self.assertIsNone(result.errors)
room = GQLRoom(result.data.get('addRoom').get('room')) room = GQLRoom(result.data.get('addRoom').get('room'))
self.assertEqual(room.title, title) self.assertEqual(room.title, title)
@ -75,7 +74,7 @@ class NewRoomMutationTestCase(SkillboxTestCase):
def test_create_new_room_for_other_school_class(self): def test_create_new_room_for_other_school_class(self):
self.assertEqual(Room.objects.count(), 0) self.assertEqual(Room.objects.count(), 0)
result = self.get_client(self.teacher2).get_result(self.mutation, variables={ result = self.get_client(self.teacher2).execute(self.mutation, variables={
'input': { 'input': {
'room': { 'room': {
'title': 'BIG NO NO!', 'title': 'BIG NO NO!',

View File

@ -175,7 +175,7 @@ mutation AddRoomEntry($input: AddRoomEntryInput!){
'room': self.room.graphql_id 'room': self.room.graphql_id
} }
result = self.get_client(self.yet_another_user).get_result(mutation, variables={ result = self.get_client(self.yet_another_user).execute(mutation, variables={
'input': { 'input': {
'roomEntry': room_entry 'roomEntry': room_entry
} }

View File

@ -30,41 +30,37 @@ query RoomQuery ($slug: String!) {
""" """
def test_restricted_query(self): def test_restricted_query(self):
res = self.get_client().execute(self.query, variables={ result = self.get_client().execute(self.query, variables={
'slug': self.room.slug 'slug': self.room.slug
}) })
result = GQLResult(res)
self.assertIsNone(result.errors) self.assertIsNone(result.errors)
self.assertFalse(result.data.get('room').get('restricted')) self.assertFalse(result.data.get('room').get('restricted'))
def test_successful_mutation(self): def test_successful_mutation(self):
res = self.get_client().execute(self.mutation, variables={ result = self.get_client().execute(self.mutation, variables={
'input': { 'input': {
'id': self.room_id, 'id': self.room_id,
'restricted': True 'restricted': True
} }
}) })
result = GQLResult(res)
self.assertIsNone(result.errors) self.assertIsNone(result.errors)
self.assertEqual(result.data.get('updateRoomVisibility').get('room').get('restricted'), True) self.assertEqual(result.data.get('updateRoomVisibility').get('room').get('restricted'), True)
def test_permission_denied(self): def test_permission_denied(self):
res = self.get_client(self.student1).execute(self.mutation, variables={ result = self.get_client(self.student1).execute(self.mutation, variables={
'input': { 'input': {
'id': self.room_id, 'id': self.room_id,
'restricted': True 'restricted': True
} }
}) })
result = GQLResult(res)
self.assertIsNotNone(result.errors) self.assertIsNotNone(result.errors)
res = self.get_client(self.teacher2).execute(self.mutation, variables={ result = self.get_client(self.teacher2).execute(self.mutation, variables={
'input': { 'input': {
'id': self.room_id, 'id': self.room_id,
'restricted': True 'restricted': True
} }
}) })
result = GQLResult(res)
self.assertIsNotNone(result.errors) self.assertIsNotNone(result.errors)

View File

@ -34,10 +34,9 @@ query RoomQuery ($slug: String!) {
""" """
def _test_room(self, user, length, restricted=True): def _test_room(self, user, length, restricted=True):
res = self.get_client(user).execute(self.query, variables={ result = self.get_client(user).execute(self.query, variables={
'slug': self.room.slug 'slug': self.room.slug
}) })
result = GQLResult(res)
self.assertIsNone(result.errors) self.assertIsNone(result.errors)
room = GQLRoom(result.data.get('room')) room = GQLRoom(result.data.get('room'))
self.assertEqual(room.restricted, restricted) self.assertEqual(room.restricted, restricted)

View File

@ -444,6 +444,18 @@ type DeleteRoomPayload {
clientMutationId: String clientMutationId: String
} }
input DeleteSnapshotInput {
id: ID!
clientMutationId: String
}
type DeleteSnapshotPayload {
result: DeleteSnapshotResult
clientMutationId: String
}
union DeleteSnapshotResult = Success | NotOwner
type DjangoDebug { type DjangoDebug {
sql: [DjangoDebugSQL] sql: [DjangoDebugSQL]
} }
@ -469,6 +481,10 @@ type DuplicateName {
reason: String reason: String
} }
interface FailureNode {
reason: String
}
type FieldError { type FieldError {
code: String code: String
} }
@ -683,6 +699,8 @@ type Mutation {
createSnapshot(input: CreateSnapshotInput!): CreateSnapshotPayload createSnapshot(input: CreateSnapshotInput!): CreateSnapshotPayload
applySnapshot(input: ApplySnapshotInput!): ApplySnapshotPayload applySnapshot(input: ApplySnapshotInput!): ApplySnapshotPayload
shareSnapshot(input: ShareSnapshotInput!): ShareSnapshotPayload shareSnapshot(input: ShareSnapshotInput!): ShareSnapshotPayload
updateSnapshot(input: UpdateSnapshotInput!): UpdateSnapshotPayload
deleteSnapshot(input: DeleteSnapshotInput!): DeleteSnapshotPayload
_debug: DjangoDebug _debug: DjangoDebug
} }
@ -706,6 +724,10 @@ type NotFound {
reason: String reason: String
} }
type NotOwner implements FailureNode {
reason: String
}
type NoteNode implements Node { type NoteNode implements Node {
id: ID! id: ID!
text: String! text: String!
@ -1065,6 +1087,14 @@ type SubmissionFeedbackNode implements Node {
id: ID! id: ID!
} }
type Success implements SuccessNode {
message: String
}
interface SuccessNode {
message: String
}
type SurveyNode implements Node { type SurveyNode implements Node {
id: ID! id: ID!
title: String! title: String!
@ -1431,6 +1461,19 @@ type UpdateSettingPayload {
clientMutationId: String clientMutationId: String
} }
input UpdateSnapshotInput {
id: ID!
title: String
clientMutationId: String
}
type UpdateSnapshotPayload {
snapshot: UpdateSnapshotResult
clientMutationId: String
}
union UpdateSnapshotResult = SnapshotNode | NotOwner
input UpdateSolutionVisibilityInput { input UpdateSolutionVisibilityInput {
slug: String slug: String
enabled: Boolean enabled: Boolean

View File

@ -62,13 +62,13 @@ class ModifySchoolClassTest(SkillboxTestCase):
'name': class_name 'name': class_name
} }
}) })
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
school_class = get_object(SchoolClass, id) school_class = get_object(SchoolClass, id)
self.assertEqual(school_class.name, class_name) self.assertEqual(school_class.name, class_name)
def test_update_school_class_not_in_class_fails(self): def test_update_school_class_not_in_class_fails(self):
client = Client(schema=schema) client = self.get_client()
teacher = TeacherFactory(username='conan') teacher = TeacherFactory(username='conan')
context = Context(user=teacher) context = Context(user=teacher)
school_class = SchoolClass.objects.get(name='skillbox') school_class = SchoolClass.objects.get(name='skillbox')
@ -81,7 +81,7 @@ class ModifySchoolClassTest(SkillboxTestCase):
} }
} }
result = client.execute(UPDATE_SCHOOL_CLASS_MUTATION, variables=variables, context=context) result = client.execute(UPDATE_SCHOOL_CLASS_MUTATION, variables=variables, context=context)
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
def test_update_school_class_fail(self): def test_update_school_class_fail(self):
class_name = 'Nanana' class_name = 'Nanana'
@ -96,7 +96,7 @@ class ModifySchoolClassTest(SkillboxTestCase):
'name': class_name 'name': class_name
} }
}) })
self.assertIsNotNone(result.get('errors')) self.assertIsNotNone(result.errors)
def test_create_school_class(self): def test_create_school_class(self):
self.assertEqual(SchoolClass.objects.count(), 2) self.assertEqual(SchoolClass.objects.count(), 2)
@ -107,8 +107,8 @@ class ModifySchoolClassTest(SkillboxTestCase):
'name': class_name 'name': class_name
} }
}) })
self.assertIsNone(query_result.get('errors')) self.assertIsNone(query_result.errors)
result = query_result.get('data').get('createSchoolClass').get('result') result = query_result.data.get('createSchoolClass').get('result')
self.assertEqual(result.get('__typename'), 'SchoolClassNode') self.assertEqual(result.get('__typename'), 'SchoolClassNode')
id = result.get('id') id = result.get('id')
self.assertEqual(SchoolClass.objects.count(), 3) self.assertEqual(SchoolClass.objects.count(), 3)
@ -129,8 +129,8 @@ class ModifySchoolClassTest(SkillboxTestCase):
'name': class_name 'name': class_name
} }
}) })
self.assertIsNone(query_result.get('errors')) self.assertIsNone(query_result.errors)
result = query_result.get('data').get('createSchoolClass').get('result') result = query_result.data.get('createSchoolClass').get('result')
self.assertEqual(result.get('__typename'), 'DuplicateName') self.assertEqual(result.get('__typename'), 'DuplicateName')
reason = result.get('reason') reason = result.get('reason')
self.assertEqual(reason, 'Dieser Name wird bereits verwendet.') self.assertEqual(reason, 'Dieser Name wird bereits verwendet.')
@ -144,4 +144,4 @@ class ModifySchoolClassTest(SkillboxTestCase):
'name': 'No School' 'name': 'No School'
} }
}) })
self.assertIsNotNone(result.get('errors')) self.assertIsNotNone(result.errors)

View File

@ -50,17 +50,17 @@ class UserSettingTests(SkillboxTestCase):
def test_selects_first_class_on_first_call(self): def test_selects_first_class_on_first_call(self):
result = self.make_query() result = self.make_query()
first_class = self.user.school_classes.first() first_class = self.user.school_classes.first()
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
self.assertEqual(result.get('data').get('me').get('selectedClass').get('name'), first_class.name) self.assertEqual(result.data.get('me').get('selectedClass').get('name'), first_class.name)
self.assertFalse(result.get('data').get('me').get('selectedClass').get('readOnly')) self.assertFalse(result.data.get('me').get('selectedClass').get('readOnly'))
def test_returns_selected_class(self): def test_returns_selected_class(self):
selected_class = self.user.school_classes.all()[1] selected_class = self.user.school_classes.all()[1]
setting = UserSetting.objects.create(user=self.user, selected_class=selected_class) setting = UserSetting.objects.create(user=self.user, selected_class=selected_class)
setting.save() setting.save()
result = self.make_query() result = self.make_query()
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
self.assertEqual(result.get('data').get('me').get('selectedClass').get('name'), self.assertEqual(result.data.get('me').get('selectedClass').get('name'),
selected_class.name) selected_class.name)
def test_user_can_select_class(self): def test_user_can_select_class(self):
@ -70,19 +70,19 @@ class UserSettingTests(SkillboxTestCase):
selected_class = self.user.school_classes.all()[1] selected_class = self.user.school_classes.all()[1]
mutation_result = self.make_mutation(selected_class.pk) mutation_result = self.make_mutation(selected_class.pk)
self.assertIsNone(mutation_result.get('errors')) self.assertIsNone(mutation_result.errors)
query_result = self.make_query() query_result = self.make_query()
self.assertIsNone(query_result.get('errors')) self.assertIsNone(query_result.errors)
self.assertEqual(query_result.get('data').get('me').get('selectedClass').get('name'), self.assertEqual(query_result.data.get('me').get('selectedClass').get('name'),
selected_class.name) selected_class.name)
def test_user_can_select_class_even_no_settings_exist(self): def test_user_can_select_class_even_no_settings_exist(self):
selected_class = self.user.school_classes.all()[1] selected_class = self.user.school_classes.all()[1]
mutation_result = self.make_mutation(selected_class.pk) mutation_result = self.make_mutation(selected_class.pk)
self.assertIsNone(mutation_result.get('errors')) self.assertIsNone(mutation_result.errors)
query_result = self.make_query() query_result = self.make_query()
self.assertIsNone(query_result.get('errors')) self.assertIsNone(query_result.errors)
self.assertEqual(query_result.get('data').get('me').get('selectedClass').get('name'), self.assertEqual(query_result.data.get('me').get('selectedClass').get('name'),
selected_class.name) selected_class.name)
@ -93,7 +93,7 @@ class UserSettingTests(SkillboxTestCase):
setting.save() setting.save()
mutation_result = self.make_mutation(self.class3.pk) mutation_result = self.make_mutation(self.class3.pk)
self.assertIsNotNone(mutation_result.get('errors')) self.assertIsNotNone(mutation_result.errors)
def test_inactive_class_as_selected_class(self): def test_inactive_class_as_selected_class(self):
selected_class = self.class2 selected_class = self.class2
@ -106,6 +106,6 @@ class UserSettingTests(SkillboxTestCase):
setting.save() setting.save()
result = self.make_query() result = self.make_query()
self.assertIsNone(result.get('errors')) self.assertIsNone(result.errors)
self.assertTrue(result.get('data').get('me').get('selectedClass').get('readOnly')) self.assertTrue(result.data.get('me').get('selectedClass').get('readOnly'))