Commit ee21bc1e by Vedran Karacic Committed by Vedran Karačić

Add address and products to SDN failure model.

parent 48700115
...@@ -29,6 +29,7 @@ class SDNCheckViewSetTests(TestCase): ...@@ -29,6 +29,7 @@ class SDNCheckViewSetTests(TestCase):
self.PATH, self.PATH,
data=json.dumps({ data=json.dumps({
'name': 'Tester', 'name': 'Tester',
'address': 'Testlandia',
'country': 'TE' 'country': 'TE'
}), }),
content_type=JSON_CONTENT_TYPE content_type=JSON_CONTENT_TYPE
......
"""API endpoint for performing an SDN check on users.""" """API endpoint for performing an SDN check on users."""
from oscar.core.loading import get_model
from requests.exceptions import HTTPError, Timeout from requests.exceptions import HTTPError, Timeout
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
...@@ -6,6 +7,8 @@ from rest_framework.response import Response ...@@ -6,6 +7,8 @@ from rest_framework.response import Response
from ecommerce.extensions.payment.utils import SDNClient from ecommerce.extensions.payment.utils import SDNClient
Basket = get_model('basket', 'Basket')
class SDNCheckViewSet(APIView): class SDNCheckViewSet(APIView):
"""Performs an SDN check for a given user.""" """Performs an SDN check for a given user."""
...@@ -18,10 +21,13 @@ class SDNCheckViewSet(APIView): ...@@ -18,10 +21,13 @@ class SDNCheckViewSet(APIView):
or failed. or failed.
""" """
name = request.data['name'] name = request.data['name']
address = request.data['address']
country = request.data['country'] country = request.data['country']
hits = 0 hits = 0
site_configuration = request.site.siteconfiguration site_configuration = request.site.siteconfiguration
basket = Basket.get_basket(request.user, site_configuration.site)
if site_configuration.enable_sdn_check: if site_configuration.enable_sdn_check:
sdn_check = SDNClient( sdn_check = SDNClient(
api_url=site_configuration.sdn_api_url, api_url=site_configuration.sdn_api_url,
...@@ -29,13 +35,13 @@ class SDNCheckViewSet(APIView): ...@@ -29,13 +35,13 @@ class SDNCheckViewSet(APIView):
sdn_list=site_configuration.sdn_api_list sdn_list=site_configuration.sdn_api_list
) )
try: try:
response = sdn_check.search(name, country) response = sdn_check.search(name, address, country)
hits = response['total'] hits = response['total']
if hits > 0: if hits > 0:
sdn_check.deactivate_user( sdn_check.deactivate_user(
request.user, basket,
request.site.siteconfiguration,
name, name,
address,
country, country,
response response
) )
......
...@@ -30,7 +30,7 @@ class Basket(AbstractBasket): ...@@ -30,7 +30,7 @@ class Basket(AbstractBasket):
If no such basket exists, create a new one. If multiple such baskets exist, If no such basket exists, create a new one. If multiple such baskets exist,
merge them into one. merge them into one.
""" """
editable_baskets = cls.objects.filter(site=site, owner=user, status__in=Basket.editable_statuses) editable_baskets = cls.objects.filter(site=site, owner=user, status__in=cls.editable_statuses)
if len(editable_baskets) == 0: if len(editable_baskets) == 0:
basket = cls.create_basket(site, user) basket = cls.create_basket(site, user)
else: else:
......
# -*- coding: utf-8 -*-
# Generated by Django 1.9.12 on 2017-02-27 14:02
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('catalogue', '0023_auto_20170215_2234'),
('payment', '0015_auto_20170215_2229'),
]
operations = [
migrations.AddField(
model_name='sdncheckfailure',
name='address',
field=models.CharField(default='', max_length=60),
),
migrations.AddField(
model_name='sdncheckfailure',
name='products',
field=models.ManyToManyField(related_name='sdn_failures', to='catalogue.Product'),
),
]
...@@ -53,8 +53,10 @@ class SDNCheckFailure(TimeStampedModel): ...@@ -53,8 +53,10 @@ class SDNCheckFailure(TimeStampedModel):
""" Record of SDN check failure. """ """ Record of SDN check failure. """
full_name = models.CharField(max_length=255) full_name = models.CharField(max_length=255)
username = models.CharField(max_length=255) username = models.CharField(max_length=255)
address = models.CharField(max_length=60, default='')
country = models.CharField(max_length=2) country = models.CharField(max_length=2)
site = models.ForeignKey('sites.Site', verbose_name=_('Site'), null=True, blank=True, on_delete=models.SET_NULL) site = models.ForeignKey('sites.Site', verbose_name=_('Site'), null=True, blank=True, on_delete=models.SET_NULL)
products = models.ManyToManyField('catalogue.Product', related_name='sdn_failures')
sdn_check_response = JSONField() sdn_check_response = JSONField()
def __unicode__(self): def __unicode__(self):
......
...@@ -6,6 +6,7 @@ import mock ...@@ -6,6 +6,7 @@ import mock
import httpretty import httpretty
from django.conf import settings from django.conf import settings
from django.test import override_settings from django.test import override_settings
from oscar.test import factories
from requests.exceptions import HTTPError, Timeout from requests.exceptions import HTTPError, Timeout
from ecommerce.core.models import User from ecommerce.core.models import User
...@@ -42,6 +43,7 @@ class SDNCheckTests(TestCase): ...@@ -42,6 +43,7 @@ class SDNCheckTests(TestCase):
def setUp(self): def setUp(self):
super(SDNCheckTests, self).setUp() super(SDNCheckTests, self).setUp()
self.name = 'Dr. Evil' self.name = 'Dr. Evil'
self.address = 'Top-secret lair'
self.country = 'EL' self.country = 'EL'
self.user = self.create_user(full_name=self.name) self.user = self.create_user(full_name=self.name)
self.site_configuration = self.site.siteconfiguration self.site_configuration = self.site.siteconfiguration
...@@ -64,6 +66,7 @@ class SDNCheckTests(TestCase): ...@@ -64,6 +66,7 @@ class SDNCheckTests(TestCase):
'api_key': self.site_configuration.sdn_api_key, 'api_key': self.site_configuration.sdn_api_key,
'type': 'individual', 'type': 'individual',
'name': self.name, 'name': self.name,
'address': self.address,
'countries': self.country 'countries': self.country
}) })
sdn_check_url = '{api_url}?{params}'.format( sdn_check_url = '{api_url}?{params}'.format(
...@@ -79,15 +82,6 @@ class SDNCheckTests(TestCase): ...@@ -79,15 +82,6 @@ class SDNCheckTests(TestCase):
content_type='application/json' content_type='application/json'
) )
def assert_sdn_check_failure_recorded(self, response):
""" Assert an SDN check failure is logged and has the correct values. """
self.assertEqual(SDNCheckFailure.objects.count(), 1)
sdn_object = SDNCheckFailure.objects.first()
self.assertEqual(sdn_object.full_name, self.name)
self.assertEqual(sdn_object.country, self.country)
self.assertEqual(sdn_object.site, self.site_configuration.site)
self.assertEqual(sdn_object.sdn_check_response, response)
@httpretty.activate @httpretty.activate
@override_settings(SDN_CHECK_REQUEST_TIMEOUT=0.1) @override_settings(SDN_CHECK_REQUEST_TIMEOUT=0.1)
def test_sdn_check_timeout(self): def test_sdn_check_timeout(self):
...@@ -99,7 +93,7 @@ class SDNCheckTests(TestCase): ...@@ -99,7 +93,7 @@ class SDNCheckTests(TestCase):
self.mock_sdn_response(mock_timeout, status_code=200) self.mock_sdn_response(mock_timeout, status_code=200)
with self.assertRaises(Timeout): with self.assertRaises(Timeout):
with mock.patch('ecommerce.extensions.payment.utils.logger.exception') as mock_logger: with mock.patch('ecommerce.extensions.payment.utils.logger.exception') as mock_logger:
self.sdn_validator.search(self.name, self.country) self.sdn_validator.search(self.name, self.address, self.country)
self.assertTrue(mock_logger.called) self.assertTrue(mock_logger.called)
@httpretty.activate @httpretty.activate
...@@ -108,7 +102,7 @@ class SDNCheckTests(TestCase): ...@@ -108,7 +102,7 @@ class SDNCheckTests(TestCase):
self.mock_sdn_response(json.dumps({'total': 1}), status_code=400) self.mock_sdn_response(json.dumps({'total': 1}), status_code=400)
with self.assertRaises(HTTPError): with self.assertRaises(HTTPError):
with mock.patch('ecommerce.extensions.payment.utils.logger.exception') as mock_logger: with mock.patch('ecommerce.extensions.payment.utils.logger.exception') as mock_logger:
self.sdn_validator.search(self.name, self.country) self.sdn_validator.search(self.name, self.address, self.country)
self.assertTrue(mock_logger.called) self.assertTrue(mock_logger.called)
@httpretty.activate @httpretty.activate
...@@ -116,19 +110,35 @@ class SDNCheckTests(TestCase): ...@@ -116,19 +110,35 @@ class SDNCheckTests(TestCase):
""" Verify the SDN check returns the number of matches and records the match. """ """ Verify the SDN check returns the number of matches and records the match. """
sdn_response = {'total': 1} sdn_response = {'total': 1}
self.mock_sdn_response(json.dumps(sdn_response)) self.mock_sdn_response(json.dumps(sdn_response))
response = self.sdn_validator.search(self.name, self.country) response = self.sdn_validator.search(self.name, self.address, self.country)
self.assertEqual(response, sdn_response) self.assertEqual(response, sdn_response)
def test_deactivate_user(self): def test_deactivate_user(self):
""" Verify an SDN failure is logged. """ """ Verify an SDN failure is logged. """
response = {'description': 'Bad dude.'} response = {'description': 'Bad dude.'}
product1 = factories.ProductFactory(stockrecords__partner__short_code='first')
product2 = factories.ProductFactory(stockrecords__partner__short_code='second')
basket = factories.BasketFactory(owner=self.user, site=self.site_configuration.site)
basket.add(product1)
basket.add(product2)
self.assertEqual(SDNCheckFailure.objects.count(), 0) self.assertEqual(SDNCheckFailure.objects.count(), 0)
with mock.patch.object(User, 'deactivate_account') as deactivate_account: with mock.patch.object(User, 'deactivate_account') as deactivate_account:
deactivate_account.return_value = True deactivate_account.return_value = True
self.sdn_validator.deactivate_user( self.sdn_validator.deactivate_user(
self.user, basket,
self.site_configuration,
self.name, self.name,
self.address,
self.country, self.country,
response) response
self.assert_sdn_check_failure_recorded(response) )
self.assertEqual(SDNCheckFailure.objects.count(), 1)
sdn_object = SDNCheckFailure.objects.first()
self.assertEqual(sdn_object.full_name, self.name)
self.assertEqual(sdn_object.address, self.address)
self.assertEqual(sdn_object.country, self.country)
self.assertEqual(sdn_object.site, self.site_configuration.site)
self.assertEqual(sdn_object.sdn_check_response, response)
self.assertEqual(sdn_object.products.count(), basket.lines.count())
self.assertIn(product1, sdn_object.products.all())
self.assertIn(product2, sdn_object.products.all())
...@@ -73,7 +73,7 @@ class SDNClient(object): ...@@ -73,7 +73,7 @@ class SDNClient(object):
self.api_key = api_key self.api_key = api_key
self.sdn_list = sdn_list self.sdn_list = sdn_list
def search(self, name, country): def search(self, name, address, country):
""" """
Searches the OFAC list for an individual with the specified details. Searches the OFAC list for an individual with the specified details.
The check returns zero hits if: The check returns zero hits if:
...@@ -83,6 +83,7 @@ class SDNClient(object): ...@@ -83,6 +83,7 @@ class SDNClient(object):
Args: Args:
name (str): Individual's full name. name (str): Individual's full name.
address (str): Individual's address.
country (str): ISO 3166-1 alpha-2 country code where the individual is from. country (str): ISO 3166-1 alpha-2 country code where the individual is from.
Returns: Returns:
dict: SDN API response. dict: SDN API response.
...@@ -93,6 +94,7 @@ class SDNClient(object): ...@@ -93,6 +94,7 @@ class SDNClient(object):
'api_key': self.api_key, 'api_key': self.api_key,
'type': 'individual', 'type': 'individual',
'name': name, 'name': name,
'address': address,
'countries': country 'countries': country
}) })
sdn_check_url = '{api_url}?{params}'.format( sdn_check_url = '{api_url}?{params}'.format(
...@@ -115,23 +117,28 @@ class SDNClient(object): ...@@ -115,23 +117,28 @@ class SDNClient(object):
return json.loads(response.content) return json.loads(response.content)
def deactivate_user(self, user, site_configuration, name, country, search_results): def deactivate_user(self, basket, name, address, country, search_results):
""" Deactivates a user account. """ Deactivates a user account.
Args: Args:
user (User): User whose account should be deactivated. basket (Basket): The user's basket.
site_configuration (SiteConfiguration): The current site's configuration.
name (str): The user's name. name (str): The user's name.
address (str): The user's address.
country (str): ISO 3166-1 alpha-2 country code where the individual is from. country (str): ISO 3166-1 alpha-2 country code where the individual is from.
search_results (dict): Results from a call to `search` that will search_results (dict): Results from a call to `search` that will
be recorded as the reason for the deactivation. be recorded as the reason for the deactivation.
""" """
SDNCheckFailure.objects.create( site = basket.site
snd_failure = SDNCheckFailure.objects.create(
full_name=name, full_name=name,
username=user.username, username=basket.owner.username,
address=address,
country=country, country=country,
site=site_configuration.site, site=site,
sdn_check_response=search_results sdn_check_response=search_results
) )
logger.warning('SDN check failed for user [%s] on site [%s]', name, site_configuration.site.name) for line in basket.lines.all():
user.deactivate_account(site_configuration) snd_failure.products.add(line.product)
logger.warning('SDN check failed for user [%s] on site [%s]', name, site.name)
basket.owner.deactivate_account(site.siteconfiguration)
...@@ -169,6 +169,8 @@ define([ ...@@ -169,6 +169,8 @@ define([
function sdnCheck(event) { function sdnCheck(event) {
var first_name = $('input[name=first_name]').val(), var first_name = $('input[name=first_name]').val(),
last_name = $('input[name=last_name]').val(), last_name = $('input[name=last_name]').val(),
address = $('input[name=address_line1]').val(),
city = $('input[name=city]').val(),
country = $('select[name=country]').val(); country = $('select[name=country]').val();
$.ajax({ $.ajax({
...@@ -181,6 +183,7 @@ define([ ...@@ -181,6 +183,7 @@ define([
}, },
data: JSON.stringify({ data: JSON.stringify({
'name': _s.sprintf('%s %s', first_name, last_name), 'name': _s.sprintf('%s %s', first_name, last_name),
'address': _s.sprintf('%s, %s', address, city),
'country': country 'country': country
}), }),
async: false, async: false,
......
define([ define([
'jquery', 'jquery',
'underscore', 'underscore',
'underscore.string',
'pages/basket_page', 'pages/basket_page',
'utils/utils', 'utils/utils',
'utils/analytics_utils', 'utils/analytics_utils',
...@@ -12,6 +13,7 @@ define([ ...@@ -12,6 +13,7 @@ define([
], ],
function ($, function ($,
_, _,
_s,
BasketPage, BasketPage,
Utils, Utils,
AnalyticsUtils, AnalyticsUtils,
...@@ -271,6 +273,8 @@ define([ ...@@ -271,6 +273,8 @@ define([
it('should perform the SDN check', function () { it('should perform the SDN check', function () {
var first_name = 'Darth', var first_name = 'Darth',
last_name = 'Vader', last_name = 'Vader',
address = 'Deck 1',
city = 'Death Star',
country = 'DS', country = 'DS',
args, args,
ajaxData, ajaxData,
...@@ -279,6 +283,8 @@ define([ ...@@ -279,6 +283,8 @@ define([
$('input[name=first_name]').val(first_name); $('input[name=first_name]').val(first_name);
$('input[name=last_name]').val(last_name); $('input[name=last_name]').val(last_name);
$('input[name=address_line1]').val(address);
$('input[name=city]').val(city);
$('select[name=country]').val(country); $('select[name=country]').val(country);
$('input[name=sdn-check]').val('enabled'); $('input[name=sdn-check]').val('enabled');
...@@ -297,7 +303,8 @@ define([ ...@@ -297,7 +303,8 @@ define([
expect(args.method).toEqual('POST'); expect(args.method).toEqual('POST');
expect(args.url).toEqual('/api/v2/sdn/search/'); expect(args.url).toEqual('/api/v2/sdn/search/');
expect(args.contentType).toEqual('application/json; charset=utf-8'); expect(args.contentType).toEqual('application/json; charset=utf-8');
expect(ajaxData.name).toEqual('Darth Vader'); expect(ajaxData.name).toEqual(_s.sprintf('%s %s', first_name, last_name));
expect(ajaxData.address).toEqual(_s.sprintf('%s, %s', address, city));
expect(ajaxData.country).toEqual(country); expect(ajaxData.country).toEqual(country);
}); });
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment