From a4ff9d2942f4efcf242711e45a222b6a3cf31c0b Mon Sep 17 00:00:00 2001 From: Ramon Wenger Date: Thu, 25 Mar 2021 00:13:43 +0100 Subject: [PATCH] Add join team mutation --- schema.graphql | 37 +++++++++++++++++- server/users/factories.py | 1 + server/users/mutations.py | 26 ++++++++++++- server/users/tests/test_teams.py | 65 +++++++++++++++++++++++++++----- 4 files changed, 116 insertions(+), 13 deletions(-) diff --git a/schema.graphql b/schema.graphql index 9d831115..a1480fd3 100644 --- a/schema.graphql +++ b/schema.graphql @@ -347,6 +347,17 @@ type CreateSchoolClassPayload { clientMutationId: String } +input CreateTeamInput { + name: String! + clientMutationId: String +} + +type CreateTeamPayload { + success: Boolean + team: TeamNode + clientMutationId: String +} + type CustomMutation { redeemCoupon(input: CouponInput!): CouponPayload spellCheck(input: SpellCheckInput!): SpellCheckPayload @@ -365,6 +376,8 @@ type CustomMutation { updateSchoolClass(input: UpdateSchoolClassInput!): UpdateSchoolClassPayload createSchoolClass(input: CreateSchoolClassInput!): CreateSchoolClassPayload updateOnboardingProgress: UpdateOnboardingProgress + createTeam(input: CreateTeamInput!): CreateTeamPayload + joinTeam(input: JoinTeamInput!): JoinTeamPayload addProject(input: AddProjectInput!): AddProjectPayload updateProject(input: UpdateProjectInput!): UpdateProjectPayload deleteProject(input: DeleteProjectInput!): DeleteProjectPayload @@ -578,6 +591,17 @@ type JoinClassPayload { clientMutationId: String } +input JoinTeamInput { + code: String! + clientMutationId: String +} + +type JoinTeamPayload { + success: Boolean + team: TeamNode + clientMutationId: String +} + type Logout { success: Boolean } @@ -803,8 +827,8 @@ type SchoolClassNode implements Node { id: ID! name: String! isDeleted: Boolean! - users(offset: Int, before: String, after: String, first: Int, last: Int, username: String, email: String): UserNodeConnection! code: String + users(offset: Int, before: String, after: String, first: Int, last: Int, username: String, email: String): UserNodeConnection! moduleSet(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, slug_Icontains: String, slug_In: [String], title: String, title_Icontains: String, title_In: [String]): ModuleNodeConnection! hiddenChapterTitles(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ChapterNodeConnection! hiddenChapterDescriptions(offset: Int, before: String, after: String, first: Int, last: Int, slug: String, title: String): ChapterNodeConnection! @@ -920,6 +944,16 @@ type SyncModuleVisibilityPayload { clientMutationId: String } +type TeamNode implements Node { + name: String! + isDeleted: Boolean! + code: String + id: ID! + creator: UserNode + members: [UserNode] + pk: Int +} + type TopicConnection { pageInfo: PageInfo! edges: [TopicEdge]! @@ -1280,6 +1314,7 @@ type UserNode implements Node { avatarUrl: String! email: String! onboardingVisited: Boolean! + team: TeamNode schoolClasses(offset: Int, before: String, after: String, first: Int, last: Int, name: String): SchoolClassNodeConnection! id: ID! pk: Int diff --git a/server/users/factories.py b/server/users/factories.py index 4f6f6362..d6e924f5 100644 --- a/server/users/factories.py +++ b/server/users/factories.py @@ -37,6 +37,7 @@ class TeamFactory(factory.django.DjangoModelFactory): model = Team name = factory.Faker('name') + code = factory.Sequence(lambda n: "CODE{}".format(n)) is_deleted = False diff --git a/server/users/mutations.py b/server/users/mutations.py index cda3f4f2..f1238497 100644 --- a/server/users/mutations.py +++ b/server/users/mutations.py @@ -132,7 +132,7 @@ class JoinClass(relay.ClientIDMutation): return cls(success=True, school_class=school_class) except SchoolClass.DoesNotExist: - raise CodeNotFoundException('[CNV] Code ist nicht gültig') # CAV = Code Not Valid + raise CodeNotFoundException('[CNV] Code ist nicht gültig') # CNV = Code Not Valid class AddRemoveMember(relay.ClientIDMutation): @@ -211,7 +211,7 @@ class CreateSchoolClass(relay.ClientIDMutation): class CreateTeam(relay.ClientIDMutation): class Input: - name = graphene.String() + name = graphene.String(required=True) success = graphene.Boolean() team = graphene.Field(TeamNode) @@ -231,6 +231,27 @@ class CreateTeam(relay.ClientIDMutation): return cls(success=True, team=team) +class JoinTeam(relay.ClientIDMutation): + class Input: + code = graphene.String(required=True) + + success = graphene.Boolean() + team = graphene.Field(TeamNode) + + @classmethod + def mutate_and_get_payload(cls, root, info, **kwargs): + user = info.context.user + code = kwargs.get('code') + try: + team = Team.objects.get(Q(code__iexact=code)) + user.team = team + user.save() + + return cls(success=True, team=team) + except Team.DoesNotExist: + raise CodeNotFoundException('[CNV] Code ist nicht gültig') # CNV = Code Not Valid + + class UpdateOnboardingProgress(graphene.Mutation): success = graphene.Boolean() @@ -254,3 +275,4 @@ class ProfileMutations: create_school_class = CreateSchoolClass.Field() update_onboarding_progress = UpdateOnboardingProgress.Field() create_team = CreateTeam.Field() + join_team = JoinTeam.Field() diff --git a/server/users/tests/test_teams.py b/server/users/tests/test_teams.py index 63bde988..f6ebe69e 100644 --- a/server/users/tests/test_teams.py +++ b/server/users/tests/test_teams.py @@ -3,10 +3,8 @@ from graphene import Context from graphene.test import Client from api.schema import schema -from core.factories import UserFactory, TeacherFactory +from core.factories import TeacherFactory from users.factories import TeamFactory -from users.models import Role -from users.services import create_teacher ME_QUERY = """ query MeQuery { @@ -31,6 +29,18 @@ CREATE_MUTATION = """ } """ +JOIN_TEAM_MUTATION = """ +mutation JoinTeamMutation($input: JoinTeamInput!) { + joinTeam(input: $input) { + success + team { + name + code + } + } +} +""" + class TeamTest(TestCase): def setUp(self): @@ -41,15 +51,50 @@ class TeamTest(TestCase): self.user = TeacherFactory(username='ueli', team=self.team) self.context = Context(user=self.user) - def test_team_query(self): - result = self.client.execute(ME_QUERY, context=self.context) + @staticmethod + def get_team(result): + return result.get('data').get('me').get('team') + + def no_error(self, result): self.assertIsNone(result.get('errors')) - team = result.get('data').get('me').get('team') - self.assertEqual(team.get('name'), self.team_name) - self.assertEqual(team.get('code'), self.code) + + def check_me(self, context, name, code): + result = self.client.execute(ME_QUERY, context=context) + self.no_error(result) + team = self.get_team(result) + + self.assertEqual(team.get('name'), name) + self.assertEqual(team.get('code'), code) + + def join_team(self, code, context): + variables = { + "input": { + "code": code + } + } + result = self.client.execute(JOIN_TEAM_MUTATION, variables=variables, context=context) + self.no_error(result) + success = result['data']['joinTeam']['success'] + self.assertTrue(success) + + def test_team_query(self): + self.check_me(self.context, self.team_name, self.code) def test_join_team_mutation(self): - raise NotImplementedError() + teacher = TeacherFactory(username='hansli') + context = Context(user=teacher) + self.join_team(self.code, context) + + self.check_me(context, self.team_name, self.code) + + def test_join_second_team_mutation(self): + teacher = TeacherFactory(username='peterli') + context = Context(user=teacher) + second_team = TeamFactory() + self.join_team(self.code, context) + self.check_me(context, self.team_name, self.code) + self.join_team(second_team.code, context) + self.check_me(context, second_team.name, second_team.code) def test_create_team_mutation(self): team_name = "Dunder Mifflin" @@ -59,7 +104,7 @@ class TeamTest(TestCase): } } result = self.client.execute(CREATE_MUTATION, context=self.context, variables=variables) - self.assertIsNone(result.get('errors')) + self.no_error(result) create_team = result.get('data').get('createTeam') team = create_team.get('team') success = create_team.get('success')