def test_set_instance_via_serializer_context(self):
        """
        Test that seriaizer.set_context gets the order from serializer.context['order'].
        """
        order = Order()
        serializer = mock.Mock(context={'order': order})

        validator = OrderInStatusValidator(allowed_statuses=())
        validator.set_context(serializer)
        assert validator.instance == order
    def test_validation_fails(self):
        """
        Test that the validation fails if order.status is NOT one of the allowed statuses.
        """
        order = OrderFactory(status=OrderStatus.complete)

        validator = OrderInStatusValidator(allowed_statuses=(
            OrderStatus.draft,
            OrderStatus.cancelled,
        ), )
        validator.set_instance(order)

        with pytest.raises(APIConflictException):
            validator()
Ejemplo n.º 3
0
    def create_from_order(self, order, attrs=None):
        """
        :param order: Order instance for this payment gateway session
        :param attrs: dict with any extra property to be set on the object

        :returns: Payment Gateway Session instance created
        :raises GOVUKPayAPIException: if there is a problem with GOV.UK Pay
        :raises Conflict: if the order is not in the allowed status
        """
        # refresh ongoing sessions for this order first of all
        for session in self.filter(order=order).ongoing().select_for_update():
            session.refresh_from_govuk_payment()

        # validate that the order is in `quote_accepted`
        order.refresh_from_db()
        validator = OrderInStatusValidator(
            allowed_statuses=(OrderStatus.quote_accepted, ), )
        validator.set_instance(order)
        validator()

        # lock order to avoid race conditions
        order.__class__.objects.select_for_update().get(pk=order.pk)

        # cancel other ongoing sessions
        for session in self.filter(order=order).ongoing():
            session.cancel()

        # create a new payment gateway session
        session_id = uuid.uuid4()

        pay = PayClient()
        govuk_payment = pay.create_payment(
            amount=order.total_cost,
            reference=f'{order.reference}-{str(session_id)[:8].upper()}',
            description=settings.GOVUK_PAY_PAYMENT_DESCRIPTION.format(
                reference=order.reference, ),
            return_url=settings.GOVUK_PAY_RETURN_URL.format(
                public_token=order.public_token,
                session_id=session_id,
            ),
        )

        session = self.create(
            id=session_id,
            order=order,
            govuk_payment_id=govuk_payment['payment_id'],
            **(attrs or {}),
        )

        return session
    def test_validation_passes(self):
        """
        Test that the validation passes if order.status is one of the allowed statuses.
        """
        order = OrderFactory(status=OrderStatus.complete)

        validator = OrderInStatusValidator(allowed_statuses=(
            OrderStatus.draft,
            OrderStatus.complete,
            OrderStatus.cancelled,
        ), )
        validator.set_instance(order)

        try:
            validator()
        except Exception:
            pytest.fail('Should not raise a validator error.')
    def test_order_not_required(self):
        """
        Test that if order_required == False and the order passed in is None,
        the validation passes.
        """
        validator = OrderInStatusValidator(
            allowed_statuses=(
                OrderStatus.draft,
                OrderStatus.complete,
                OrderStatus.cancelled,
            ),
            order_required=False,
        )
        validator.set_instance(None)

        try:
            validator()
        except Exception:
            pytest.fail('Should not raise a validator error.')
Ejemplo n.º 6
0
    def test_validation_fails(self, serializer_factory):
        """
        Test that the validation fails if order.status is NOT one of the allowed statuses.
        """
        order = OrderFactory(status=OrderStatus.COMPLETE)

        validator = OrderInStatusValidator(allowed_statuses=(
            OrderStatus.DRAFT,
            OrderStatus.CANCELLED,
        ), )
        serializer = serializer_factory(order)

        with pytest.raises(APIConflictException):
            validator({}, serializer)
Ejemplo n.º 7
0
    def test_validation_passes(self, serializer_factory):
        """
        Test that the validation passes if order.status is one of the allowed statuses.
        """
        order = OrderFactory(status=OrderStatus.COMPLETE)

        validator = OrderInStatusValidator(allowed_statuses=(
            OrderStatus.DRAFT,
            OrderStatus.COMPLETE,
            OrderStatus.CANCELLED,
        ), )
        serializer = serializer_factory(order)

        try:
            validator({}, serializer)
        except Exception:
            pytest.fail('Should not raise a validator error.')
Ejemplo n.º 8
0
class SubscribedAdviserListSerializer(serializers.ListSerializer):
    """DRF List serializer for OrderSubscriber(s)."""

    default_validators = [
        OrderInStatusValidator(allowed_statuses=(
            OrderStatus.draft,
            OrderStatus.quote_awaiting_acceptance,
            OrderStatus.quote_accepted,
            OrderStatus.paid,
        ), ),
    ]

    def save(self, **kwargs):
        """
        Overrides save as the logic is not the standard DRF one.

        1. if a subscriber is still in the list, don't do anything
        2. if a subscriber was not in the list, add it
        3. if a subscriber is not in the list any more, remove it
        """
        assert hasattr(self, '_errors'), (
            'You must call `.is_valid()` before calling `.save()`.')

        assert not self.errors, (
            'You cannot call `.save()` on a serializer with invalid data.')

        order = self.context['order']
        modified_by = self.context['modified_by']

        current_list = set(
            order.subscribers.values_list('adviser_id', flat=True))
        final_list = {data['id'] for data in self.validated_data}

        to_delete = current_list - final_list
        to_add = final_list - current_list

        order.subscribers.filter(adviser__in=to_delete).delete()
        for adviser_id in to_add:
            OrderSubscriber.objects.create(
                order=order,
                adviser_id=adviser_id,
                created_by=modified_by,
                modified_by=modified_by,
            )
Ejemplo n.º 9
0
    def test_order_not_required(self):
        """
        Test that if order_required == False and the order passed in is None,
        the validation passes.
        """
        validator = OrderInStatusValidator(
            allowed_statuses=(
                OrderStatus.draft,
                OrderStatus.complete,
                OrderStatus.cancelled,
            ),
            order_required=False,
        )
        serializer = mock.Mock(instance=None, context={})

        try:
            validator({}, serializer)
        except Exception:
            pytest.fail('Should not raise a validator error.')
Ejemplo n.º 10
0
class OrderAssigneeListSerializer(serializers.ListSerializer):
    """DRF List serializer for OrderAssignee(s)."""

    default_validators = [
        OrderInStatusValidator(allowed_statuses=(
            OrderStatus.draft,
            OrderStatus.quote_awaiting_acceptance,
            OrderStatus.quote_accepted,
            OrderStatus.paid,
        ), ),
    ]

    def validate(self, data):
        """Validate the list of assignees."""
        self.validate_only_one_lead(data)
        self.validate_force_delete(data)
        return data

    def validate_force_delete(self, data):
        """Validate that assignees cannot be deleted if the order.status == paid."""
        order = self.context['order']
        force_delete = self.context['force_delete']

        if order.status != OrderStatus.draft and force_delete:
            raise ValidationError(
                'You cannot delete any assignees at this stage.')
        return data

    def validate_only_one_lead(self, data):
        """
        If the order is in draft, validate that only one assignee can be marked as lead.
        """
        order = self.context['order']
        force_delete = self.context['force_delete']

        if order.status != OrderStatus.draft:
            return

        existing_assignees = dict(
            order.assignees.values_list('adviser_id', 'is_lead'))

        leads = []
        for assignee_data in data:
            adviser_id = assignee_data['adviser'].id

            try:
                is_lead = assignee_data['is_lead']
            except KeyError:
                # in case of PATCH when the field has not been passed in,
                # get it from the db record instead
                is_lead = existing_assignees.get(adviser_id, False)

            if is_lead:
                leads.append(adviser_id)

            existing_assignees.pop(adviser_id, None)

        # in case of PATCH if not force_delete, you don't have to pass all the elements
        # so we need to check the remaining db records.
        if not force_delete:
            leads += [
                adviser_id
                for adviser_id, is_lead in existing_assignees.items()
                if is_lead
            ]

        if len(leads) > 1:
            raise ValidationError('Only one lead allowed.')
        return data

    def save(self, **kwargs):
        """
        Overrides save as the logic is not the standard DRF one.

        1. if an assignee is not in data and force_delete is True then, assignee is deleted
        2. if the assignee is in data, it gets updated
        3. if data has extra assignees, they get created
        """
        assert hasattr(self, '_errors'), (
            'You must call `.is_valid()` before calling `.save()`.')

        assert not self.errors, (
            'You cannot call `.save()` on a serializer with invalid data.')

        order = self.context['order']
        modified_by = self.context['modified_by']
        force_delete = self.context['force_delete']

        validated_data_dict = {
            data['adviser'].id: data
            for data in self.validated_data
        }
        for assignee in order.assignees.all():
            assignee_adviser_id = assignee.adviser.id

            if assignee_adviser_id not in validated_data_dict:
                # DELETE
                if force_delete:
                    self.child.delete(assignee)
            else:
                # UPDATE
                data = validated_data_dict[assignee_adviser_id]
                data = {
                    field_name: field_value
                    for field_name, field_value in data.items()
                    if getattr(assignee, field_name) != field_value
                }
                if data:  # if something has changed, save
                    data = {
                        **data,
                        'order': order,
                        'modified_by': modified_by,
                    }
                    self.child.update(assignee, data)

                del validated_data_dict[assignee_adviser_id]

        # ADD
        for data in validated_data_dict.values():
            data = {
                **data,
                'order': order,
                'created_by': modified_by,
                'modified_by': modified_by,
            }
            self.child.create(data)