Exemple #1
0
class GroupReceiverTests(ChannelTestCase):
    def setUp(self):
        self.client = WSClient()
        self.member = UserFactory()
        self.user = UserFactory()
        self.group = GroupFactory(members=[self.member])

    def test_receive_group_changes(self):
        self.client.force_login(self.member)
        self.client.send_and_consume('websocket.connect', path='/')

        name = faker.name()
        self.group.name = name
        self.group.save()

        response = self.client.receive(json=True)
        self.assertEqual(response['topic'], 'groups:group_detail')
        self.assertEqual(response['payload']['name'], name)
        self.assertTrue('description' in response['payload'])

        response = self.client.receive(json=True)
        self.assertEqual(response['topic'], 'groups:group_preview')
        self.assertEqual(response['payload']['name'], name)
        self.assertTrue('description' not in response['payload'])

        self.assertIsNone(self.client.receive(json=True))

    def test_receive_group_changes_as_nonmember(self):
        self.client.force_login(self.user)
        self.client.send_and_consume('websocket.connect', path='/')

        name = faker.name()
        self.group.name = name
        self.group.save()

        response = self.client.receive(json=True)
        self.assertEqual(response['topic'], 'groups:group_preview')
        self.assertEqual(response['payload']['name'], name)
        self.assertTrue('description' not in response['payload'])

        self.assertIsNone(self.client.receive(json=True))
Exemple #2
0
class TestWallMessagesUpdateStatus(APITestCase):
    def setUp(self):
        self.user = UserFactory()
        self.group = GroupFactory(members=[self.user])
        self.conversation = Conversation.objects.get_or_create_for_target(
            self.group)
        self.conversation.join(self.user)

    def test_wall_message_activates_inactive_group(self):
        self.group.status = GroupStatus.INACTIVE.value
        self.group.save()
        self.client.force_login(user=self.user)
        data = {
            'conversation': self.conversation.id,
            'content': 'a nice message'
        }
        response = self.client.post('/api/messages/', data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED,
                         response.data)
        self.assertEqual(response.data['content'], data['content'])
        self.group.refresh_from_db()
        self.assertEqual(self.group.status, GroupStatus.ACTIVE.value)
class GroupReceiverTests(WSTestCase):
    def setUp(self):
        super().setUp()
        self.member = UserFactory()
        self.user = UserFactory()
        self.group = GroupFactory(members=[self.member])

    def test_receive_group_changes(self):
        self.client = self.connect_as(self.member)

        name = faker.name()
        self.group.name = name
        self.group.save()

        response = self.client.messages[0]
        self.assertEqual(response['topic'], 'groups:group_detail')
        self.assertEqual(response['payload']['name'], name)
        self.assertTrue('description' in response['payload'])

        response = self.client.messages[1]
        self.assertEqual(response['topic'], 'groups:group_preview')
        self.assertEqual(response['payload']['name'], name)
        self.assertTrue('description' not in response['payload'])

        self.assertEqual(len(self.client.messages), 2)

    def test_receive_group_changes_as_nonmember(self):
        self.client = self.connect_as(self.user)

        name = faker.name()
        self.group.name = name
        self.group.save()

        response = self.client.messages[0]
        self.assertEqual(response['topic'], 'groups:group_preview')
        self.assertEqual(response['payload']['name'], name)
        self.assertTrue('description' not in response['payload'])

        self.assertEqual(len(self.client.messages), 1)
Exemple #4
0
class TestPickupDatesAPI(APITestCase, ExtractPaginationMixin):
    def setUp(self):
        self.url = '/api/pickup-dates/'

        # pickup date for group with one member and one store
        self.member = UserFactory()
        self.group = GroupFactory(members=[self.member])
        self.store = StoreFactory(group=self.group)
        self.pickup = PickupDateFactory(store=self.store)
        self.pickup_url = self.url + str(self.pickup.id) + '/'
        self.join_url = self.pickup_url + 'add/'
        self.leave_url = self.pickup_url + 'remove/'
        self.conversation_url = self.pickup_url + 'conversation/'

        # not a member of the group
        self.user = UserFactory()

        # another pickup date for above store
        self.pickup_data = {
            'date': timezone.now() + relativedelta(days=2),
            'max_collectors': 5,
            'store': self.store.id
        }

        # past pickup date
        self.past_pickup_data = {
            'date': timezone.now() - relativedelta(days=1),
            'max_collectors': 5,
            'store': self.store.id
        }
        self.past_pickup = PickupDateFactory(store=self.store, date=timezone.now() - relativedelta(days=1))
        self.past_pickup_url = self.url + str(self.past_pickup.id) + '/'
        self.past_join_url = self.past_pickup_url + 'add/'
        self.past_leave_url = self.past_pickup_url + 'remove/'

    def test_create_pickup(self):
        response = self.client.post(self.url, self.pickup_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, response.data)

    def test_create_pickup_as_user(self):
        self.client.force_login(user=self.user)
        response = self.client.post(self.url, self.pickup_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, response.data)

    def test_create_pickup_as_group_member(self):
        self.client.force_login(user=self.member)
        response = self.client.post(self.url, self.pickup_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.data)

    def test_create_pickup_as_group_member_activates_group(self):
        self.client.force_login(user=self.member)
        self.group.status = GroupStatus.INACTIVE.value
        self.group.save()
        self.client.post(self.url, self.pickup_data, format='json')
        self.group.refresh_from_db()
        self.assertEqual(self.group.status, GroupStatus.ACTIVE.value)

    def test_create_past_pickup_date_fails(self):
        self.client.force_login(user=self.member)
        response = self.client.post(self.url, self.past_pickup_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST, response.data)

    def test_create_pickup_as_newcomer_fails(self):
        newcomer = UserFactory()
        self.group.groupmembership_set.create(user=newcomer)
        self.client.force_login(user=newcomer)
        response = self.client.post(self.url, self.pickup_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, response.data)

    def test_list_pickups(self):
        response = self.get_results(self.url)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, response.data)

    def test_list_pickups_as_user(self):
        self.client.force_login(user=self.user)
        response = self.get_results(self.url)
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(len(response.data), 0)

    def test_list_pickups_as_group_member(self):
        self.client.force_login(user=self.member)
        response = self.get_results(self.url)
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(len(response.data), 2)

    def test_retrieve_pickups(self):
        response = self.client.get(self.pickup_url)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, response.data)

    def test_retrieve_pickups_as_user(self):
        self.client.force_login(user=self.user)
        response = self.client.get(self.pickup_url)
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND, response.data)

    def test_retrieve_pickups_as_group_member(self):
        self.client.force_login(user=self.member)
        response = self.client.get(self.pickup_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)

    def test_patch_pickup(self):
        response = self.client.patch(self.pickup_url, self.pickup_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, response.data)

    def test_patch_pickup_as_user(self):
        self.client.force_login(user=self.user)
        response = self.client.patch(self.pickup_url, self.pickup_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND, response.data)

    def test_patch_pickup_as_group_member(self):
        self.client.force_login(user=self.member)
        response = self.client.patch(self.pickup_url, self.pickup_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)

    def test_patch_pickup_as_group_member_activates_group(self):
        self.client.force_login(user=self.member)
        self.group.status = GroupStatus.INACTIVE.value
        self.group.save()
        self.client.patch(self.pickup_url, self.pickup_data, format='json')
        self.group.refresh_from_db()
        self.assertEqual(self.group.status, GroupStatus.ACTIVE.value)

    def test_patch_max_collectors_to_negative_value_fails(self):
        self.client.force_login(user=self.member)
        response = self.client.patch(self.pickup_url, {'max_collectors': -1})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST, response.data)

    def test_patch_past_pickup_fails(self):
        self.client.force_login(user=self.member)
        response = self.client.patch(self.past_pickup_url, self.pickup_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, response.data)

    def test_patch_as_newcomer_fails(self):
        newcomer = UserFactory()
        self.group.groupmembership_set.create(user=newcomer)
        self.client.force_login(user=newcomer)
        response = self.client.patch(self.pickup_url, {'max_collectors': 1}, format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, response.data)

    def test_join_pickup(self):
        response = self.client.post(self.join_url)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, response.data)

    def test_join_pickup_as_user(self):
        self.client.force_login(user=self.user)
        response = self.client.post(self.join_url)
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND, response.data)

    def test_join_pickup_as_member(self):
        self.client.force_login(user=self.member)
        response = self.client.post(self.join_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)

        # should have access to chat
        response = self.client.get(self.conversation_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_join_pickup_as_newcomer(self):
        newcomer = UserFactory()
        self.group.groupmembership_set.create(user=newcomer)
        self.client.force_login(user=newcomer)
        response = self.client.post(self.join_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)

    def test_join_pickup_as_member_activates_group(self):
        self.client.force_login(user=self.member)
        self.group.status = GroupStatus.INACTIVE.value
        self.group.save()
        self.client.post(self.join_url)
        self.group.refresh_from_db()
        self.assertEqual(self.group.status, GroupStatus.ACTIVE.value)

    def test_join_pickup_without_max_collectors_as_member(self):
        self.client.force_login(user=self.member)
        p = PickupDateFactory(max_collectors=None, store=self.store)
        response = self.client.post('/api/pickup-dates/{}/add/'.format(p.id))
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)

    def test_join_full_pickup_fails(self):
        self.client.force_login(user=self.member)
        self.pickup.max_collectors = 1
        self.pickup.save()
        u2 = UserFactory()
        GroupMembership.objects.create(group=self.group, user=u2)
        self.pickup.add_collector(u2)
        response = self.client.post(self.join_url)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, response.data)
        self.assertEqual(response.data['detail'], 'Pickup date is already full.')

    def test_join_past_pickup_fails(self):
        self.client.force_login(user=self.member)
        response = self.client.post(self.past_join_url)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, response.data)

    def test_leave_pickup(self):
        response = self.client.post(self.leave_url)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, response.data)

    def test_leave_pickup_as_user(self):
        self.client.force_login(user=self.user)
        response = self.client.post(self.leave_url)
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND, response.data)

    def test_leave_pickup_as_member(self):
        self.client.force_login(user=self.member)
        self.pickup.add_collector(self.member)
        response = self.client.post(self.leave_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)

        # should be removed from chat
        response = self.client.get(self.conversation_url)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_leave_pickup_as_newcomer(self):
        newcomer = UserFactory()
        self.group.groupmembership_set.create(user=newcomer)
        self.pickup.add_collector(newcomer)
        self.client.force_login(user=newcomer)
        response = self.client.post(self.leave_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)

    def test_leave_pickup_activates_group(self):
        self.client.force_login(user=self.member)
        self.pickup.add_collector(self.member)
        self.group.status = GroupStatus.INACTIVE.value
        self.group.save()
        self.client.post(self.leave_url)
        self.group.refresh_from_db()
        self.assertEqual(self.group.status, GroupStatus.ACTIVE.value)

    def test_leave_past_pickup_fails(self):
        self.client.force_login(user=self.member)
        self.past_pickup.add_collector(self.member)
        response = self.client.post(self.past_leave_url)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN, response.data)

    def test_get_conversation_not_as_collector(self):
        self.client.force_login(user=self.member)
        response = self.client.get(self.conversation_url)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        self.assertEqual(response.data['detail'], 'You are not in this conversation')

    def test_get_conversation_as_collector(self):
        self.client.force_login(user=self.member)
        self.pickup.add_collector(self.member)
        response = self.client.get(self.conversation_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertIn(self.member.id, response.data['participants'])
        self.assertEqual(response.data['type'], 'pickup')
class TestPickupDateSeriesCreationAPI(APITestCase, ExtractPaginationMixin):
    """
    This is an integration test for the pickup-date-series API
    """
    def setUp(self):

        self.member = UserFactory()
        self.group = GroupFactory(members=[
            self.member,
        ])
        self.store = StoreFactory(group=self.group)

    def test_create_and_get_recurring_series(self):
        url = '/api/pickup-date-series/'
        recurrence = rrule.rrule(
            freq=rrule.WEEKLY,
            byweekday=[0, 1]  # Monday and Tuesday
        )
        start_date = self.group.timezone.localize(datetime.now().replace(
            hour=20, minute=0))
        pickup_series_data = {
            'max_collectors': 5,
            'store': self.store.id,
            'rule': str(recurrence),
            'start_date': start_date
        }
        start_date = start_date.replace(second=0, microsecond=0)
        self.client.force_login(user=self.member)
        response = self.client.post(url, pickup_series_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED,
                         response.data)
        series_id = response.data['id']
        self.assertEqual(parse(response.data['start_date']), start_date)
        del response.data['id']
        del response.data['start_date']
        expected_series_data = {
            'max_collectors': 5,
            'store': self.store.id,
            'rule': str(recurrence),
            'description': ''
        }
        self.assertEqual(response.data, expected_series_data)

        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        for _ in response.data:
            self.assertEqual(parse(_['start_date']), start_date)
            del _['id']
            del _['start_date']
        self.assertEqual(response.data, [expected_series_data])

        response = self.client.get(url + str(series_id) + '/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(parse(response.data['start_date']), start_date)
        del response.data['id']
        del response.data['start_date']
        self.assertEqual(response.data, expected_series_data)

        url = '/api/pickup-dates/'
        created_pickup_dates = []
        # do recurrence calculation in local time to avoid daylight saving time problems
        tz = self.group.timezone
        dates_list = recurrence.replace(dtstart=timezone.now(
        ).astimezone(tz).replace(
            hour=20, minute=0, second=0, microsecond=0, tzinfo=None)).between(
                timezone.now().astimezone(tz).replace(tzinfo=None),
                timezone.now().astimezone(tz).replace(tzinfo=None) +
                relativedelta(weeks=4))
        dates_list = [tz.localize(d) for d in dates_list]

        response = self.get_results(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        # verify date field
        for response_data_item, expected_date in zip(response.data,
                                                     dates_list):
            self.assertEqual(parse(response_data_item['date']), expected_date,
                             response_data_item['date'])

        # verify non-date fields, don't need parsing
        for _ in response.data:
            del _['id']
            del _['date']
        for _ in dates_list:
            created_pickup_dates.append({
                'max_collectors': 5,
                'series': series_id,
                'collector_ids': [],
                'store': self.store.id,
                'description': ''
            })
        self.assertEqual(response.data, created_pickup_dates)

    def test_pickup_series_create_activates_group(self):
        url = '/api/pickup-date-series/'
        recurrence = rrule.rrule(
            freq=rrule.WEEKLY,
            byweekday=[0, 1]  # Monday and Tuesday
        )
        start_date = self.group.timezone.localize(datetime.now().replace(
            hour=20, minute=0))
        pickup_series_data = {
            'max_collectors': 5,
            'store': self.store.id,
            'rule': str(recurrence),
            'start_date': start_date
        }
        self.group.status = GroupStatus.INACTIVE.value
        self.group.save()
        self.client.force_login(user=self.member)
        self.client.post(url, pickup_series_data, format='json')
        self.group.refresh_from_db()
        self.assertEqual(self.group.status, GroupStatus.ACTIVE.value)
class TestPickupDateSeriesChangeAPI(APITestCase, ExtractPaginationMixin):
    """
    This is an integration test for the pickup-date-series API with pre-created series
    """
    def setUp(self):
        self.now = timezone.now()
        self.member = UserFactory()
        self.group = GroupFactory(members=[
            self.member,
        ])
        self.store = StoreFactory(group=self.group)
        self.series = PickupDateSeriesFactory(max_collectors=3,
                                              store=self.store)
        self.series.update_pickup_dates(start=lambda: self.now)

    def test_change_max_collectors_for_series(self):
        "should change all future instances (except for individually changed ones), but not past ones"
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        self.client.force_login(user=self.member)
        response = self.client.patch(url, {'max_collectors': 99})
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(response.data['max_collectors'], 99)

        url = '/api/pickup-dates/'
        response = self.get_results(url, {
            'series': self.series.id,
            'date_0': self.now
        })
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        for _ in response.data:
            self.assertEqual(_['max_collectors'], 99)

    def test_change_series_activates_group(self):
        self.group.status = GroupStatus.INACTIVE.value
        self.group.save()
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        self.client.force_login(user=self.member)
        self.client.patch(url, {'max_collectors': 99})
        self.group.refresh_from_db()
        self.assertEqual(self.group.status, GroupStatus.ACTIVE.value)

    def test_change_start_time(self):
        self.client.force_login(user=self.member)
        # get original times
        url = '/api/pickup-dates/'
        response = self.get_results(url, {
            'series': self.series.id,
            'date_0': self.now
        })
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        original_dates = [parse(_['date']) for _ in response.data]

        # change times
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        new_startdate = shift_date_in_local_time(
            self.series.start_date, relativedelta(hours=2, minutes=20),
            self.group.timezone)
        response = self.client.patch(url,
                                     {'start_date': new_startdate.isoformat()})
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(parse(response.data['start_date']), new_startdate)

        # compare resulting pickups
        url = '/api/pickup-dates/'
        response = self.get_results(url, {
            'series': self.series.id,
            'date_0': self.now
        })
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        for response_pickup, old_date in zip(response.data, original_dates):
            self.assertEqual(
                parse(response_pickup['date']),
                shift_date_in_local_time(old_date,
                                         relativedelta(hours=2, minutes=20),
                                         self.group.timezone))

    def test_change_start_date_to_future(self):
        self.client.force_login(user=self.member)
        # get original dates
        url = '/api/pickup-dates/'
        response = self.get_results(url, {
            'series': self.series.id,
            'date_0': self.now
        })
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        original_dates = [parse(_['date']) for _ in response.data]

        # change dates
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        new_startdate = shift_date_in_local_time(self.series.start_date,
                                                 relativedelta(days=5),
                                                 self.group.timezone)
        response = self.client.patch(url,
                                     {'start_date': new_startdate.isoformat()})
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(parse(response.data['start_date']), new_startdate)

        # compare resulting pickups
        url = '/api/pickup-dates/'
        response = self.get_results(url, {
            'series': self.series.id,
            'date_0': self.now
        })
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        for response_pickup, old_date in zip_longest(response.data,
                                                     original_dates):
            self.assertEqual(
                parse(response_pickup['date']),
                shift_date_in_local_time(old_date, relativedelta(days=5),
                                         self.group.timezone))

    def test_change_start_date_to_past(self):
        self.client.force_login(user=self.member)
        # get original dates
        url = '/api/pickup-dates/'
        response = self.get_results(url, {
            'series': self.series.id,
            'date_0': self.now
        })
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        original_dates = [parse(_['date']) for _ in response.data]

        # change dates
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        new_startdate = shift_date_in_local_time(self.series.start_date,
                                                 relativedelta(days=-5),
                                                 self.group.timezone)
        response = self.client.patch(url,
                                     {'start_date': new_startdate.isoformat()})
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(parse(response.data['start_date']), new_startdate)

        # compare resulting pickups
        url = '/api/pickup-dates/'
        response = self.get_results(url, {
            'series': self.series.id,
            'date_0': self.now
        })
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)

        # shifting 5 days to the past is similar to shifting 2 days to the future
        for response_pickup, old_date in zip_longest(response.data,
                                                     original_dates):
            new_date = shift_date_in_local_time(old_date,
                                                relativedelta(days=2),
                                                self.group.timezone)
            if new_date > self.now + relativedelta(
                    weeks=self.store.weeks_in_advance):
                # date too far in future
                self.assertIsNone(response_pickup)
            else:
                self.assertEqual(parse(response_pickup['date']), new_date)

    def test_set_end_date(self):
        self.client.force_login(user=self.member)
        # change rule
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        rule = rrulestr(self.series.rule, dtstart=self.now) \
            .replace(until=self.now + relativedelta(days=8))
        response = self.client.patch(url, {'rule': str(rule)})
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(response.data['rule'], str(rule))

        # compare resulting pickups
        url = '/api/pickup-dates/'
        response = self.get_results(url, {
            'series': self.series.id,
            'date_0': self.now
        })
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(len(response.data), 2, response.data)

    def test_set_end_date_with_users_have_joined_pickup(self):
        self.client.force_login(user=self.member)
        self.series.pickup_dates.last().collectors.add(self.member)
        # change rule
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        rule = rrulestr(self.series.rule, dtstart=self.now) \
            .replace(until=self.now)
        response = self.client.patch(url, {'rule': str(rule)})
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(response.data['rule'], str(rule))

        # compare resulting pickups
        url = '/api/pickup-dates/'
        response = self.get_results(url, {
            'series': self.series.id,
            'date_0': self.now
        })
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(len(response.data), 1, response.data)

    def test_delete_pickup_series(self):
        "the series should get removed, as well as all upcoming pickups if they don't have collectors"
        self.client.force_login(user=self.member)
        joined_pickup = self.series.pickup_dates.last()
        joined_pickup.collectors.add(self.member)

        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        response = self.client.delete(url)
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT,
                         response.data)

        url = '/api/pickup-dates/'
        response = self.get_results(url, {
            'series': self.series.id,
            'date_0': self.now
        })
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(len(response.data), 0, response.data)

        url = '/api/pickup-dates/{}/'.format(joined_pickup.id)
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(response.data['collector_ids'], [
            self.member.id,
        ])

    def test_change_max_collectors_to_invalid_number_fails(self):
        self.client.force_login(user=self.member)
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        response = self.client.patch(url, {'max_collectors': -1})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST,
                         response.data)

    def test_set_invalid_store_fails(self):
        unrelated_store = StoreFactory()

        self.client.force_login(user=self.member)
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        response = self.client.patch(url, {'store': unrelated_store.id})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST,
                         response.data)
        self.assertEqual(
            response.data,
            {'store': ["You are not member of the store's group."]})

    def test_set_multiple_rules_fails(self):
        self.client.force_login(user=self.member)
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        response = self.client.patch(
            url, {'rule': 'RRULE:FREQ=WEEKLY;BYDAY=MO\nRRULE:FREQ=MONTHLY'})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST,
                         response.data)
        self.assertEqual(
            response.data,
            {'rule': ['Only single recurrence rules are allowed.']})

    def test_keep_changes_to_max_collectors(self):
        self.client.force_login(user=self.member)
        pickup_under_test = self.series.pickup_dates.first()
        url = '/api/pickup-dates/{}/'.format(pickup_under_test.id)

        # change setting of pickup
        response = self.client.patch(url, {'max_collectors': 666})
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(response.data['max_collectors'], 666)

        # run regular update command of series
        self.series.update_pickup_dates()

        # check if changes persist
        url = '/api/pickup-dates/{}/'.format(pickup_under_test.id)
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(response.data['max_collectors'], 666)

    def test_keep_changes_to_date(self):
        self.client.force_login(user=self.member)
        pickup_under_test = self.series.pickup_dates.first()
        url = '/api/pickup-dates/{}/'.format(pickup_under_test.id)

        # change setting of pickup
        target_date = timezone.now() + relativedelta(hours=2)
        response = self.client.patch(url, {'date': target_date})
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(parse(response.data['date']), target_date)

        # run regular update command of series
        self.series.update_pickup_dates()

        # check if changes persist
        url = '/api/pickup-dates/{}/'.format(pickup_under_test.id)
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(parse(response.data['date']), target_date)

    def test_keep_changes_to_description(self):
        self.client.force_login(user=self.member)
        pickup_under_test = self.series.pickup_dates.first()
        url = '/api/pickup-dates/{}/'.format(pickup_under_test.id)

        # change setting of pickup
        response = self.client.patch(url, {'description': 'asdf'})
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(response.data['description'], 'asdf')

        # run regular update command of series
        self.series.update_pickup_dates()

        # check if changes persist
        url = '/api/pickup-dates/{}/'.format(pickup_under_test.id)
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(response.data['description'], 'asdf')

    def test_dont_mark_as_changed_if_data_is_equal(self):
        self.client.force_login(user=self.member)
        pickup_under_test = self.series.pickup_dates.first()
        url = '/api/pickup-dates/{}/'.format(pickup_under_test.id)

        # change setting of pickup
        response = self.client.patch(
            url, {
                'date': pickup_under_test.date,
                'max_collectors': pickup_under_test.max_collectors
            })
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)

        pickup_under_test = PickupDate.objects.get(id=pickup_under_test.id)

        self.assertFalse(pickup_under_test.is_max_collectors_changed)
        self.assertFalse(pickup_under_test.is_date_changed)

    def test_keep_date_if_pickup_has_collectors(self):
        """
        https://github.com/yunity/foodsaving-frontend/issues/596
        It's unexpected if the date changes automatically when people have joined
        """
        self.client.force_login(user=self.member)
        pickup_under_test = self.series.pickup_dates.first()
        url = '/api/pickup-dates/{}/add/'.format(pickup_under_test.id)

        # join pickup
        response = self.client.post(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        original_date = pickup_under_test.date

        # change series date
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        response = self.client.patch(
            url,
            {'start_date': self.series.start_date + relativedelta(hours=1)})
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)

        # check if time of pickup is the same
        url = '/api/pickup-dates/{}/'.format(pickup_under_test.id)
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(parse(response.data['date']), original_date,
                         "time shouldn't change!")

    def test_invalid_rule_fails(self):
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        self.client.force_login(user=self.member)
        response = self.client.patch(url, {'rule': 'FREQ=WEEKLY;BYDAY='})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST,
                         response.data)
Exemple #7
0
class TestStoresAPI(APITestCase, ExtractPaginationMixin):
    def setUp(self):
        self.url = '/api/stores/'

        # group with two members and one store
        self.member = UserFactory()
        self.member2 = UserFactory()
        self.group = GroupFactory(members=[self.member, self.member2])
        self.store = StoreFactory(group=self.group)
        self.store_url = self.url + str(self.store.id) + '/'

        # not a member
        self.user = UserFactory()

        # another store for above group
        self.store_data = {
            'name': faker.name(),
            'description': faker.name(),
            'group': self.group.id,
            'address': faker.address(),
            'latitude': faker.latitude(),
            'longitude': faker.longitude()
        }

        # another group
        self.different_group = GroupFactory(members=[self.member2])

    def test_create_store(self):
        response = self.client.post(self.url, self.store_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_create_store_as_user(self):
        self.client.force_login(user=self.user)
        response = self.client.post(self.url, self.store_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_create_store_as_group_member(self):
        self.client.force_login(user=self.member)
        response = self.client.post(self.url, self.store_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['name'], self.store_data['name'])

    def test_create_store_as_newcomer_fails(self):
        newcomer = UserFactory()
        self.group.groupmembership_set.create(user=newcomer)
        self.client.force_login(user=newcomer)
        response = self.client.post(self.url, self.store_data, format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_create_store_activates_group(self):
        self.group.status = GroupStatus.INACTIVE.value
        self.group.save()
        self.client.force_login(user=self.member)
        self.client.post(self.url, self.store_data, format='json')
        self.group.refresh_from_db()
        self.assertEqual(self.group.status, GroupStatus.ACTIVE.value)

    def test_create_store_with_short_name_fails(self):
        self.client.force_login(user=self.member)
        data = deepcopy(self.store_data)
        data['name'] = 's'
        response = self.client.post(self.url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

    def test_list_stores(self):
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_list_stores_as_user(self):
        self.client.force_login(user=self.user)
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 0)

    def test_list_stores_as_group_member(self):
        self.client.force_login(user=self.member)
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 1)

    def test_retrieve_stores(self):
        response = self.client.get(self.store_url)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_retrieve_stores_as_user(self):
        self.client.force_login(user=self.user)
        response = self.client.get(self.store_url)
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

    def test_retrieve_stores_as_group_member(self):
        self.client.force_login(user=self.member)
        response = self.client.get(self.store_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_patch_store(self):
        response = self.client.patch(self.store_url,
                                     self.store_data,
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_patch_store_as_user(self):
        self.client.force_login(user=self.user)
        response = self.client.patch(self.store_url,
                                     self.store_data,
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

    def test_patch_store_as_group_member(self):
        self.client.force_login(user=self.member)
        response = self.client.patch(self.store_url,
                                     self.store_data,
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_edit_store_as_newcomer_fails(self):
        newcomer = UserFactory()
        self.group.groupmembership_set.create(user=newcomer)
        self.client.force_login(user=newcomer)
        response = self.client.patch(self.store_url,
                                     self.store_data,
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_patch_store_activates_group(self):
        self.group.status = GroupStatus.INACTIVE.value
        self.group.save()
        self.client.force_login(user=self.member)
        self.client.patch(self.store_url, self.store_data, format='json')
        self.group.refresh_from_db()
        self.assertEqual(self.group.status, GroupStatus.ACTIVE.value)

    def test_valid_status(self):
        self.client.force_login(user=self.member)
        response = self.client.patch(self.store_url, {'status': 'active'},
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_invalid_status(self):
        self.client.force_login(user=self.member)
        response = self.client.patch(self.store_url, {'status': 'foobar'},
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

    def test_change_group_as_member_in_one(self):
        self.client.force_login(user=self.member)
        response = self.client.patch(self.store_url,
                                     {'group': self.different_group.id},
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_change_group_as_member_in_both(self):
        self.client.force_login(user=self.member2)
        response = self.client.patch(self.store_url,
                                     {'group': self.different_group.id},
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        response = self.client.patch(self.store_url, {'group': self.group.id},
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_delete_stores(self):
        response = self.client.delete(self.store_url)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_delete_stores_as_user(self):
        self.client.force_login(user=self.user)
        response = self.client.delete(self.store_url)
        self.assertEqual(response.status_code,
                         status.HTTP_405_METHOD_NOT_ALLOWED)

    def test_delete_stores_as_group_member(self):
        self.client.force_login(user=self.member)
        response = self.client.delete(self.store_url)
        self.assertEqual(response.status_code,
                         status.HTTP_405_METHOD_NOT_ALLOWED)
class TestAgreementsAPI(APITestCase):
    def setUp(self):
        self.normal_member = UserFactory()
        self.agreement_manager = UserFactory()
        self.group = GroupFactory(members=[
            self.normal_member,
            self.agreement_manager,
        ])
        self.agreement = Agreement.objects.create(group=self.group,
                                                  title=faker.text(),
                                                  content=faker.text())
        membership = GroupMembership.objects.get(group=self.group,
                                                 user=self.agreement_manager)
        membership.roles.append(roles.GROUP_AGREEMENT_MANAGER)
        membership.save()

        # other group/agreement that neither user is part of
        self.other_group = GroupFactory()
        self.other_agreement = Agreement.objects.create(group=self.other_group,
                                                        title=faker.text(),
                                                        content=faker.text())

    def test_can_create_agreement(self):
        self.client.force_login(user=self.agreement_manager)
        response = self.client.post('/api/agreements/', {
            'title': faker.text(),
            'content': faker.text(),
            'group': self.group.id
        })
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)

    def test_cannot_create_agreement_for_another_group(self):
        self.client.force_login(user=self.agreement_manager)
        response = self.client.post(
            '/api/agreements/', {
                'title': faker.text(),
                'content': faker.text(),
                'group': self.other_group.id
            })
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_can_update_agreement(self):
        self.client.force_login(user=self.agreement_manager)
        response = self.client.patch(
            '/api/agreements/{}/'.format(self.agreement.id),
            {'title': faker.name()})
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_normal_member_cannot_create_agreement(self):
        self.client.force_login(user=self.normal_member)
        response = self.client.post('/api/agreements/', {
            'title': faker.name(),
            'content': faker.text(),
            'group': self.group.id
        })
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

    def test_list_agreements(self):
        self.client.force_login(user=self.normal_member)
        response = self.client.get('/api/agreements/')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 1)
        self.assertEqual(response.data[0]['agreed'], False)

    def test_view_agreement(self):
        self.client.force_login(user=self.normal_member)
        response = self.client.get('/api/agreements/{}/'.format(
            self.agreement.id))
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['title'], self.agreement.title)
        self.assertEqual(response.data['content'], self.agreement.content)

    def test_can_agree(self):
        self.client.force_login(user=self.normal_member)
        response = self.client.post('/api/agreements/{}/agree/'.format(
            self.agreement.id))
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['agreed'], True)

    def test_can_agree_is_idempotent(self):
        self.client.force_login(user=self.normal_member)
        response = self.client.post('/api/agreements/{}/agree/'.format(
            self.agreement.id))
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        response = self.client.post('/api/agreements/{}/agree/'.format(
            self.agreement.id))
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(
            UserAgreement.objects.filter(user=self.normal_member,
                                         agreement=self.agreement).count(), 1)

    def test_cannot_view_agreements_for_other_groups(self):
        self.client.force_login(user=self.normal_member)
        response = self.client.get('/api/agreements/{}/'.format(
            self.other_agreement.id))
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

    def test_cannot_agree_agreements_for_other_groups(self):
        self.client.force_login(user=self.normal_member)
        response = self.client.post('/api/agreements/{}/agree/'.format(
            self.other_agreement.id))
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

    def test_can_set_group_agreement(self):
        self.client.force_login(user=self.agreement_manager)
        response = self.client.patch('/api/groups/{}/'.format(self.group.id),
                                     {'active_agreement': self.agreement.id})
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_can_unset_group_agreement(self):
        self.client.force_login(user=self.agreement_manager)
        self.group.active_agreement = self.agreement
        self.group.save()
        # using json.dumps as otherwise it sends an empty string, but we want it to send json value "null"
        response = self.client.patch('/api/groups/{}/'.format(self.group.id),
                                     json.dumps({'active_agreement': None}),
                                     content_type='application/json')

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['active_agreement'], None)

    def test_cannot_set_group_agreement_if_for_wrong_group(self):
        self.client.force_login(user=self.agreement_manager)
        response = self.client.patch(
            '/api/groups/{}/'.format(self.group.id),
            {'active_agreement': self.other_agreement.id})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

    def test_normal_user_cannot_group_agreement(self):
        self.client.force_login(user=self.normal_member)
        response = self.client.patch('/api/groups/{}/'.format(self.group.id),
                                     {'active_agreement': self.agreement.id})
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
class FeedbackTest(APITestCase, ExtractPaginationMixin):
    def setUp(self):
        self.url = '/api/feedback/'

        self.member = UserFactory()
        self.collector = UserFactory()
        self.collector2 = UserFactory()
        self.collector3 = UserFactory()
        self.evil_collector = UserFactory()
        self.group = GroupFactory(members=[
            self.member, self.collector, self.evil_collector, self.collector2,
            self.collector3
        ])
        self.store = StoreFactory(group=self.group)
        self.pickup = PickupDateFactory(
            store=self.store,
            date=timezone.now() + relativedelta(days=1),
            collectors=[self.collector, self.collector2, self.collector3],
        )

        # not a member of the group
        self.user = UserFactory()

        # past pickup date
        self.past_pickup = PickupDateFactory(
            store=self.store,
            date=timezone.now() - relativedelta(days=1),
            collectors=[
                self.collector, self.evil_collector, self.collector2,
                self.collector3
            ],
        )

        # old pickup date with feedback
        self.old_pickup = PickupDateFactory(
            store=self.store,
            date=timezone.now() -
            relativedelta(days=settings.FEEDBACK_POSSIBLE_DAYS + 2),
            collectors=[
                self.collector3,
            ])
        self.old_feedback = FeedbackFactory(about=self.old_pickup,
                                            given_by=self.collector3)

        # create feedback for POST method
        self.feedback_post = {
            'about': self.past_pickup.id,
            'weight': 2,
            'comment': 'asfjk'
        }

        # create feedback for POST method without weight and comment
        self.feedback_without_weight_comment = {
            'about': self.past_pickup.id,
        }

        # create feedback to future pickup
        self.future_feedback_post = {
            'about': self.pickup.id,
            'weight': 2,
            'comment': 'asfjk'
        }

        # create feedback for an old pickup
        self.feedback_for_old_pickup = {
            'about': self.old_pickup.id,
            'weight': 5,
            'comment': 'this is long ago'
        }

        # create feedback for GET method
        self.feedback_get = {
            'given_by': self.collector,
            'about': self.past_pickup,
            'weight': 2,
            'comment': 'asfjk2'
        }

        self.feedback_get_2 = {
            'given_by': self.collector2,
            'about': self.past_pickup,
            'weight': 2,
            'comment': 'asfjk'
        }

        # create 2 instances of feedback for GET method
        self.feedback = Feedback.objects.create(**self.feedback_get)
        Feedback.objects.create(**self.feedback_get_2)

        self.feedback_url = self.url + str(self.feedback.id) + '/'
        self.old_feedback_url = self.url + str(self.old_feedback.id) + '/'

    def test_create_feedback_fails_as_non_user(self):
        """
        Non-User is not allowed to give feedback.
        """
        response = self.client.post(self.url,
                                    self.feedback_post,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN,
                         response.data)

    def test_create_feedback_fails_as_non_group_member(self):
        """
        User is not allowed to give feedback when not a member of the store's group.
        """
        self.client.force_login(user=self.user)
        response = self.client.post(self.url,
                                    self.feedback_post,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST,
                         response.data)
        self.assertEqual(
            response.data,
            {'about': ['You are not member of the store\'s group.']})

    def test_create_feedback_fails_as_non_collector(self):
        """
        Group Member is not allowed to give feedback when he is not assigned to the pickup.
        """
        self.client.force_login(user=self.member)
        response = self.client.post(self.url,
                                    self.feedback_post,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST,
                         response.data)
        self.assertEqual(response.data,
                         {'about': ['You aren\'t assigned to the pickup.']})

    def test_create_feedback_works_as_collector(self):
        """
        Editor is allowed to give feedback when he is assigned to the Pickup.
        """
        self.client.force_login(user=self.collector3)
        response = self.client.post(self.url,
                                    self.feedback_post,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED,
                         response.data)

    def test_create_feedback_as_newcomer_collector(self):
        """
        Newcomer is allowed to give feedback when he is assigned to the Pickup.
        """
        newcomer = UserFactory()
        self.group.groupmembership_set.create(user=newcomer)
        self.past_pickup.add_collector(newcomer)
        self.client.force_login(user=newcomer)
        response = self.client.post(self.url,
                                    self.feedback_post,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED,
                         response.data)

    def test_create_feedback_activates_group(self):
        self.group.status = GroupStatus.INACTIVE.value
        self.group.save()
        self.client.force_login(user=self.collector3)
        self.client.post(self.url, self.feedback_post, format='json')
        self.group.refresh_from_db()
        self.assertEqual(self.group.status, GroupStatus.ACTIVE.value)

    def test_create_feedback_twice_fails_for_one_pickup(self):
        """
        Collector is not allowed to give feedback more than one time to the Pickup.
        """
        self.client.force_login(user=self.collector3)
        response = self.client.post(self.url,
                                    self.feedback_post,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED,
                         response.data)
        response = self.client.post(self.url,
                                    self.feedback_post,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST,
                         response.data)
        self.assertEqual(
            response.data, {
                'non_field_errors':
                ['The fields about, given_by must make a unique set.']
            })

    def test_create_feedback_fails_for_old_pickup(self):
        """
        Collector is not allowed to give feedback for old Pickups.
        """
        self.client.force_login(user=self.collector3)
        response = self.client.post(self.url,
                                    self.feedback_for_old_pickup,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST,
                         response.data)
        self.assertEqual(
            response.data, {
                'about': [
                    'You can\'t give feedback for pickups more than {} days ago.'
                    .format(settings.FEEDBACK_POSSIBLE_DAYS)
                ]
            })

    def test_create_feedback_without_weight(self):
        """
        Weight field can be empty
        """
        self.client.force_login(user=self.collector3)
        response = self.client.post(self.url, {
            k: v
            for (k, v) in self.feedback_post.items() if k is not 'weight'
        })
        self.assertEqual(response.status_code, status.HTTP_201_CREATED,
                         response.data)
        self.assertIsNone(response.data['weight'])

    def test_create_feedback_without_comment(self):
        """
        Comment field can be empty
        """
        self.client.force_login(user=self.collector3)
        response = self.client.post(self.url, {
            k: v
            for (k, v) in self.feedback_post.items() if k is not 'comment'
        })
        self.assertEqual(response.status_code, status.HTTP_201_CREATED,
                         response.data)
        self.assertEqual(response.data['comment'], '')

    def test_weight_and_comment_is_null_fails(self):
        """
        Both comment and weight cannot be empty
        """
        self.client.force_login(user=self.collector3)
        response = self.client.post(self.url,
                                    self.feedback_without_weight_comment,
                                    format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST,
                         response.data)
        self.assertEqual(
            response.data,
            {'non_field_errors': ['Both comment and weight cannot be blank.']})

    def test_list_feedback_fails_as_non_user(self):
        """
        Non-User is NOT allowed to see list of feedback
        """
        response = self.get_results(self.url)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN,
                         response.data)

    def test_list_feedback_works_as_non_group_member(self):
        """
        Non-Member doesn't see feedback but an empty list
        """
        self.client.force_login(user=self.user)
        response = self.get_results(self.url)
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(len(response.data['feedback']), 0)

    def test_list_feedback_works_as_group_member(self):
        """
        Member is allowed to see list of feedback
        """
        self.client.force_login(user=self.member)
        with self.assertNumQueries(3):
            response = self.get_results(self.url)
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        feedback = response.data['feedback']
        self.assertEqual(len(feedback), 3)

        # check related data
        pickup_ids = set(f['about'] for f in feedback)
        self.assertEqual(len(response.data['pickups']), len(pickup_ids))
        self.assertEqual(set(p['id'] for p in response.data['pickups']),
                         pickup_ids)

    def test_list_feedback_works_as_collector(self):
        """
        Collector is allowed to see list of feedback
        """
        self.client.force_login(user=self.collector)
        response = self.get_results(self.url)
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(len(response.data['feedback']), 3)

    def test_retrieve_feedback_fails_as_non_user(self):
        """
        Non-User is NOT allowed to see single feedback
        """
        response = self.get_results(self.feedback_url)
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN,
                         response.data)

    def test_retrieve_feedback_fails_as_non_group_member(self):
        """
        Non-Member is NOT allowed to see single feedback
        """
        self.client.force_login(user=self.user)
        response = self.get_results(self.feedback_url)
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND,
                         response.data)

    def test_retrieve_feedback_works_as_group_member(self):
        """
        Member is allowed to see single feedback
        """
        self.client.force_login(user=self.member)
        response = self.get_results(self.feedback_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)

    def test_retrieve_feedback_works_as_collector(self):
        """
        Collector is allowed to see list of feedback
        """
        self.client.force_login(user=self.collector)
        response = self.get_results(self.feedback_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)

    def test_create_future_feedback_fails_as_collector(self):
        """
        Collector is NOT allowed to leave feedback for future pickup
        """
        self.client.force_login(user=self.collector3)
        response = self.client.post(self.url, self.future_feedback_post)
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST,
                         response.data)
        self.assertEqual(response.data,
                         {'about': ['The pickup is not done yet']})

    def test_patch_feedback_fails_as_non_user(self):
        """
        Non-user is not allowed to change feedback
        """
        response = self.client.patch(self.feedback_url,
                                     self.feedback_post,
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN,
                         response.data)

    def test_patch_feedback_fails_as_user(self):
        """
        User is not allowed to change feedback
        """
        self.client.force_login(user=self.user)
        response = self.client.patch(self.feedback_url,
                                     self.feedback_post,
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND,
                         response.data)

    def test_patch_feedback_fails_as_group_member(self):
        """
        Group member is not allowed to change feedback
        """
        self.client.force_login(user=self.member)
        response = self.client.patch(self.feedback_url,
                                     self.feedback_post,
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN,
                         response.data)

    def test_patch_feedback_fails_as_evil_collector(self):
        """
        A collector is not allowed to change feedback if he didn't created it
        """
        self.client.force_login(user=self.evil_collector)
        response = self.client.patch(self.feedback_url, {'weight': 3},
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN,
                         response.data)

    def test_patch_feedback_works_as_collector(self):
        """
        Collector is allowed to change feedback
        """
        self.client.force_login(user=self.collector)
        response = self.client.patch(self.feedback_url, {'weight': 3},
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(response.data['weight'], 3)

    def test_patch_feedback_activates_group(self):
        self.group.status = GroupStatus.INACTIVE.value
        self.group.save()
        self.client.force_login(user=self.collector)
        self.client.patch(self.feedback_url, {'weight': 3}, format='json')
        self.group.refresh_from_db()
        self.assertEqual(self.group.status, GroupStatus.ACTIVE.value)

    def test_patch_weight_to_negative_value_fails(self):
        """
        Collector cannot change weight to negative value
        """
        self.client.force_login(user=self.collector)
        response = self.client.patch(self.feedback_url, {'weight': -1},
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST,
                         response.data)

    def test_patch_feedback_fails_if_pickup_too_old(self):
        self.client.force_login(user=self.collector3)
        response = self.client.patch(self.old_feedback_url, {'weight': 499},
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN,
                         response.data)
        self.assertEqual(
            response.data['detail'],
            'You can\'t give feedback for pickups more than {} days ago.'.
            format(settings.FEEDBACK_POSSIBLE_DAYS))

    def test_patch_feedback_to_remove_weight(self):
        self.client.force_login(user=self.collector)
        response = self.client.patch(self.feedback_url, {'weight': None},
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(response.data['weight'], None)

    def test_patch_feedback_to_remove_weight_fails_if_comment_is_empty(self):
        self.client.force_login(user=self.collector)
        self.feedback.comment = ''
        self.feedback.save()
        response = self.client.patch(self.feedback_url, {'weight': None},
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST,
                         response.data)
        self.assertEqual(
            response.data,
            {'non_field_errors': ['Both comment and weight cannot be blank.']})

    def test_patch_feedback_to_remove_comment(self):
        self.client.force_login(user=self.collector)
        response = self.client.patch(self.feedback_url, {'comment': ''},
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK,
                         response.data)
        self.assertEqual(response.data['comment'], '')

    def test_patch_feedback_to_remove_comment_fails_if_weight_is_empty(self):
        self.client.force_login(user=self.collector)
        self.feedback.weight = None
        self.feedback.save()
        response = self.client.patch(self.feedback_url, {'comment': ''},
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST,
                         response.data)
        self.assertEqual(
            response.data,
            {'non_field_errors': ['Both comment and weight cannot be blank.']})

    def test_patch_feedback_to_remove_comment_and_weight_fails(self):
        self.client.force_login(user=self.collector)
        response = self.client.patch(self.feedback_url, {
            'comment': '',
            'weight': None
        },
                                     format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST,
                         response.data)
        self.assertEqual(
            response.data,
            {'non_field_errors': ['Both comment and weight cannot be blank.']})
class TestPickupDateSeriesChangeAPI(APITestCase, ExtractPaginationMixin):
    """
    This is an integration test for the pickup-date-series API with pre-created series
    """

    def setUp(self):
        self.now = timezone.now()
        self.member = UserFactory()
        self.group = GroupFactory(members=[self.member])
        self.store = StoreFactory(group=self.group)
        self.series = PickupDateSeriesFactory(max_collectors=3, store=self.store)

    def test_change_max_collectors_for_series(self):
        "should change all future instances (except for individually changed ones), but not past ones"
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        self.client.force_login(user=self.member)
        response = self.client.patch(url, {'max_collectors': 99})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(response.data['max_collectors'], 99)

        url = '/api/pickup-dates/'
        response = self.get_results(url, {'series': self.series.id, 'date_min': self.now})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        for _ in response.data:
            self.assertEqual(_['max_collectors'], 99)

    def test_change_series_activates_group(self):
        self.group.status = GroupStatus.INACTIVE.value
        self.group.save()
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        self.client.force_login(user=self.member)
        self.client.patch(url, {'max_collectors': 99})
        self.group.refresh_from_db()
        self.assertEqual(self.group.status, GroupStatus.ACTIVE.value)

    def test_change_start_time(self):
        self.client.force_login(user=self.member)
        # get original times
        url = '/api/pickup-dates/'
        response = self.get_results(url, {'series': self.series.id, 'date_min': self.now})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        original_dates = [parse(_['date']) for _ in response.data]

        # change times
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        new_startdate = shift_date_in_local_time(
            self.series.start_date, relativedelta(hours=2, minutes=20), self.group.timezone
        )
        response = self.client.patch(url, {'start_date': new_startdate.isoformat()})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(parse(response.data['start_date']), new_startdate)

        # compare resulting pickups
        url = '/api/pickup-dates/'
        response = self.get_results(url, {'series': self.series.id, 'date_min': self.now})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        for response_pickup, old_date in zip(response.data, original_dates):
            self.assertEqual(
                parse(response_pickup['date']),
                shift_date_in_local_time(old_date, relativedelta(hours=2, minutes=20), self.group.timezone)
            )

    def test_change_start_date_to_future(self):
        self.client.force_login(user=self.member)
        # get original dates
        url = '/api/pickup-dates/'
        response = self.get_results(url, {'series': self.series.id, 'date_min': self.now})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        original_dates = [parse(_['date']) for _ in response.data]

        # change dates
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        new_startdate = shift_date_in_local_time(self.series.start_date, relativedelta(days=5), self.group.timezone)
        response = self.client.patch(url, {'start_date': new_startdate.isoformat()})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(parse(response.data['start_date']), new_startdate)

        # compare resulting pickups
        url = '/api/pickup-dates/'
        response = self.get_results(url, {'series': self.series.id, 'date_min': self.now})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        for response_pickup, old_date in zip_longest(response.data, original_dates):
            self.assertEqual(
                parse(response_pickup['date']),
                shift_date_in_local_time(old_date, relativedelta(days=5), self.group.timezone)
            )

    def test_change_start_date_to_past(self):
        self.client.force_login(user=self.member)
        # get original dates
        url = '/api/pickup-dates/'
        response = self.get_results(url, {'series': self.series.id, 'date_min': self.now})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        original_dates = [parse(_['date']) for _ in response.data]

        # change dates
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        new_startdate = shift_date_in_local_time(self.series.start_date, relativedelta(days=-5), self.group.timezone)
        response = self.client.patch(url, {'start_date': new_startdate.isoformat()})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(parse(response.data['start_date']), new_startdate)

        # compare resulting pickups
        url = '/api/pickup-dates/'
        response = self.get_results(url, {'series': self.series.id, 'date_min': self.now})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)

        # shifting 5 days to the past is similar to shifting 2 days to the future
        for response_pickup, old_date in zip_longest(response.data, original_dates):
            new_date = shift_date_in_local_time(old_date, relativedelta(days=2), self.group.timezone)
            if new_date > self.now + relativedelta(weeks=self.store.weeks_in_advance):
                # date too far in future
                self.assertIsNone(response_pickup)
            else:
                self.assertEqual(parse(response_pickup['date']), new_date)

    def test_set_end_date(self):
        self.client.force_login(user=self.member)
        # change rule
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        rule = rrulestr(self.series.rule, dtstart=self.now) \
            .replace(until=self.now + relativedelta(days=8))
        response = self.client.patch(url, {'rule': str(rule)})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(response.data['rule'], str(rule))

        # compare resulting pickups
        url = '/api/pickup-dates/'
        response = self.get_results(url, {'series': self.series.id, 'date_min': self.now})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(len(response.data), 2, response.data)

    def test_set_end_date_with_users_have_joined_pickup(self):
        self.client.force_login(user=self.member)
        self.series.pickup_dates.last().add_collector(self.member)
        # change rule
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        rule = rrulestr(self.series.rule, dtstart=self.now) \
            .replace(until=self.now)
        response = self.client.patch(url, {
            'rule': str(rule),
        })
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(response.data['rule'], str(rule))

        # compare resulting pickups
        url = '/api/pickup-dates/'
        response = self.get_results(url, {'series': self.series.id, 'date_min': self.now})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(len(response.data), 1, response.data)

    def test_disable_pickup_series(self):
        "the series should get removed, empty upcoming pickups disabled, non-empty pickups kept"
        self.client.force_login(user=self.member)
        joined_pickup = self.series.pickup_dates.last()
        joined_pickup.add_collector(self.member)

        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        response = self.client.delete(url)
        self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT, response.data)

        url = '/api/pickup-dates/'
        response = self.get_results(url, {'date_min': self.now})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        empty_pickups = [p for p in response.data if len(p['collector_ids']) == 0]
        self.assertEqual(empty_pickups, [])

        url = '/api/pickup-dates/{}/'.format(joined_pickup.id)
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(response.data['collector_ids'], [self.member.id])
        self.assertFalse(response.data['is_disabled'])

    def test_change_max_collectors_to_invalid_number_fails(self):
        self.client.force_login(user=self.member)
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        response = self.client.patch(url, {'max_collectors': -1})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST, response.data)

    def test_set_invalid_store_fails(self):
        original_store = self.series.store
        unrelated_store = StoreFactory()

        self.client.force_login(user=self.member)
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        response = self.client.patch(url, {'store': unrelated_store.id})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(response.data['store'], original_store.id)
        self.series.refresh_from_db()
        self.assertEqual(self.series.store.id, original_store.id)

    def test_set_multiple_rules_fails(self):
        self.client.force_login(user=self.member)
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        response = self.client.patch(url, {'rule': 'RRULE:FREQ=WEEKLY;BYDAY=MO\nRRULE:FREQ=MONTHLY'})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST, response.data)
        self.assertEqual(response.data, {'rule': ['Only single recurrence rules are allowed.']})

    def test_keep_changes_to_max_collectors(self):
        self.client.force_login(user=self.member)
        pickup_under_test = self.series.pickup_dates.first()
        url = '/api/pickup-dates/{}/'.format(pickup_under_test.id)

        # change setting of pickup
        response = self.client.patch(url, {'max_collectors': 666})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(response.data['max_collectors'], 666)

        # run regular update command of series
        self.series.update_pickups()

        # check if changes persist
        url = '/api/pickup-dates/{}/'.format(pickup_under_test.id)
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(response.data['max_collectors'], 666)

        # modify series max_collectors
        series_url = '/api/pickup-date-series/{}/'.format(self.series.id)
        response = self.client.patch(series_url, {'max_collectors': 20})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)

        # check if changes persist
        url = '/api/pickup-dates/{}/'.format(pickup_under_test.id)
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(response.data['max_collectors'], 666)

    def test_keep_changes_to_description(self):
        self.client.force_login(user=self.member)
        pickup_under_test = self.series.pickup_dates.first()
        url = '/api/pickup-dates/{}/'.format(pickup_under_test.id)

        # change setting of pickup
        response = self.client.patch(url, {'description': 'asdf'})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(response.data['description'], 'asdf')

        # run regular update command of series
        self.series.update_pickups()

        # check if changes persist
        url = '/api/pickup-dates/{}/'.format(pickup_under_test.id)
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(response.data['description'], 'asdf')

        # modify series description
        series_url = '/api/pickup-date-series/{}/'.format(self.series.id)
        response = self.client.patch(series_url, {'description': 'new series description'})
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)

        # check if changes persist
        url = '/api/pickup-dates/{}/'.format(pickup_under_test.id)
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK, response.data)
        self.assertEqual(response.data['description'], 'asdf')

    def test_invalid_rule_fails(self):
        url = '/api/pickup-date-series/{}/'.format(self.series.id)
        self.client.force_login(user=self.member)
        response = self.client.patch(url, {'rule': 'FREQ=WEEKLY;BYDAY='})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST, response.data)

    def test_keeps_joined_pickups(self):
        # join pickups
        [p.add_collector(self.member) for p in self.series.pickup_dates.all()]

        # change series rule to add another day
        recurrence = rrule.rrule(
            freq=rrule.WEEKLY,
            byweekday=[
                self.now.weekday(),
                (self.now + relativedelta(days=1)).weekday(),
            ],
        )
        series_url = '/api/pickup-date-series/{}/'.format(self.series.id)
        self.client.force_login(user=self.member)
        response = self.client.patch(series_url, {
            'rule': str(recurrence),
        })
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.series.refresh_from_db()

        response = self.client.get('/api/pickup-dates/?series={}'.format(self.series.id))
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual([parse(p['date']) for p in response.data['results']], [
            shift_date_in_local_time(self.series.start_date, delta, self.group.timezone) for delta in (
                relativedelta(days=0),
                relativedelta(days=1),
                relativedelta(days=7),
                relativedelta(days=8),
                relativedelta(days=14),
                relativedelta(days=15),
                relativedelta(days=21),
                relativedelta(days=22),
            )
        ])
        self.assertEqual(
            [p['collector_ids'] for p in response.data['results']],
            list(interleave(
                [[self.member.id] for _ in range(4)],
                [[] for _ in range(4)],
            )),
        )

    def test_removes_empty_leftover_pickups_when_reducing_weeks_in_advance(self):
        # join one pickup
        joined_pickup = self.series.pickup_dates.first()
        joined_pickup.add_collector(self.member)

        # change weeks_in_advance
        store_url = '/api/stores/{}/'.format(self.store.id)
        self.client.force_login(user=self.member)
        response = self.client.patch(store_url, {
            'weeks_in_advance': 1,
        })
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        response = self.get_results('/api/pickup-dates/?series={}'.format(self.series.id))
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 1)
        self.assertEqual(response.data[0]['id'], joined_pickup.id)
        self.assertEqual(response.data[0]['collector_ids'], [self.member.id])

    def test_cannot_move_pickups_in_a_series(self):
        self.client.force_login(user=self.member)
        pickup = self.series.pickup_dates.last()

        response = self.client.patch(
            '/api/pickup-dates/{}/'.format(pickup.id), {'date': pickup.date + relativedelta(weeks=7)}
        )

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('You can\'t move pickups', response.data['date'][0])