diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..941226f4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +Dockerfile* +docker-compose* +.dockerignore +.git +.gitignore diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..ae210603 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM amazonlinux:2017.09.0.20170930 + +RUN yum -y install vim httpd python36 python36-devel python36-pip wget gcc gcc-c++ subversion make uuid libuuid-devel httpd-devel mysql-devel +# set python 3.6 as default version +RUN alternatives --install /usr/bin/python python /usr/bin/python2.7 1 && alternatives --install /usr/bin/python python /usr/bin/python3.6 2 && alternatives --set python /usr/bin/python3.6 +RUN wget https://github.com/GrahamDumpleton/mod_wsgi/archive/4.5.15.tar.gz && tar -xvf 4.5.15.tar.gz && cd mod_wsgi-4.5.15 && ./configure --with-python=/usr/bin/python36 && make; make install && cd .. && rm -r mod_wsgi-4.5.15 4.5.15.tar.gz +# configure settings.py as part of build process +COPY docker_config/vhosts.conf /etc/httpd/conf.d/ +COPY docker_config/wsgi.conf /etc/httpd/conf.d/ +COPY dependencies.list /etc/ +RUN easy_install-3.6 pip && pip3 install --upgrade pip +RUN pip3 install -r /etc/dependencies.list && rm /etc/dependencies.list +# update this to pipe to AWS log later +RUN mkdir /var/log/api && chown -R apache:apache /var/log/api +RUN echo "ServerName 172.*" >> /etc/httpd/conf/httpd.conf +EXPOSE 80 +CMD ["/usr/sbin/httpd","-D","FOREGROUND"] \ No newline at end of file diff --git a/apikey/migrations/0001_initial.py b/apikey/migrations/0001_initial.py index 796ec7ab..ffbdfdfc 100644 --- a/apikey/migrations/0001_initial.py +++ b/apikey/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/apikey/pyTests.py b/apikey/pyTests.py deleted file mode 100644 index ad43442d..00000000 --- a/apikey/pyTests.py +++ /dev/null @@ -1,39 +0,0 @@ -#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. - -import django -import unittest -import sys, getopt -from unittest import TestCase -import requests -import json -from models import ApiKey -from testSamples import ApiKeySample -from common.pyTests import PyTestGenerics, GenericCRUDTest - -# Create your tests here. -django.setup() -serverUrl = PyTestGenerics.initPyTest() -print "using server url %s" % serverUrl - -# ---------------------- UNIT TEST FOR BASIC CRUD OPERATIONS ------------- - -class ApiKeyCRUD(GenericCRUDTest, TestCase): - sample = ApiKeySample(serverUrl) - - def setUp(self): -# super(ApiKeyCRUD, self).setUp() - ApiKey.objects.filter(apiKey=self.sample.data['apiKey']).delete() - ApiKey.objects.filter(apiKey=self.sample.updateData['apiKey']).delete() - - # overrides parent class teardown. - def tearDown(self): - pass -# ----------------- END OF BASIC CRUD OPERATIONS ---------------------- - -print "Running unit tests on party web services API........." - -if __name__ == '__main__': - sys.argv[1:] = [] - unittest.main() - ret = not runner.run(suite).wasSuccessful() - sys.exit(ret) diff --git a/apikey/serializers.py b/apikey/serializers.py index 57d20a9a..1f286413 100644 --- a/apikey/serializers.py +++ b/apikey/serializers.py @@ -1,6 +1,6 @@ #Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. -from models import ApiKey +from .models import ApiKey from rest_framework import serializers class ApiKeySerializer(serializers.ModelSerializer): diff --git a/apikey/testSamples.py b/apikey/testSamples.py index 14270b0b..5071cf86 100644 --- a/apikey/testSamples.py +++ b/apikey/testSamples.py @@ -1,12 +1,10 @@ import django -import unittest -import sys, getopt -from unittest import TestCase -from models import ApiKey +import sys +from django.test import TestCase +from .models import ApiKey +from common.tests import TestGenericInterfaces -from common.pyTests import PyTestGenerics - -genericForcePost = PyTestGenerics.forcePost +genericForcePost = TestGenericInterfaces.forcePost class ApiKeySample(): path = 'apikeys/' @@ -15,13 +13,13 @@ class ApiKeySample(): 'apiKey':'proxyKey', } updateData = { - 'apiKey':'proxy2Key', + 'apiKey':'proxyKey2', } pkName = 'apiKeyId' model = ApiKey def __init__(self, serverUrl): - self.url = serverUrl+self.path + self.url = serverUrl + self.path - def forcePost(self,data): + def forcePost(self, data): return genericForcePost(self.model, self.pkName, data) diff --git a/apikey/tests.py b/apikey/tests.py index 7ce503c2..b2b48524 100644 --- a/apikey/tests.py +++ b/apikey/tests.py @@ -1,3 +1,35 @@ +#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. + +import django +import unittest +import sys +import requests +import json from django.test import TestCase +from .models import ApiKey +from .testSamples import ApiKeySample +from common.tests import TestGenericInterfaces, GenericCRUDTest +from http.cookies import SimpleCookie + +# Create your tests here. +django.setup() +serverUrl = TestGenericInterfaces.getHost() + +# ---------------------- UNIT TEST FOR BASIC CRUD OPERATIONS ------------- + +class ApiKeyCRUD(GenericCRUDTest, TestCase): + sample = ApiKeySample(serverUrl) + + def test_for_get_all(self): + url = self.getUrl(self.sample.url) + self.getAllHelper(url, 'apiKey', self.apiKey) + +# ----------------- END OF BASIC CRUD OPERATIONS ---------------------- + +print("Running unit tests on API key web services API.........") -# Create your tests here. +if __name__ == '__main__': + sys.argv[1:] = [] + unittest.main() + ret = not runner.run(suite).wasSuccessful() + sys.exit(ret) diff --git a/apikey/views.py b/apikey/views.py index dbb5458d..48185ff8 100644 --- a/apikey/views.py +++ b/apikey/views.py @@ -3,8 +3,8 @@ from django.http import HttpResponse from rest_framework import generics -from models import ApiKey -from serializers import ApiKeySerializer +from .models import ApiKey +from .serializers import ApiKeySerializer import json diff --git a/authentication/manualTests.py b/authentication/manualTests.py new file mode 100644 index 00000000..b4fd0935 --- /dev/null +++ b/authentication/manualTests.py @@ -0,0 +1,38 @@ +#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. + +import django +import unittest +import sys +import json +from django.test import TestCase, Client +from partner.testSamples import PartnerSample +from party.testSamples import UserPartySample +from common.tests import TestGenericInterfaces, GenericTest +from .testSamples import CredentialSample +from .tests import CredentialGenericTest +from http.cookies import SimpleCookie +from django.test.utils import override_settings + +# Create your tests here. +django.setup() +serverUrl = TestGenericInterfaces.getHost() + +# test for API endpoint /credentials/resetPwd/ +@override_settings(EMAIL_BACKEND='django.core.mail.backends.smtp.EmailBackend') +class ResetPasswordTest(CredentialGenericTest): + + def test_for_reset_password(self): + url = '%scredentials/resetPwd/?user=%s&partnerId=%s' % (serverUrl, self.sample.getUsername(), self.partnerId) + + # the default content type for put is 'application/octet-stream' + # does not test for partyId update + res = self.client.put(url, None, content_type='application/json') + self.assertEqual(res.status_code, 200) + +print("Running unit tests on authentication/credential reset password web services API.........") + +if __name__ == '__main__': + sys.argv[1:] = [] + unittest.main() + ret = not runner.run(suite).wasSuccessful() + sys.exit(ret) \ No newline at end of file diff --git a/authentication/migrations/0001_initial.py b/authentication/migrations/0001_initial.py index 392a8553..c6cb06df 100644 --- a/authentication/migrations/0001_initial.py +++ b/authentication/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -16,7 +16,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('gmail', models.CharField(max_length=128, db_index=True)), - ('partyId', models.ForeignKey(to='party.Party')), + ('partyId', models.ForeignKey(to='party.Party', on_delete=models.PROTECT)), ], options={ 'db_table': 'GoogleEmail', @@ -30,7 +30,7 @@ class Migration(migrations.Migration): ('password', models.CharField(max_length=32)), ('email', models.CharField(max_length=128, null=True)), ('institution', models.CharField(max_length=64, null=True)), - ('partyId', models.ForeignKey(to='party.Party', db_column=b'partyId')), + ('partyId', models.ForeignKey(to='party.Party', db_column='partyId', on_delete=models.PROTECT)), ], options={ 'db_table': 'User', diff --git a/authentication/migrations/0002_auto_20150806_1926.py b/authentication/migrations/0002_auto_20150806_1926.py index 31b10958..a6d6a613 100644 --- a/authentication/migrations/0002_auto_20150806_1926.py +++ b/authentication/migrations/0002_auto_20150806_1926.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -15,7 +15,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='user', name='partnerId', - field=models.ForeignKey(db_column=b'partnerId', default='test', to='partner.Partner'), + field=models.ForeignKey(db_column='partnerId', default='test', to='partner.Partner', on_delete=models.PROTECT), preserve_default=False, ), migrations.AddField( diff --git a/authentication/migrations/0003_auto_20150821_2103.py b/authentication/migrations/0003_auto_20150821_2103.py index 41f8deee..a5f591a2 100644 --- a/authentication/migrations/0003_auto_20150821_2103.py +++ b/authentication/migrations/0003_auto_20150821_2103.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/authentication/migrations/0004_credential_name.py b/authentication/migrations/0004_credential_name.py index ab582d77..52f65532 100644 --- a/authentication/migrations/0004_credential_name.py +++ b/authentication/migrations/0004_credential_name.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/authentication/migrations/0005_Credentials_institution_length_64_200.py b/authentication/migrations/0005_Credentials_institution_length_64_200.py index b1d00662..ea783fab 100644 --- a/authentication/migrations/0005_Credentials_institution_length_64_200.py +++ b/authentication/migrations/0005_Credentials_institution_length_64_200.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/authentication/migrations/0006_Credential_tbl_drop_name_column_PW-161.py b/authentication/migrations/0006_Credential_tbl_drop_name_column_PW-161.py index 82509958..9676dfad 100644 --- a/authentication/migrations/0006_Credential_tbl_drop_name_column_PW-161.py +++ b/authentication/migrations/0006_Credential_tbl_drop_name_column_PW-161.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/authentication/migrations/0007_lname_and_fname_in_credential.py b/authentication/migrations/0007_lname_and_fname_in_credential.py index 89646847..06d88dc4 100644 --- a/authentication/migrations/0007_lname_and_fname_in_credential.py +++ b/authentication/migrations/0007_lname_and_fname_in_credential.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/authentication/migrations/0008_auto_20160820_1956.py b/authentication/migrations/0008_auto_20160820_1956.py index 7bb45cc9..1adc9813 100644 --- a/authentication/migrations/0008_auto_20160820_1956.py +++ b/authentication/migrations/0008_auto_20160820_1956.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/authentication/models.py b/authentication/models.py index 848f8dbc..c7a457bc 100644 --- a/authentication/models.py +++ b/authentication/models.py @@ -14,35 +14,44 @@ class Credential(models.Model): password = models.CharField(max_length=64) email = models.CharField(max_length=254, null=True) institution = models.CharField(max_length=200, null=True)#PW-254 - partyId = models.ForeignKey(Party, db_column='partyId') - partnerId = models.ForeignKey(Partner, db_column='partnerId') + partyId = models.ForeignKey(Party, db_column='partyId', on_delete=models.PROTECT) + partnerId = models.ForeignKey(Partner, db_column='partnerId', on_delete=models.PROTECT) userIdentifier = models.CharField(max_length=32, null=True) #name = models.CharField(max_length=64, null=True) vet PW-161 - + @staticmethod def validate(partyId, secretKey): if partyId and secretKey and partyId.isdigit() and Party.objects.filter(partyId=partyId).exists(): pu = Party.objects.filter(partyId=partyId) if Credential.objects.filter(partyId_id__in=pu.values('partyId')).exists(): usu = Credential.objects.filter(partyId_id__in=pu.values('partyId')).first() - digested = base64.b64encode(hmac.new(str(partyId).encode('ascii'), usu.password.encode('ascii'), hashlib.sha1).digest()) + digested = Credential.generateSecretKey(partyId, usu.password) if digested == secretKey: return True #TODO: validation still fail pu = pu.first().consortiums.all() if Credential.objects.filter(partyId_id__in=pu.values('partyId')).exists(): for usu in Credential.objects.filter(partyId_id__in=pu.values('partyId')): - digested = base64.b64encode(hmac.new(str(usu.partyId).encode('ascii'), usu.password.encode('ascii'), hashlib.sha1).digest()) + digested = Credential.generateSecretKey(usu.partyId, usu.password) if digested == secretKey: return True return False + @staticmethod + def generatePasswordHash(password): + return hashlib.sha1(password.encode()).hexdigest() + + @staticmethod + def generateSecretKey(partyId, password): + encoded = base64.b64encode(hmac.new(str(partyId).encode('ascii'), password.encode('ascii'), hashlib.sha1).digest()) + return encoded.decode() + class Meta: db_table = "Credential" unique_together = ("username","partnerId") class GooglePartyAffiliation(models.Model): gmail = models.CharField(max_length=128, db_index=True) - partyId = models.ForeignKey(Party) + partyId = models.ForeignKey(Party, on_delete=models.PROTECT) class Meta: db_table = "GoogleEmail" diff --git a/authentication/testSamples.py b/authentication/testSamples.py new file mode 100644 index 00000000..c85eeecd --- /dev/null +++ b/authentication/testSamples.py @@ -0,0 +1,128 @@ +import django +import hashlib +import copy +from authentication.models import Credential +from partner.models import Partner +from party.models import Party +from common.tests import TestGenericInterfaces + +genericForcePost = TestGenericInterfaces.forcePost + +class CredentialSample(): + path = 'credentials/' + loginPath = 'credentials/login/' + serverUrl = None + url = None + USERNAME = 'test_user' + FIRSTNAME = 'Phoenix' + LASTNAME = 'Bioinformatics' + PASSWORD = 'phoenix123' + PASSWORD_UPDATE = 'phoenix456' + EMAIL = 'techteam@arabidopsis.org' + EMAIL_UPDATE = 'techteam@phoenixbioinformatics.org' + INSTITUTION = 'Phoenix Bioinformatics' + USER_IDENTIFIER = 123 + USER_IDENTIFIER_UPDATE = 124 + data = { + 'username': USERNAME, + 'firstName': FIRSTNAME, + 'lastName': LASTNAME, + 'password': PASSWORD, + 'email': EMAIL, + 'institution': INSTITUTION, + 'partyId': None, + 'partnerId': None, + 'userIdentifier': USER_IDENTIFIER + } + updateData = { + 'username': USERNAME + '_update', + 'firstName': FIRSTNAME + '_update', + 'lastName': LASTNAME + '_update', + 'password': PASSWORD_UPDATE, + 'email': EMAIL_UPDATE, + 'institution': INSTITUTION + ' Update', + 'partyId': None, + 'partnerId': None, + 'userIdentifier': USER_IDENTIFIER_UPDATE + } + updateData_no_pwd = { + 'username': USERNAME + '_update', + 'firstName': FIRSTNAME + '_update', + 'lastName': LASTNAME + '_update', + 'email': EMAIL_UPDATE, + 'institution': INSTITUTION + ' Update', + 'partyId': None, + 'partnerId': None, + 'userIdentifier': USER_IDENTIFIER_UPDATE + } + updateData_invalid = { + 'username': USERNAME + '_update', + 'firstName': True, + 'lastName': LASTNAME + '_update', + 'password': PASSWORD_UPDATE, + 'email': EMAIL_UPDATE, + 'institution': INSTITUTION + ' Update', + 'partyId': None, + 'partnerId': None, + 'userIdentifier': USER_IDENTIFIER_UPDATE + } + pkName = 'id' + model = Credential + + def __init__(self, serverUrl): + self.serverUrl = serverUrl + self.url = serverUrl + self.path + self.data = copy.deepcopy(self.data) + + def setAsUpdateExample(self): + for key in self.data: + if self.updateData[key]: + self.data[key] = self.updateData[key] + + def setPartnerId(self, partnerId): + self.data['partnerId'] = partnerId + + def setPartyId(self, partyId): + self.data['partyId'] = partyId + + def getUserIdentifier(self): + return self.data['userIdentifier'] + + def getUsername(self): + return self.data['username'] + + def getEmail(self): + return self.data['email'] + + def getPartyId(self): + return self.data['partyId'] + + # data to submit to POST API to create credential without party + def getDataForCreate(self): + dataForCreate = copy.deepcopy(self.data) + del dataForCreate['partyId'] + dataForCreate['name'] = '%s %s' % (self.data['firstName'], self.data['lastName']) + return dataForCreate + + def forcePost(self,data): + postData = copy.deepcopy(data) + postData['partyId'] = Party.objects.get(partyId=self.data['partyId']) + postData['partnerId'] = Partner.objects.get(partnerId=self.data['partnerId']) + postData['password'] = self.hashPassword(self.data['password']) + return genericForcePost(self.model, self.pkName, postData) + + def hashPassword(self, password): + return Credential.generatePasswordHash(password) + + def getLoginUrl(self): + return self.serverUrl + self.loginPath + '?partnerId=%s' % self.data['partnerId'] + + def getLoginData(self): + return { + 'user': self.data['username'], + 'password': self.data['password'] + } + + def getSecretKey(self): + # this has dependency on authentication.views regarding argument + return Credential.generateSecretKey(self.data['partyId'], self.hashPassword(self.data['password'])) diff --git a/authentication/tests.py b/authentication/tests.py index 7ce503c2..a269ddb2 100644 --- a/authentication/tests.py +++ b/authentication/tests.py @@ -1,3 +1,233 @@ -from django.test import TestCase +#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. -# Create your tests here. +import django +import unittest +import sys +import json +import copy +import urllib.request, urllib.parse, urllib.error +from django.test import TestCase, Client +from partner.testSamples import PartnerSample +from party.testSamples import UserPartySample +from common.tests import TestGenericInterfaces, GenericCRUDTest, GenericTest, ManualTest, checkMatch +from .testSamples import CredentialSample +from http.cookies import SimpleCookie + +# Create your tests here. +django.setup() +serverUrl = TestGenericInterfaces.getHost() + +# test for API endpoint /credentials/ and /credentials/profile/ +# does not extend GenericCRUDTest class since all methods need override +class CredentialCRUDTest(GenericTest, TestCase): + sample = CredentialSample(serverUrl) + partnerId = None + + def setUp(self): + super(CredentialCRUDTest,self).setUp() + + partnerSample = PartnerSample(serverUrl) + self.partnerId = partnerSample.forcePost(partnerSample.data) + self.sample.data['partnerId']=self.sample.updateData['partnerId']=self.partnerId + + userPartySample = UserPartySample(serverUrl) + partyId = userPartySample.forcePost(userPartySample.data) + self.sample.data['partyId']=self.sample.updateData['partyId']=partyId + + def test_for_create_with_party_id(self): + sample = self.sample + url = sample.url + if self.apiKey: + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + + res = self.client.post(url, sample.data) + + self.assertEqual(res.status_code, 201) + # note the returned credential object do not contain credential table primary key "id" + # but always contains party id instead + self.assertIsNotNone(TestGenericInterfaces.forceGet(self.sample.model,'partyId',sample.getPartyId())) + + def test_for_create_without_party_id(self): + sample = self.sample + url = sample.url + if self.apiKey: + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + + res = self.client.post(url, sample.getDataForCreate()) + + self.assertEqual(res.status_code, 201) + # note the returned credential object do not contain credential table primary key "id" + # but always contains party id instead + self.assertIsNotNone(TestGenericInterfaces.forceGet(self.sample.model,'partyId',json.loads(res.content)['partyId'])) + + def test_for_get(self): + sample = self.sample + pk = sample.forcePost(sample.data) + partnerId = self.partnerId + + # test get by user identifier + queryByUserIdentifier = 'userIdentifier=%s&partnerId=%s' % (sample.getUserIdentifier(), partnerId) + self.assertGetRequestByQueryParam(queryByUserIdentifier) + + # test get by username + queryByUsername = 'username=%s&partnerId=%s' % (sample.getUsername(), partnerId) + self.assertGetRequestByQueryParam(queryByUsername) + + # test get by partyId + queryByPartyId = 'partyId=%s' % sample.getPartyId() + self.assertGetRequestByQueryParam(queryByPartyId) + + def assertGetRequestByQueryParam(self, queryParam): + url = '%s?%s' % (self.sample.url, queryParam) + res = self.client.get(url) + self.assertEqual(res.status_code, 200) + resObj = json.loads(res.content) + # remove password from comparison since it is not returned in GET API + data = copy.deepcopy(self.sample.data) + del data['password'] + # note the returned credential object do not contain credential table primary key "id" + # but always contains party id instead + self.assertEqual(checkMatch(data, resObj, 'partyId', self.sample.getPartyId()), True) + + def test_for_update_by_party_id(self): + queryParam = 'partyId=%s' % self.sample.getPartyId() + self.runUpdateTestByQueryParam(queryParam) + + def test_for_update_by_user_identifier(self): + queryParam = 'userIdentifier=%s&partnerId=%s' % (self.sample.getUserIdentifier(), self.partnerId) + self.runUpdateTestByQueryParam(queryParam) + + def test_for_update_by_username(self): + queryParam = 'username=%s&partnerId=%s' % (self.sample.getUsername(), self.partnerId) + self.runUpdateTestByQueryParam(queryParam) + + def runUpdateTestByQueryParam(self, queryParam): + sample = self.sample + sample.forcePost(sample.data) + partnerId = self.partnerId + loginCredential = self.getUserLoginCredential(); + + url = '%s?%s&%s' % (sample.url, loginCredential, queryParam) + + # the default content type for put is 'application/octet-stream' + # does not test for partyId update + res = self.client.put(url, json.dumps(sample.updateData), content_type='application/json') + self.assertEqual(res.status_code, 200) + resObj = json.loads(res.content) + + # hash password for comparison + updateData = {} + for key in sample.updateData: + if key == 'password': + updateData[key] = sample.hashPassword(sample.updateData[key]) + else: + updateData[key] = sample.updateData[key] + # manipulate sample data to match the test condition + self.assertEqual(checkMatch(updateData, resObj, 'partyId', sample.getPartyId()), True) + + # test for API endpoint /credentials/profile/ + # this is smiliar to the UPDATE methods above except that it only accepts + # pratyId as query param and returns status code 201 when succeed + def test_for_update_profile(self): + sample = self.sample + sample.forcePost(sample.data) + partnerId = self.partnerId + loginCredential = self.getUserLoginCredential(); + + url = '%scredentials/profile/?%s&partyId=%s' % (serverUrl, loginCredential, sample.getPartyId()) + # the default content type for put is 'application/octet-stream' + # does not test for partyId update + res = self.client.put(url, json.dumps(sample.updateData), content_type='application/json') + # API should return 200 for an update but returns 201 + self.assertEqual(res.status_code, 201) + resObj = json.loads(res.content) + + # hash password for comparison + updateData = {} + for key in sample.updateData: + if key == 'password': + updateData[key] = sample.hashPassword(sample.updateData[key]) + else: + updateData[key] = sample.updateData[key] + # manipulate sample data to match the test condition + self.assertEqual(checkMatch(updateData, resObj, 'partyId', sample.getPartyId()), True) + + def getUserLoginCredential(self): + sample = self.sample + secretKey = urllib.parse.quote(sample.getSecretKey()) + return 'credentialId=%s&secretKey=%s' % (sample.getPartyId(), secretKey) + +class CredentialGenericTest(TestCase): + sample = CredentialSample(serverUrl) + partnerId = None + + def setUp(self): + super(CredentialGenericTest,self).setUp() + + partnerSample = PartnerSample(serverUrl) + self.partnerId = partnerSample.forcePost(partnerSample.data) + self.sample.data['partnerId']=self.partnerId + + userPartySample = UserPartySample(serverUrl) + partyId = userPartySample.forcePost(userPartySample.data) + self.sample.data['partyId']=partyId + + self.sample.forcePost(self.sample.data) + +# test for API endpoint /crendetials/login/ +class CredentialLoginTest(CredentialGenericTest): + + def test_for_login(self): + loginUrl = self.sample.getLoginUrl() + loginData = self.sample.getLoginData() + res = self.client.post(loginUrl, loginData) + self.assertEqual(res.status_code, 200) + resObj = json.loads(res.content) + self.assertIsNotNone(resObj['credentialId']) + self.assertIsNotNone(resObj['secretKey']) + +# test for API endpoint /credentials/getUsernames/ +class GetUsernamesTest(CredentialGenericTest): + + def test_for_get_usernames(self): + url = '%scredentials/getUsernames/?email=%s&partnerId=%s' % (serverUrl, self.sample.getEmail(), self.partnerId) + res = self.client.get(url) + self.assertEqual(res.status_code, 200) + resObj = json.loads(res.content) + # remove password from comparison since it is not returned + data = copy.deepcopy(self.sample.data) + del data['password'] + # note the returned credential object do not contain credential table primary key "id" + # but always contains party id instead + self.assertEqual(checkMatch(data, resObj, 'partyId', self.sample.getPartyId()), True) + +# test for API endpoint /credentials/checkAccountExists/ +class CheckAccountExistsTest(CredentialGenericTest): + + def test_for_check_account_exists(self): + self.assertGetRequestByType('email', self.sample.getEmail()) + self.assertGetRequestByType('username', self.sample.getUsername()) + + def assertGetRequestByType(self, keyName, value): + url = '%scredentials/checkAccountExists?%s=%s&partnerId=%s' % (serverUrl, keyName, value, self.partnerId) + checkKey = '%sExist' % keyName + + res = self.client.get(url) + self.assertEqual(res.status_code, 200) + resObj = json.loads(res.content) + # remove password from comparison since it is not returned + self.assertEqual(resObj[checkKey], True) + +# test for API endpoint /credentials/resetPwd/ +# test in manualTests.py +class ResetPasswordTest(ManualTest, TestCase): + path = "/credentials/resetPwd/" + testMethodStr = "running ./manage.py test authentication.manualTests" + +print("Running unit tests on authentication/credential web services API.........") + +if __name__ == '__main__': + sys.argv[1:] = [] + unittest.main() + ret = not runner.run(suite).wasSuccessful() + sys.exit(ret) diff --git a/authentication/urls.py b/authentication/urls.py index 2070fb5f..851ef6ac 100644 --- a/authentication/urls.py +++ b/authentication/urls.py @@ -4,6 +4,7 @@ from authentication import views from rest_framework.urlpatterns import format_suffix_patterns +app_name = 'authentication' urlpatterns = [ url(r'^$', views.listcreateuser.as_view(), name='listcreateuser'), url(r'^login/$', views.login, name='login'), diff --git a/authentication/views.py b/authentication/views.py index 54d78a5a..9a816bad 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -1,5 +1,4 @@ from django.shortcuts import render, redirect -from django.core.urlresolvers import reverse from django.http import HttpResponse from django.core.mail import send_mail @@ -80,7 +79,7 @@ def post(self, request, format=None): if ApiKeyPermission.has_permission(request, self): serializer_class = self.get_serializer_class() data = request.data.copy() # PW-660 - data['password'] = hashlib.sha1(data['password']).hexdigest() + data['password'] = Credential.generatePasswordHash(data['password']) if 'partyId' in data: partyId = data['partyId'] if Credential.objects.all().filter(partyId=partyId).exists(): @@ -108,7 +107,7 @@ def post(self, request, format=None): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - + def put(self, request, format=None): # TODO: security risk here, get username based on the partyId verified in isPhoenix -SC if not isPhoenix(self.request): @@ -125,7 +124,7 @@ def put(self, request, format=None): #http://stackoverflow.com/questions/18930234/django-modifying-the-request-object PW-123 data = request.data.copy() # PW-123 if 'password' in data: - data['password'] = hashlib.sha1(data['password']).hexdigest() + data['password'] = Credential.generatePasswordHash(data['password']) serializer = serializer_class(obj, data=data, partial=True) if serializer.is_valid(): serializer.save() @@ -140,8 +139,8 @@ def put(self, request, format=None): if partySerializer.is_valid(): partySerializer.save() if 'password' in data: - #data['password'] = generateSecretKey(str(obj.partyId.partyId), data['password'])#PW-254 and YM: TAIR-2493 - data['loginKey'] = generateSecretKey(str(obj.partyId.partyId), data['password']) + #data['password'] = Credential.generateSecretKey(str(obj.partyId.partyId), data['password'])#PW-254 and YM: TAIR-2493 + data['loginKey'] = Credential.generateSecretKey(str(obj.partyId.partyId), data['password']) return Response(data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -171,13 +170,13 @@ def login(request): # msg = "Incorrect password" # logger.info("%s, %s: %s %s %s" % (ip, msg, request.POST['user'], request.POST['password'], request.GET['partnerId'])) # return HttpResponse(json.dumps({"message":msg}), status=401) - + # msg = "No such user" # logger.info("%s, %s: %s %s %s" % (ip, msg, request.POST['user'], request.POST['password'], request.GET['partnerId'])) # return HttpResponse(json.dumps({"message":msg}), status=401) requestPassword = request.POST.get('password') - requestHashedPassword = hashlib.sha1(request.POST.get('password')).hexdigest() + requestHashedPassword = Credential.generatePasswordHash(request.POST.get('password')) requestUser = request.POST.get('user') # iexact does not work unfortunately. Steve to find out why @@ -213,7 +212,7 @@ def login(request): response = HttpResponse(json.dumps({ "message": "Correct password", "credentialId": dbUser.partyId.partyId, - "secretKey": generateSecretKey(str(dbUser.partyId.partyId), dbUser.password), + "secretKey": Credential.generateSecretKey(str(dbUser.partyId.partyId), dbUser.password), "email": dbUser.email, "role":"librarian", "username": dbUser.username, @@ -244,9 +243,9 @@ def resetPwd(request): if user: user = user.first() password = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(8)) - user.password=hashlib.sha1(password).hexdigest() + user.password = Credential.generatePasswordHash(password) user.save() - + subject = "Temporary password for %s (%s)" % (user.username, user.email)#PW-215 unlikely ''' message = "username: %s (%s)\n\nYour temp password is %s \n\n" \ @@ -254,12 +253,12 @@ def resetPwd(request): % (user.username, user.email, password)#PW-215 ''' message = partnerObj.resetPasswordEmailBody % (user.username, user.email, password) - + from_email = "info@phoenixbioinformatics.org" - + recipient_list = [user.email] send_mail(subject=subject, message=message, from_email=from_email, recipient_list=recipient_list) - + return HttpResponse(json.dumps({'reset pwd':'success', 'username':user.username, 'useremail':user.email, 'temppwd':user.password}), content_type="application/json")#PW-215 unlikely return HttpResponse(json.dumps({"reset pwd failed":"No such user"}), status=401) #/credentials/register/ @@ -268,9 +267,6 @@ def registerUser(request): context = {'partnerId': request.GET.get('partnerId', "")} return render(request, "authentication/register.html", context) -def generateSecretKey(partyId, password): - return base64.b64encode(hmac.new(str(partyId).encode('ascii'), password.encode('ascii'), hashlib.sha1).digest()) - #/credentials/profile/ class profile(GenericCRUDView): queryset = Credential.objects.all() @@ -289,7 +285,7 @@ def put(self, request, format=None): #http://stackoverflow.com/questions/18930234/django-modifying-the-request-object PW-123 data = request.data.copy() # PW-123 if 'password' in data: - data['password'] = hashlib.sha1(data['password']).hexdigest() + data['password'] = Credential.generatePasswordHash(data['password']) serializer = serializer_class(obj, data=data, partial=True) if serializer.is_valid(): serializer.save() diff --git a/authorization/controls.py b/authorization/controls.py index c94c520b..c6d3b2ac 100644 --- a/authorization/controls.py +++ b/authorization/controls.py @@ -1,6 +1,6 @@ #import Path, AccessType -from models import Status, AccessType +from .models import Status, AccessType from subscription.models import Subscription from authentication.models import Credential diff --git a/authorization/migrations/0001_initial.py b/authorization/migrations/0001_initial.py index 6fd60bb0..2a401d1a 100644 --- a/authorization/migrations/0001_initial.py +++ b/authorization/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -34,7 +34,7 @@ class Migration(migrations.Migration): name='UriPattern', fields=[ ('patternId', models.AutoField(serialize=False, primary_key=True)), - ('pattern', models.CharField(default=b'', max_length=200)), + ('pattern', models.CharField(default='', max_length=200)), ], options={ 'db_table': 'UriPattern', @@ -43,16 +43,16 @@ class Migration(migrations.Migration): migrations.AddField( model_name='accessrule', name='accessTypeId', - field=models.ForeignKey(to='authorization.AccessType'), + field=models.ForeignKey(to='authorization.AccessType', on_delete=models.PROTECT), ), migrations.AddField( model_name='accessrule', name='partnerId', - field=models.ForeignKey(to='partner.Partner'), + field=models.ForeignKey(to='partner.Partner', on_delete=models.PROTECT), ), migrations.AddField( model_name='accessrule', name='patternId', - field=models.ForeignKey(to='authorization.UriPattern'), + field=models.ForeignKey(to='authorization.UriPattern', on_delete=models.PROTECT), ), ] diff --git a/authorization/migrations/0002_uripattern_length.py b/authorization/migrations/0002_uripattern_length.py index c13760fc..f7b6a22c 100644 --- a/authorization/migrations/0002_uripattern_length.py +++ b/authorization/migrations/0002_uripattern_length.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -14,6 +14,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='uripattern', name='pattern', - field=models.CharField(default=b'', max_length=5000), + field=models.CharField(default='', max_length=5000), ), ] diff --git a/authorization/migrations/0003_UriPattern_RedirectUri_add_column_PWL715.py b/authorization/migrations/0003_UriPattern_RedirectUri_add_column_PWL715.py index eb9dd316..304ccf48 100644 --- a/authorization/migrations/0003_UriPattern_RedirectUri_add_column_PWL715.py +++ b/authorization/migrations/0003_UriPattern_RedirectUri_add_column_PWL715.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/authorization/models.py b/authorization/models.py index 1cbb0376..a59f3c6d 100644 --- a/authorization/models.py +++ b/authorization/models.py @@ -39,16 +39,16 @@ class Meta: class AccessRule(models.Model): accessRuleId = models.AutoField(primary_key=True) - patternId = models.ForeignKey('UriPattern') - accessTypeId = models.ForeignKey('AccessType') - partnerId = models.ForeignKey('partner.Partner') + patternId = models.ForeignKey('UriPattern', on_delete=models.PROTECT) + accessTypeId = models.ForeignKey('AccessType', on_delete=models.PROTECT) + partnerId = models.ForeignKey('partner.Partner', on_delete=models.PROTECT) class Meta: db_table = "AccessRule" class AccessType(models.Model): accessTypeId = models.AutoField(primary_key=True) name = models.CharField(max_length=200) - + @staticmethod def checkHasAccessRule(url, accessTypeName, partnerId): if not (url and accessTypeName and partnerId): diff --git a/authorization/pyTests.py b/authorization/pyTests.py deleted file mode 100644 index f03c1cb2..00000000 --- a/authorization/pyTests.py +++ /dev/null @@ -1,235 +0,0 @@ -#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. - -import django -import unittest -import sys, getopt -import requests -from unittest import TestCase -from common.pyTests import PyTestGenerics, GenericCRUDTest, GenericTest - -from subscription.testSamples import SubscriptionSample -from party.testSamples import IpRangeSample, PartySample -from partner.testSamples import PartnerSample -from partner.models import Partner -from authorization.models import Status - -from testSamples import UriPatternSample, AccessRuleSample, AccessTypeSample, CredentialSample - -from authentication.views import generateSecretKey - -# Create your tests here. -django.setup() -serverUrl = PyTestGenerics.initPyTest() -print "using server url %s" % serverUrl - - -# ---------------------- UNIT TEST FOR BASIC CRUD OPERATIONS ------------- - -class UriPatternCRUD(GenericCRUDTest, TestCase): - sample = UriPatternSample(serverUrl) - -class AccessRuleCRUD(GenericCRUDTest, TestCase): - sample = AccessRuleSample(serverUrl) - partnerSample = PartnerSample(serverUrl) - patternSample = UriPatternSample(serverUrl) - accessTypeSample = AccessTypeSample(serverUrl) - def setUp(self): - super(AccessRuleCRUD,self).setUp() - Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() - self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) - self.patternId = self.patternSample.forcePost(self.patternSample.data) - self.accessTypeId = self.accessTypeSample.forcePost(self.accessTypeSample.data) - self.sample.partnerId=self.sample.data['partnerId']=self.sample.updateData['partnerId']=self.partnerId - self.sample.data['patternId']=self.sample.updateData['patternId']=self.patternId - self.sample.data['accessTypeId']=self.sample.updateData['accessTypeId']=self.accessTypeId - - def tearDown(self): - super(AccessRuleCRUD,self).tearDown() - PyTestGenerics.forceDelete(self.partnerSample.model, self.partnerSample.pkName, self.partnerId) - PyTestGenerics.forceDelete(self.patternSample.model, self.patternSample.pkName, self.patternId) - PyTestGenerics.forceDelete(self.accessTypeSample.model, self.accessTypeSample.pkName, self.accessTypeId) - -class AccessTypesCRUD(GenericCRUDTest, TestCase): - sample = AccessTypeSample(serverUrl) - -# ----------------- END OF BASIC CRUD OPERATIONS ---------------------- - - -# Base class for sample management for access, subscription, and authorization access tests. -class AuthorizationTestBase(GenericTest, TestCase): - - # should be consistent with IpRangeSample object - successIp = '120.1.0.0' - failIp = '12.2.3.4' - - # should be consistent with SubscriptionSample object - successSubscriptionData = { - 'startDate':'2012-04-12T00:00:00Z', - 'endDate':'2018-04-12T00:00:00Z', - 'partnerId':None, # To be populated after Partner is created - 'partyId':None, # To be populated after Party is created - } - failSubscriptionData = { - 'startDate':'2012-04-12T00:00:00Z', - 'endDate':'2014-04-12T00:00:00Z', - 'partnerId':None, - 'partyId':None, - } - - def initSamples(self): - self.partnerSample = PartnerSample(serverUrl) - self.partySample = PartySample(serverUrl) - self.subscriptionSample = SubscriptionSample(serverUrl) - self.ipRangeSample = IpRangeSample(serverUrl) - self.uriPatternSample = UriPatternSample(serverUrl) - self.accessTypeSample = AccessTypeSample(serverUrl) - self.accessRuleSample = AccessRuleSample(serverUrl) - self.credentialSample = CredentialSample() - - def createSamples(self): - # create independent objects - self.accessTypeId = self.accessTypeSample.forcePost(self.accessTypeSample.data) - self.uriPatternId = self.uriPatternSample.forcePost(self.uriPatternSample.data) - Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() - self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) - self.partyId = self.partySample.forcePost(self.partySample.data) - - # create AccessRule based on AccessType, Pattern, and Partner objects created - self.accessRuleSample.data['accessTypeId']=self.accessTypeId - self.accessRuleSample.data['patternId']=self.uriPatternId - self.accessRuleSample.data['partnerId'] = self.partnerId - self.accessRuleId = self.accessRuleSample.forcePost(self.accessRuleSample.data) - - # create Subscription object based on Party and Partner objects created - self.subscriptionSample.data['partyId'] = self.partyId - self.subscriptionSample.data['partnerId'] = self.partnerId - self.subscriptionId = self.subscriptionSample.forcePost(self.subscriptionSample.data) - - # create IpRange object based on data Party created - self.ipRangeSample.data['partyId'] = self.partyId - self.ipRangeId = self.ipRangeSample.forcePost(self.ipRangeSample.data) - - # create Credential object based on Party created - PyTestGenerics.forceDelete(self.credentialSample.model, 'username', self.credentialSample.data['username']) - self.credentialSample.data['partyId'] = self.partyId - self.credentialSample.data['partnerId'] = self.partnerId - self.credentialId = self.credentialSample.forcePost(self.credentialSample.data) - - def deleteSamples(self): - PyTestGenerics.forceDelete(self.subscriptionSample.model, self.subscriptionSample.pkName, self.subscriptionId) - PyTestGenerics.forceDelete(self.ipRangeSample.model, self.ipRangeSample.pkName, self.ipRangeId) - PyTestGenerics.forceDelete(self.partySample.model, self.partySample.pkName, self.partyId) - PyTestGenerics.forceDelete(self.partnerSample.model, self.partnerSample.pkName, self.partnerId) - PyTestGenerics.forceDelete(self.accessTypeSample.model, self.accessTypeSample.pkName, self.accessTypeId) - PyTestGenerics.forceDelete(self.uriPatternSample.model, self.uriPatternSample.pkName, self.uriPatternId) - PyTestGenerics.forceDelete(self.accessRuleSample.model, self.accessRuleSample.pkName, self.accessRuleId) - PyTestGenerics.forceDelete(self.credentialSample.model, self.credentialSample.pkName, self.credentialId) - -class AuthenticationTest(AuthorizationTestBase): - url = serverUrl+'authorizations/authentications/' - - def runTest(self, urlType, expectedStatus): - #initialize samples - self.initSamples() - - # setting up data - self.accessTypeSample.data['name'] = urlType - - # create sample models in database - self.createSamples() - - # run the system test - loginKey = generateSecretKey(self.partyId, self.credentialSample.data['password']) - url = self.url + '?url=%s&partnerId=%s' % (self.uriPatternSample.data['pattern'], self.partnerId) - cookies = {'credentialId':str(self.partyId), 'secretKey':loginKey, 'apiKey':self.apiKey} - req = requests.get(url,cookies=cookies) - self.assertEqual(req.status_code, 200) - self.assertEqual(req.json()['access'], expectedStatus) - - # delete samples in database - self.deleteSamples() - - def test_for_authentication(self): - self.runTest("Login", True) - -class SubscriptionTest(AuthorizationTestBase): - url = serverUrl+'authorizations/subscriptions/' - - def runTest(self, subscriptionData, usePartyId, ip, urlType, expectedStatus): - #initialize samples - self.initSamples() - - # setting up data - self.subscriptionSample.data = subscriptionData - self.accessTypeSample.data['name'] = urlType - - # create sample models in database - self.createSamples() - - # run the system test - url = self.url + '?partnerId=%s&url=%s&apiKey=%s' % (self.partnerId, self.uriPatternSample.data['pattern'], self.apiKey) - if not ip == None: - url = url+'&ip=%s' % (ip) - if usePartyId: - url = url+'&partyId=%s' % (self.partyId) - - cookies = {'apiKey':self.apiKey} - req = requests.get(url,cookies=cookies) - self.assertEqual(req.status_code, 200) - self.assertEqual(req.json()['access'], expectedStatus) - - # delete samples in database - self.deleteSamples() - - def test_for_subscription(self): - # valid subscription based on partyId, Paid url. access should be True - self.runTest(self.successSubscriptionData, True, None, 'Paid', True) - # invalid subscription, Paid url. access should be False - self.runTest(self.failSubscriptionData, True, None, 'Paid', False) - # invalid subscription, not Paid url. access should be True - self.runTest(self.failSubscriptionData, True, None, 'Free', True) - # valid subscription based on IP, Paid url, access should be True - self.runTest(self.successSubscriptionData, False, self.successIp, 'Paid', True) - -class AccessTest(AuthorizationTestBase): - url = serverUrl+'authorizations/access/' - - def runTest(self, subscriptionData, usePartyId, ip, urlType, expectedStatus): - # initialize samples - self.initSamples() - - # setting up data - self.subscriptionSample.data = subscriptionData - self.accessTypeSample.data['name'] = urlType - - # create sample models in database - self.createSamples() - - # run the system test - url = self.url + '?partnerId=%s&url=%s&apiKey=%s' % (self.partnerId, self.uriPatternSample.data['pattern'], self.apiKey) - if not ip == None: - url = url+'&ip=%s' % (ip) - cookies = {'apiKey':self.apiKey} - if usePartyId: - cookies['partyId'] = str(self.partyId) - req = requests.get(url, cookies=cookies) - self.assertEqual(req.status_code, 200) - self.assertEqual(req.json()['status'], expectedStatus) - - # delete samples in database - self.deleteSamples() - - def test_for_access(self): - # valid subscription, paid url, status should be OK - self.runTest(self.successSubscriptionData, True, None, 'Paid', Status.ok) - # invalid subscription, paid url, status should be need subscription - self.runTest(self.failSubscriptionData, True, None, 'Paid', Status.needSubscription) - #self.runAccessTest(self.successSubscriptionData, False, self.successIp, self.paidUrl, Status.ok) - -print "Running unit tests on authorization web services API........." - -if __name__ == '__main__': - sys.argv[1:] = [] - unittest.main() - ret = not runner.run(suite).wasSuccessful() - sys.exit(ret) diff --git a/authorization/serializers.py b/authorization/serializers.py index eb1c3e1c..d15ad1f9 100644 --- a/authorization/serializers.py +++ b/authorization/serializers.py @@ -1,6 +1,6 @@ #Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. -from models import AccessType, AccessRule, UriPattern +from .models import AccessType, AccessRule, UriPattern from rest_framework import serializers class AccessTypeSerializer(serializers.ModelSerializer): diff --git a/authorization/testSamples.py b/authorization/testSamples.py index 28040b93..622b7b6f 100644 --- a/authorization/testSamples.py +++ b/authorization/testSamples.py @@ -1,23 +1,13 @@ #Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. -import django -import unittest -import sys, getopt -import requests -from unittest import TestCase from authorization.models import UriPattern, AccessRule, AccessType -from authentication.models import Credential from partner.models import Partner from party.models import Party -from common.pyTests import PyTestGenerics - -from authorization.models import Status +from common.tests import TestGenericInterfaces import copy - import hashlib -genericForcePost = PyTestGenerics.forcePost - +genericForcePost = TestGenericInterfaces.forcePost class UriPatternSample(): url = None @@ -34,25 +24,23 @@ class UriPatternSample(): def __init__(self, serverUrl): self.url = serverUrl+self.path - def forcePost(self,data): - return genericForcePost(self.model, self.pkName, data) + def forcePost(self,postData): + return genericForcePost(self.model, self.pkName, postData) class AccessRuleSample(): partnerId = 'tair' url = None path = 'authorizations/accessRules/' data = { - 'accessRuleId':1, - 'patternId':1, - 'accessTypeId':1, - 'partnerId':'tair', + 'partnerId':None, + 'patternId':None, + 'accessTypeId':None, } updateData = { - 'accessRuleId':1, - 'patternId':7, - 'accessTypeId':1, - 'partnerId':'cdiff', + 'partnerId':None, + 'patternId':None, + 'accessTypeId':None, } pkName = 'accessRuleId' model = AccessRule @@ -60,22 +48,23 @@ class AccessRuleSample(): def __init__(self, serverUrl): self.url = serverUrl+self.path - def forcePost(self,data): - postData = copy.deepcopy(data) - postData['patternId'] = UriPattern.objects.get(patternId=data['patternId']) - postData['accessTypeId'] = AccessType.objects.get(accessTypeId=data['accessTypeId']) - postData['partnerId'] = Partner.objects.get(partnerId=data['partnerId']) - return genericForcePost(self.model, self.pkName, postData) + def forcePost(self,postData): + processedData = copy.deepcopy(postData) + processedData['patternId'] = UriPattern.objects.get(patternId=postData['patternId']) + processedData['accessTypeId'] = AccessType.objects.get(accessTypeId=postData['accessTypeId']) + processedData['partnerId'] = Partner.objects.get(partnerId=postData['partnerId']) + return genericForcePost(self.model, self.pkName, processedData) class AccessTypeSample(): url = None path = 'authorizations/accessTypes/' + TYPE_LOGIN = 'Login' + TYPE_PAID = 'Paid' data = { - 'name':'test1', + 'name':TYPE_LOGIN, } - updateData = { - 'name':'test2', + 'name':TYPE_PAID, } pkName = 'accessTypeId' model = AccessType @@ -83,26 +72,11 @@ class AccessTypeSample(): def __init__(self, serverUrl): self.url = serverUrl+self.path - def forcePost(self,data): - return genericForcePost(self.model, self.pkName, data) - + def setAsLoginType(self): + self.data['name'] = self.TYPE_LOGIN -class CredentialSample(): - data = { - 'username':'steve', - 'password':hashlib.sha1('stevepass').hexdigest(), - 'email':'steve@getexp.com', - 'institution':'test organization', - 'partyId':None, - 'partnerId':None, - 'userIdentifier':'1234536', - } - pkName = 'id' - model = Credential - - def forcePost(self,data): - postData = copy.deepcopy(data) - postData['partyId'] = Party.objects.get(partyId=data['partyId']) - postData['partnerId'] = Partner.objects.get(partnerId=data['partnerId']) - return genericForcePost(self.model, self.pkName, postData) + def setAsPaidType(self): + self.data['name'] = self.TYPE_PAID + def forcePost(self,postData): + return genericForcePost(self.model, self.pkName, postData) \ No newline at end of file diff --git a/authorization/tests.py b/authorization/tests.py index 7ce503c2..964d1e90 100644 --- a/authorization/tests.py +++ b/authorization/tests.py @@ -1,3 +1,471 @@ +#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. + +import django +import unittest +import sys +import json from django.test import TestCase +from common.tests import TestGenericInterfaces, GenericCRUDTest, GenericTest +from party.testSamples import UserPartySample, OrganizationPartySample, IpRangeSample, CountrySample, ConsortiumPartySample, InstitutionPartySample, PartyAffiliationSample +from partner.testSamples import PartnerSample +from authentication.testSamples import CredentialSample +from subscription.testSamples import SubscriptionSample +from .testSamples import UriPatternSample, AccessRuleSample, AccessTypeSample +from authorization.models import Status +from http.cookies import SimpleCookie + +# Create your tests here. +django.setup() +serverUrl = TestGenericInterfaces.getHost() + +# ---------------------- UNIT TEST FOR BASIC CRUD OPERATIONS ------------- + +# Test for API end point /authorizations/patterns/ +class UriPatternCRUDTest(GenericCRUDTest, TestCase): + sample = UriPatternSample(serverUrl) + +# Test for API end point /authorizations/accessTypes/ +class AccessTypesCRUDTest(GenericCRUDTest, TestCase): + sample = AccessTypeSample(serverUrl) + +# Test for API end point /authorizations/accessRules/ +class AccessRuleCRUDTest(GenericCRUDTest, TestCase): + sample = AccessRuleSample(serverUrl) + + def setUp(self): + super(AccessRuleCRUDTest,self).setUp() + partnerSample = PartnerSample(serverUrl) + partnerId = partnerSample.forcePost(partnerSample.data) + self.sample.data['partnerId']=self.sample.updateData['partnerId']=partnerId + + patternSample = UriPatternSample(serverUrl) + patternId = patternSample.forcePost(patternSample.data) + self.sample.data['patternId']=patternId + + accessTypeSample = AccessTypeSample(serverUrl) + accessTypeId = accessTypeSample.forcePost(accessTypeSample.data) + self.sample.data['accessTypeId']=accessTypeId + + def test_for_update(self): + # set different pattern for update data + patternSample = UriPatternSample(serverUrl) + updatePatternId = patternSample.forcePost(patternSample.updateData) + self.sample.updateData['patternId']=updatePatternId + + # set different access type for update data + accessTypeSample = AccessTypeSample(serverUrl) + updateAccessTypeId = accessTypeSample.forcePost(accessTypeSample.updateData) + self.sample.updateData['accessTypeId']=updateAccessTypeId + + super(AccessRuleCRUDTest, self).test_for_update() + +# # ----------------- END OF BASIC CRUD OPERATIONS ---------------------- + +# Base class for sample management for access, subscription, and authorization access tests. +class GenericAuthorizationTest(GenericTest, TestCase): + partnerId = None + patternId = None + patternSample = UriPatternSample(serverUrl) + accessRuleSample = AccessRuleSample(serverUrl) + + def setUp(self): + super(GenericAuthorizationTest, self).setUp() + + partnerSample = PartnerSample(serverUrl) + self.partnerId = partnerSample.forcePost(partnerSample.data) + self.accessRuleSample.data['partnerId'] = self.partnerId + + patternId = self.patternSample.forcePost(self.patternSample.data) + self.accessRuleSample.data['patternId'] = patternId + + def setUpLoginAccessRule(self): + accessTypeSample = AccessTypeSample(serverUrl) + accessTypeSample.setAsLoginType() + accessTypeId = accessTypeSample.forcePost(accessTypeSample.data) + + self.accessRuleSample.data['accessTypeId'] = accessTypeId + self.accessRuleSample.forcePost(self.accessRuleSample.data) + + def setUpPaidAccessRule(self): + accessTypeSample = AccessTypeSample(serverUrl) + accessTypeSample.setAsPaidType() + accessTypeId = accessTypeSample.forcePost(accessTypeSample.data) + + self.accessRuleSample.data['accessTypeId'] = accessTypeId + self.accessRuleSample.forcePost(self.accessRuleSample.data) + + def setUpSubscribedUser(self): + credentialSample = self.setUpCredentialSample() + + individualSubscriptionSample = SubscriptionSample(serverUrl) + individualSubscriptionSample.setPartnerId(self.partnerId) + individualSubscriptionSample.setPartyId(credentialSample.getPartyId()) + individualSubscriptionSample.forcePost(individualSubscriptionSample.data) + + return credentialSample + + def setUpCredentialSample(self): + userPartySample = UserPartySample(serverUrl) + userPartyId = userPartySample.forcePost(userPartySample.data) + + credentialSample = CredentialSample(serverUrl) + + credentialSample.setPartnerId(self.partnerId) + credentialSample.setPartyId(userPartyId) + credentialSample.forcePost(credentialSample.data) + + return credentialSample + + def setUpSubscribedOrganization(self): + countrySample = CountrySample(serverUrl) + countryId = countrySample.forcePost(countrySample.data) + + organizationPartySample = OrganizationPartySample(serverUrl) + organizationPartySample.setCountry(countryId) + organizationPartyId = organizationPartySample.forcePost(organizationPartySample.data) + + orgIpRangeSample = IpRangeSample(serverUrl) + orgIpRangeSample.setPartyId(organizationPartyId) + orgIpRangeSample.forcePost(orgIpRangeSample.data) + + organizationSubscriptionSample = SubscriptionSample(serverUrl) + organizationSubscriptionSample.setPartnerId(self.partnerId) + organizationSubscriptionSample.setPartyId(organizationPartyId) + organizationSubscriptionSample.forcePost(organizationSubscriptionSample.data) + + return orgIpRangeSample + + def setUpSubscribedConsortium(self): + countrySample = CountrySample(serverUrl) + countryId = countrySample.forcePost(countrySample.data) + + # create subscribed consortium + consortiumPartySample = ConsortiumPartySample(serverUrl) + consortiumPartySample.setCountry(countryId) + consortiumPartyId = consortiumPartySample.forcePost(consortiumPartySample.data) + + consortiumSubscriptionSample = SubscriptionSample(serverUrl) + consortiumSubscriptionSample.setPartnerId(self.partnerId) + consortiumSubscriptionSample.setPartyId(consortiumPartyId) + consortiumSubscriptionSample.forcePost(consortiumSubscriptionSample.data) + + # create institution that is a subsidy of consortium + institutionPartySample = InstitutionPartySample(serverUrl) + institutionPartySample.setCountry(countryId) + institutionPartyId = institutionPartySample.forcePost(institutionPartySample.data) + + institutionIpRangeSample = IpRangeSample(serverUrl) + institutionIpRangeSample.setPartyId(institutionPartyId) + institutionIpRangeSample.forcePost(institutionIpRangeSample.data) + + affiliationSample = PartyAffiliationSample(serverUrl) + affiliationSample.setParentId(consortiumPartyId) + affiliationSample.setChildId(institutionPartyId) + affiliationSample.forcePost(affiliationSample.data) + + return institutionIpRangeSample + +# Test for API end point /authorizations/authentications/ +# Checks if user can access a Login type pattern +# End point returns true if url to check does not require login or user already logins +# Returns false when url requires login and user is not logged in +# ApiKey, credentialId and secretKey are required and need to be passed in by cookie +class AuthenticationTest(GenericAuthorizationTest): + url = serverUrl+'authorizations/authentications/' + loginPattern = None + nonLoginPattern = None + credentialSample = None + + def setUp(self): + super(AuthenticationTest,self).setUp() + + self.setUpLoginAccessRule() + self.loginPattern = self.patternSample.data['pattern'] + self.nonLoginPattern = self.patternSample.updateData['pattern'] + + # create user credential and its subscription + self.credentialSample = self.setUpCredentialSample() + + def test_for_authentication(self): + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + + # test login not required pattern when user is not logged in + self.runAuthenticationTest(self.nonLoginPattern, True) + + # test login required pattern when user is not logged in + self.runAuthenticationTest(self.loginPattern, False) + + # test login required pattern when user is logged in + credentialSample = self.credentialSample + cookies = SimpleCookie({'apiKey':self.apiKey, 'credentialId':credentialSample.getPartyId(), 'secretKey':credentialSample.getSecretKey()}) + self.runAuthenticationTest(self.loginPattern, True, cookies) + + def runAuthenticationTest(self, urlToCheck, expectedStatus, cookies = None): + url = '%s?partnerId=%s&url=%s' % (self.url, self.partnerId, urlToCheck) + if cookies: + self.client.cookies = cookies + else: + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + res = self.client.get(url) + self.assertEqual(res.status_code, 200) + self.assertEqual(json.loads(res.content)['access'], expectedStatus) + +# Test for API end point /authorizations/subscriptions/ +# Checks if an user (by partyId) or an IP address can access a Paid (subsription required) +# type pattern +# End point returns true if url to check does not require subscription or if user +# or ip address belongs to a subscribed entity +# Returns false when url requires subscription and no subscribed entity detected +class SubscriptionTest(GenericAuthorizationTest): + url = serverUrl + 'authorizations/subscriptions/' + paidPattern = None + nonPaidPattern = None + + def setUp(self): + super(SubscriptionTest,self).setUp() + + self.setUpPaidAccessRule() + self.paidPattern = self.patternSample.data['pattern'] + self.nonPaidPattern = self.patternSample.updateData['pattern'] + + def test_for_subscription(self): + credentialSample = self.setUpSubscribedUser() + userPartyId = credentialSample.getPartyId() + + orgIpRangeSample = self.setUpSubscribedOrganization() + + # User with subscription, Paid url. access should be True + # Note if a non-exist partyId is passed in an error will be thrown + authParam = 'partyId=%s' % userPartyId + self.runSubscriptionTest(self.paidPattern, authParam , True) + + # IP of organization with subscription, Paid url, access should be True + subscribedIP = orgIpRangeSample.getInRangeIp() + authParam = 'ip=%s' % subscribedIP + self.runSubscriptionTest(self.paidPattern, authParam, True) + + # IP outside of organization with subscription, Paid url, access should be False + # Note that if an invalid format IP is passed in, error will be thrown + unsubscribedIP = orgIpRangeSample.getOutRangeIp() + authParam = 'ip=%s' % unsubscribedIP + self.runSubscriptionTest(self.paidPattern, authParam, False) + + # No identity info, Paid url. access should be False + self.runSubscriptionTest(self.paidPattern, None, False) + + # Not Paid url. access should be True + self.runSubscriptionTest(self.nonPaidPattern, None, True) + + def test_for_consortium_subscription(self): + consOrgIpRangeSample = self.setUpSubscribedConsortium() + + # Institution whose parent consortium has subscription, Paid url. Passed in institution party id + # Access should be True + # Note if a non-exist partyId is passed in an error will be thrown + authParam = 'partyId=%s' % consOrgIpRangeSample.getPartyId() + self.runSubscriptionTest(self.paidPattern, authParam , True) + + # IP of institution whose parent consortium has subscription, Paid url, access should be True + subscribedIP = consOrgIpRangeSample.getInRangeIp() + authParam = 'ip=%s' % subscribedIP + self.runSubscriptionTest(self.paidPattern, authParam, True) + + # IP outside of organization with subscription, Paid url, access should be False + # Note that if an invalid format IP is passed in, error will be thrown + unsubscribedIP = consOrgIpRangeSample.getOutRangeIp() + authParam = 'ip=%s' % unsubscribedIP + self.runSubscriptionTest(self.paidPattern, authParam, False) + + def runSubscriptionTest(self, urlToCheck, authParam, expectedStatus): + url = '%s?partnerId=%s&url=%s' % (self.url, self.partnerId, urlToCheck) + if authParam: + url = '%s&%s' % (url, authParam) + + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + res = self.client.get(url) + self.assertEqual(res.status_code, 200) + self.assertEqual(json.loads(res.content)['access'], expectedStatus) + +# Test for API end point /authorizations/access/ +# Checks if a url pattern requires subscription and if an IP address or a list of IP addresses +# can access this pattern +# End point returns a JSON object, which includes but not limited to +# (1) status as either "NeedLogin" (for pattern that requires login, this checks before subscription status) +# or "OK" (for free pattern or paid pattern + subscribed IP) or "NeedSubscription" (paid pattern + +# unsubscribed IP) +# (2) isPaidContent as "T" (paid pattern) or "F" (free pattern) +# (3) userIdentifier for the passed in credentialId and secretKey +# ApiKey, credentialId and secretKey are required and need to be passed in by cookie +class AccessTest(GenericAuthorizationTest): + url = serverUrl + 'authorizations/access/' + + def setUp(self): + super(AccessTest, self).setUp() + + def test_for_login_only_access(self): + self.setUpLoginAccessRule() + loginPattern = self.patternSample.data['pattern'] + nonLoginPattern = self.patternSample.updateData['pattern'] + + credentialSample = self.setUpCredentialSample() + + # test login not required & free pattern when user is not logged in, status should be OK + self.runAccessTest(urlToCheck=nonLoginPattern, expectedUrlPaidStatus=False, + authParam=None, ipList=None, expectedNeedLoginStatus=False, + expectedAccessStatus=True, expectedUserIdentifier=None, cookies=None) + + # test login required & free pattern when user is not logged in, status should be NeedLogin + self.runAccessTest(urlToCheck=loginPattern, expectedUrlPaidStatus=False, + authParam=None, ipList=None, expectedNeedLoginStatus=True, + expectedAccessStatus=None, expectedUserIdentifier=None, cookies=None) + + # test valid credential + login required & free url, status should be OK + partyId = credentialSample.getPartyId() + # note this partyId can be different from the credentialId we passed in cookies, and the user identifier + # returned is in accordance with this partyId. For the sake of consistency we use same id in the test + authParam = 'partyId=%s' % partyId + cookies = SimpleCookie({'apiKey':self.apiKey, 'credentialId':partyId, 'secretKey':credentialSample.getSecretKey()}) + self.runAccessTest(urlToCheck=loginPattern, expectedUrlPaidStatus=False, + authParam=authParam, ipList=None, expectedNeedLoginStatus=False, + expectedAccessStatus=True, expectedUserIdentifier=credentialSample.getUserIdentifier(), cookies=cookies) + + def test_for_subscription_only_access(self): + orgIpRangeSample = self.setUpSubscribedOrganization() + + self.runSubscriptionOnlyAccessTest(orgIpRangeSample) + + def test_for_subscription_only_access_for_consortium(self): + orgIpRangeSample = self.setUpSubscribedConsortium() + + self.runSubscriptionOnlyAccessTest(orgIpRangeSample) + + def runSubscriptionOnlyAccessTest(self, orgIpRangeSample): + self.setUpPaidAccessRule() + paidPattern = self.patternSample.data['pattern'] + nonPaidPattern = self.patternSample.updateData['pattern'] + + credentialSample = self.setUpSubscribedUser() + + # test no credential & ip for paid url, status should be NeedSubscription + self.runAccessTest(urlToCheck=paidPattern, expectedUrlPaidStatus=True, + authParam=None, ipList=None, expectedNeedLoginStatus=False, + expectedAccessStatus=False, expectedUserIdentifier=None, cookies=None) + + # test no credential & ip for free url, status should be OK + self.runAccessTest(urlToCheck=nonPaidPattern, expectedUrlPaidStatus=False, + authParam=None, ipList=None, expectedNeedLoginStatus=False, + expectedAccessStatus=True, expectedUserIdentifier=None, cookies=None) + + # test valid subscription for individual, paid url, status should be OK + partyId = credentialSample.getPartyId() + authParam = 'partyId=%s' % partyId + cookies = SimpleCookie({'apiKey':self.apiKey, 'credentialId':partyId, 'secretKey':credentialSample.getSecretKey()}) + self.runAccessTest(urlToCheck=paidPattern, expectedUrlPaidStatus=True, + authParam=authParam, ipList=None, expectedNeedLoginStatus=False, + expectedAccessStatus=True, expectedUserIdentifier=credentialSample.getUserIdentifier(), cookies=cookies) + + # test valid subscription for organization, paid url, status should be OK + subscribedIP = orgIpRangeSample.getInRangeIp() + self.runAccessTest(urlToCheck=paidPattern, expectedUrlPaidStatus=True, + authParam=None, ipList=subscribedIP, expectedNeedLoginStatus=False, + expectedAccessStatus=True, expectedUserIdentifier=None, cookies=None) + + # test IP outside of subscribed organization subscription, paid url, status should be NeedSubscription + unsubscribedIP = orgIpRangeSample.getOutRangeIp() + self.runAccessTest(urlToCheck=paidPattern, expectedUrlPaidStatus=True, + authParam=None, ipList=unsubscribedIP, expectedNeedLoginStatus=False, + expectedAccessStatus=False, expectedUserIdentifier=None, cookies=None) + + def test_for_login_and_subscription_access(self): + orgIpRangeSample = self.setUpSubscribedOrganization() + + self.runLoginAndSubscriptionAccessTest(orgIpRangeSample) + + def test_for_login_and_subscription_access_for_consortium(self): + orgIpRangeSample = self.setUpSubscribedConsortium() + + self.runLoginAndSubscriptionAccessTest(orgIpRangeSample) + + def runLoginAndSubscriptionAccessTest(self, orgIpRangeSample): + self.setUpLoginAccessRule() + self.setUpPaidAccessRule() + loginPaidPattern = self.patternSample.data['pattern'] + + # test login required & paid pattern when no user nor IP provided, status should be NeedLogin + self.runAccessTest(urlToCheck=loginPaidPattern, expectedUrlPaidStatus=True, + authParam=None, ipList=None, expectedNeedLoginStatus=True, + expectedAccessStatus=None, expectedUserIdentifier=None, cookies=None) + + # test login required & paid pattern when only IP provided, status should be NeedLogin + subscribedIP = orgIpRangeSample.getInRangeIp() + self.runAccessTest(urlToCheck=loginPaidPattern, expectedUrlPaidStatus=True, + authParam=None, ipList=subscribedIP, expectedNeedLoginStatus=True, + expectedAccessStatus=None, expectedUserIdentifier=None, cookies=None) + + # test login required & paid pattern with subscribed user, status should be OK + credentialSample = self.setUpSubscribedUser() + partyId = credentialSample.getPartyId() + authParam = 'partyId=%s' % partyId + cookies = SimpleCookie({'apiKey':self.apiKey, 'credentialId':partyId, 'secretKey':credentialSample.getSecretKey()}) + self.runAccessTest(urlToCheck=loginPaidPattern, expectedUrlPaidStatus=True, + authParam=authParam, ipList=None, expectedNeedLoginStatus=False, + expectedAccessStatus=True, expectedUserIdentifier=credentialSample.getUserIdentifier(), cookies=cookies) + + # create additional credential that is not subscribed + nonSubUserPartySample = UserPartySample(serverUrl) + nonSubUserPartyId = nonSubUserPartySample.forcePost(nonSubUserPartySample.updateData) + + nonSubCredentialSample = CredentialSample(serverUrl) + nonSubCredentialSample.setAsUpdateExample() + nonSubCredentialSample.setPartnerId(self.partnerId) + nonSubCredentialSample.setPartyId(nonSubUserPartyId) + nonSubCredentialSample.forcePost(nonSubCredentialSample.data) + + # test login required & paid pattern with unsubscribed user , status should be NeedSubscription + partyId = nonSubCredentialSample.getPartyId() + authParam = 'partyId=%s' % partyId + cookies = SimpleCookie({'apiKey':self.apiKey, 'credentialId':partyId, 'secretKey':nonSubCredentialSample.getSecretKey()}) + self.runAccessTest(urlToCheck=loginPaidPattern, expectedUrlPaidStatus=True, + authParam=authParam, ipList=None, expectedNeedLoginStatus=False, + expectedAccessStatus=False, expectedUserIdentifier=nonSubCredentialSample.getUserIdentifier(), cookies=cookies) + + # Note ipList param always need to add to end point call even when no value for it + def runAccessTest(self, urlToCheck, expectedUrlPaidStatus, authParam = None, ipList = None, + expectedNeedLoginStatus = None, expectedAccessStatus = None, expectedUserIdentifier = None, cookies = None): + if not ipList: + ipList = '' + url = '%s?partnerId=%s&url=%s&ipList=%s' % (self.url, self.partnerId, urlToCheck, ipList) + if authParam: + url = '%s&%s' % (url, authParam) + + if cookies: + self.client.cookies = cookies + else: + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + res = self.client.get(url) + self.assertEqual(res.status_code, 200) + res = json.loads(res.content) + status = '' + if expectedNeedLoginStatus is not None and expectedNeedLoginStatus: + status = Status.needLogin + if expectedAccessStatus is not None: + if expectedAccessStatus: + status = Status.ok + else: + status = Status.needSubscription + self.assertEqual(res['status'], status) + if expectedUrlPaidStatus: + isPaidContent = 'T' + else: + isPaidContent = 'F' + self.assertEqual(res['isPaidContent'], isPaidContent) + if expectedUserIdentifier: + self.assertEqual(res['userIdentifier'], str(expectedUserIdentifier)) + +print("Running unit tests on authorization web services API.........") -# Create your tests here. +if __name__ == '__main__': + sys.argv[1:] = [] + unittest.main() + ret = not runner.run(suite).wasSuccessful() + sys.exit(ret) diff --git a/authorization/views.py b/authorization/views.py index f092c4c1..50d1fcbe 100644 --- a/authorization/views.py +++ b/authorization/views.py @@ -3,10 +3,10 @@ from django.http import HttpResponse from rest_framework.views import APIView from rest_framework import generics -from controls import Authorization +from .controls import Authorization -from models import AccessType, AccessRule, UriPattern, Status -from serializers import AccessTypeSerializer, AccessRuleSerializer, UriPatternSerializer +from .models import AccessType, AccessRule, UriPattern, Status +from .serializers import AccessTypeSerializer, AccessRuleSerializer, UriPatternSerializer from partner.models import Partner from authentication.models import Credential from common.views import GenericCRUDView @@ -16,7 +16,7 @@ import json import re -import urllib +import urllib.request, urllib.parse, urllib.error import logging logger = logging.getLogger('phoenix.api.authorization') @@ -35,7 +35,7 @@ def get(self, request, format=None): partyId = request.COOKIES.get('credentialId') loginKey = request.COOKIES.get('secretKey') ipList = request.GET.get('ipList') - url = request.GET.get('url').decode('utf8') + url = request.GET.get('url') partnerId = request.GET.get('partnerId') apiKey = request.COOKIES.get('apiKey') ipList = ipList.split(',') @@ -118,18 +118,18 @@ def get(self, request, format=None): requestPatternId = request.GET.get('patternId') if UriPattern.objects.filter(patternId = requestPatternId).exists(): obj = UriPattern.objects.get(patternId = requestPatternId) - serializer = UriPatternSerializer(obj, many=True) + serializer = UriPatternSerializer(obj) logger.info("Authorization URIAccess %s%s %s%s %s%s" % ("requestPatternId:",requestPatternId,"serializer.data:",serializer.data,"status:",status.HTTP_200_OK)) return Response(serializer.data, status=status.HTTP_200_OK) else: logger.info("Authorization URIAccess %s%s %s" % ("GET URIAccess error: requestPatternId:",requestPatternId,"not found")) return Response({'GET error: patternId' + requestPatternId + ' not found'}) - + def delete(self, request, format=None): params = request.GET if 'patternId' not in params: return Response({'DELETE error':'patternId required'},status=status.HTTP_400_BAD_REQUEST) - + requestPatternId = params['patternId'] if UriPattern.objects.filter(patternId = requestPatternId).exists(): pattern = UriPattern.objects.get(patternId = requestPatternId) @@ -137,7 +137,7 @@ def delete(self, request, format=None): return Response({'DELETE success':'delete of patternId '+requestPatternId+' completed'},status=status.HTTP_200_OK) else: return Response({'DELETE error':'delete of patternId '+requestPatternId+' failed. patternId not found'},status=status.HTTP_400_BAD_REQUEST) - + def put(self, request, format=None): params = request.GET if 'patternId' not in params: @@ -145,7 +145,7 @@ def put(self, request, format=None): data = request.data if 'pattern' not in data: return Response({'error':'PUT method:pattern is required as form-data'}, status=status.HTTP_400_BAD_REQUEST) - + patternFromRequest = data['pattern'] isREValid = isRegExpValid(patternFromRequest) if not isREValid: @@ -154,7 +154,7 @@ def put(self, request, format=None): patternIdFromRequest = request.GET.get('patternId') if not UriPattern.objects.filter(patternId = patternIdFromRequest).exists(): return Response({'error':'PUT method:patternId '+patternIdFromRequest+' not found'}, status=status.HTTP_400_BAD_REQUEST) - + pattern = UriPattern.objects.get(patternId=patternIdFromRequest) serializer = UriPatternSerializer(pattern,data=data) @@ -163,17 +163,17 @@ def put(self, request, format=None): return Response(serializer.data, status=status.HTTP_200_OK) else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - + def post(self,request, format=None): data = request.data if 'pattern' not in data: return Response({'error':'POST method:pattern is required as form-data'}, status=status.HTTP_400_BAD_REQUEST) - + patternFromRequest = data['pattern'] isREValid = isRegExpValid(patternFromRequest) if not isREValid: return Response({'error':'POST method:pattern '+patternFromRequest+' is not valid regexp'}, status=status.HTTP_400_BAD_REQUEST) - + serializer = UriPatternSerializer(data=data) if serializer.is_valid(): serializer.save() diff --git a/common/common.py b/common/common.py index 33fb8a6a..025f3121 100644 --- a/common/common.py +++ b/common/common.py @@ -1,62 +1,62 @@ -# The catch-all container for any commonly used classes/functions that don't (yet) deserve dedicated containers of their own. -from django.core.exceptions import ValidationError -from django.utils.translation import ugettext_lazy as _ -from netaddr import IPAddress, IPRange, IPNetwork - -# Determine IP address of the host from which the given request has been received. -# -def getRemoteIpAddress(request): - - # If the request comes through an HTTP proxy, use the first of the IP addresses specified in the XFF header. - x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') - if x_forwarded_for: - ip = x_forwarded_for.split(',')[0] - else: - ip = request.META.get('REMOTE_ADDR') - return ip - -# validate ip range based on a series of conditions -def validateIpRange(start, end): - if isIpRangePrivate(start, end): - raise ValidationError({'IP Range': _('IP range contains private IP: %s - %s' % (start, end))}) - if not validateIpRangeSize(start, end): - raise ValidationError({'IP Range': _('IP range too large: %s - %s' % (start, end))}) - -# check if the ip range is private -def isIpRangePrivate(start, end): - if IPAddress(start).is_private() or IPAddress(end).is_private(): - return True - ipRange = IPRange(start, end) - if ipRange.__getstate__()[2] == 4: - for cidr in IPV4_PRIVATE: - - if ipRange.__contains__(cidr): - return True - if ipRange.__getstate__()[2] == 6: - for cidr in IPV6_PRIVATE: - if ipRange.__contains__(cidr): - return True - return False - -IPV4_PRIVATE = ( - IPNetwork('10.0.0.0/8'), # Class A private network local communication (RFC 1918) - IPNetwork('100.64.0.0/10'), # Carrier grade NAT (RFC 6598) - IPNetwork('172.16.0.0/12'), # Private network - local communication (RFC 1918) - IPNetwork('192.0.0.0/24'), # IANA IPv4 Special Purpose Address Registry (RFC 5736) - IPNetwork('192.168.0.0/16'), # Class B private network local communication (RFC 1918) - IPNetwork('198.18.0.0/15'), # Testing of inter-network communications between subnets (RFC 2544) - IPRange('239.0.0.0', '239.255.255.255'), # Administrative Multicast -) - -IPV6_PRIVATE = ( - IPNetwork('fc00::/7'), # Unique Local Addresses (ULA) - IPNetwork('fec0::/10'), # Site Local Addresses (deprecated - RFC 3879) -) - -# check if the ip range is over the limit -def validateIpRangeSize(start, end): - ipRange = IPRange(start, end) - if ipRange.__getstate__()[2] == 4: - return True if ipRange.size <= 65536 else False - if ipRange.__getstate__()[2] == 6: - return True if ipRange.size <= 324518553658426726783156020576256 else False +# The catch-all container for any commonly used classes/functions that don't (yet) deserve dedicated containers of their own. +from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ +from netaddr import IPAddress, IPRange, IPNetwork + +# Determine IP address of the host from which the given request has been received. +# +def getRemoteIpAddress(request): + + # If the request comes through an HTTP proxy, use the first of the IP addresses specified in the XFF header. + x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') + if x_forwarded_for: + ip = x_forwarded_for.split(',')[0] + else: + ip = request.META.get('REMOTE_ADDR') + return ip + +# validate ip range based on a series of conditions +def validateIpRange(start, end): + if isIpRangePrivate(start, end): + raise serializers.ValidationError({'IP Range': _('IP range contains private IP: %s - %s' % (start, end))}) + if not validateIpRangeSize(start, end): + raise serializers.ValidationError({'IP Range': _('IP range too large: %s - %s' % (start, end))}) + +# check if the ip range is private +def isIpRangePrivate(start, end): + if IPAddress(start).is_private() or IPAddress(end).is_private(): + return True + ipRange = IPRange(start, end) + if ipRange.__getstate__()[2] == 4: + for cidr in IPV4_PRIVATE: + + if ipRange.__contains__(cidr): + return True + if ipRange.__getstate__()[2] == 6: + for cidr in IPV6_PRIVATE: + if ipRange.__contains__(cidr): + return True + return False + +IPV4_PRIVATE = ( + IPNetwork('10.0.0.0/8'), # Class A private network local communication (RFC 1918) + IPNetwork('100.64.0.0/10'), # Carrier grade NAT (RFC 6598) + IPNetwork('172.16.0.0/12'), # Private network - local communication (RFC 1918) + IPNetwork('192.0.0.0/24'), # IANA IPv4 Special Purpose Address Registry (RFC 5736) + IPNetwork('192.168.0.0/16'), # Class B private network local communication (RFC 1918) + IPNetwork('198.18.0.0/15'), # Testing of inter-network communications between subnets (RFC 2544) + IPRange('239.0.0.0', '239.255.255.255'), # Administrative Multicast +) + +IPV6_PRIVATE = ( + IPNetwork('fc00::/7'), # Unique Local Addresses (ULA) + IPNetwork('fec0::/10'), # Site Local Addresses (deprecated - RFC 3879) +) + +# check if the ip range is over the limit +def validateIpRangeSize(start, end): + ipRange = IPRange(start, end) + if ipRange.__getstate__()[2] == 4: + return True if ipRange.size <= 65536 else False + if ipRange.__getstate__()[2] == 6: + return True if ipRange.size <= 324518553658426726783156020576256 else False diff --git a/common/permissions.py b/common/permissions.py index b6743813..602943bc 100644 --- a/common/permissions.py +++ b/common/permissions.py @@ -1,4 +1,5 @@ from apikey.models import ApiKey +from authentication.models import Credential class ApiKeyPermission(): @staticmethod @@ -20,7 +21,6 @@ def has_object_permission(self, request, view, obj): return True def isPhoenix(request): - from authentication.models import Credential credentialId = request.GET.get('credentialId') secretKey = request.GET.get('secretKey') if credentialId and secretKey and Credential.validate(credentialId, secretKey): @@ -28,7 +28,6 @@ def isPhoenix(request): return False def isLoggedIn(request): - from authentication.models import Credential credentialId = request.GET.get('credentialId') secretKey = request.GET.get('secretKey') if credentialId and secretKey and Credential.validate(credentialId, secretKey):# and Credential.objects.get(partyId=credentialId).partyId.partyType=='phoenix': diff --git a/common/pyTests.py b/common/pyTests.py deleted file mode 100644 index 71d48bf3..00000000 --- a/common/pyTests.py +++ /dev/null @@ -1,138 +0,0 @@ -import requests -import json -import sys, getopt - -from testSamples import CommonApiKeySample -from partner.models import Partner -from apikey.models import ApiKey - -class GenericTest(object): - apiKeySample = CommonApiKeySample() - apiKey = None - def setUp(self): - #delete possible entries that we use as test case - ApiKey.objects.filter(apiKey=self.apiKeySample.data['apiKey']).delete() - self.apiKeyId = self.apiKeySample.forcePost(self.apiKeySample.data) - self.apiKey = self.apiKeySample.data['apiKey'] - - def tearDown(self): - PyTestGenerics.forceDelete(self.apiKeySample.model, self.apiKeySample.pkName, self.apiKeyId) - -# This function checks if sampleData is within the array of data retrieved -# from API call. -def checkMatch(sampleData, retrievedDataArray, pkName, pk): - hasMatch = False - for item in retrievedDataArray: - # find the entry from dataArray that has the same PK - # as the entry created - if item[pkName] == pk: - hasMatch = True - for key in sampleData: - # makes sure that all contents from sample is the - # same as content retrieved from request - if not (item[key] == sampleData[key] or float(item[key])==float(sampleData[key])): - hasMatch = False - break - return hasMatch - -class GenericCRUDTest(GenericTest): - def test_for_create(self): - sample = self.sample - url = sample.url - if self.apiKey: - url = url+'?apiKey=%s' % (self.apiKey) - cookies = {'apiKey':self.apiKey} - req = requests.post(url, data=sample.data, cookies=cookies) - - self.assertEqual(req.status_code, 201) - self.assertIsNotNone(PyTestGenerics.forceGet(sample.model,sample.pkName,req.json()[sample.pkName])) - PyTestGenerics.forceDelete(sample.model,sample.pkName,req.json()[sample.pkName]) - - def test_for_get_all(self): - sample = self.sample - pk = sample.forcePost(sample.data) - url = sample.url - if self.apiKey: - url = url+'?apiKey=%s' % (self.apiKey) - cookies = {'apiKey':self.apiKey} - req = requests.get(url, cookies=cookies) - self.assertEqual(req.status_code, 200) - self.assertEqual(checkMatch(sample.data, req.json(), sample.pkName, pk), True) - - PyTestGenerics.forceDelete(sample.model,sample.pkName,pk) - - def test_for_update(self): - sample = self.sample - pk = sample.forcePost(sample.data) - url = sample.url + '?%s=%s' % (sample.pkName, str(pk)) - if self.apiKey: - url = url+'&apiKey=%s' % (self.apiKey) - cookies = {'apiKey':self.apiKey} - req = requests.put(url, data=sample.updateData,cookies=cookies) - if sample.pkName in sample.updateData: - pk = sample.updateData[sample.pkName] - self.assertEqual(req.status_code, 200) - self.assertEqual(checkMatch(sample.updateData, req.json(), sample.pkName, pk), True) - PyTestGenerics.forceDelete(sample.model,sample.pkName,pk) - - def test_for_delete(self): - sample = self.sample - pk = sample.forcePost(sample.data) - url = sample.url + '?%s=%s' % (sample.pkName, str(pk)) - if self.apiKey: - url = url+'&apiKey=%s' % (self.apiKey) - cookies = {'apiKey':self.apiKey} - req = requests.delete(url, cookies=cookies) - self.assertIsNone(PyTestGenerics.forceGet(sample.model,sample.pkName,pk)) - - def test_for_get(self): - sample = self.sample - pk = sample.forcePost(sample.data) - url = sample.url + '?%s=%s' % (sample.pkName, str(pk)) - if self.apiKey: - url = url+'&apiKey=%s' % (self.apiKey) - cookies = {'apiKey':self.apiKey} - req = requests.get(url, cookies=cookies) - self.assertEqual(req.status_code, 200) - self.assertEqual(checkMatch(sample.data, req.json(), sample.pkName, pk), True) - PyTestGenerics.forceDelete(sample.model,sample.pkName,pk) - -class PyTestGenerics: - @staticmethod - def initPyTest(): - try: - opts, args = getopt.getopt(sys.argv[1:], "h:" , ["host="]) - except getopt.GetoptError: - print "Usage: python -m metering.pyTests --host \n\rExample hostname: 'http://pb.steveatgetexp.com:8080/'" - sys.exit(1) - serverUrl = "" - for opt, arg in opts: - if opt=='--host' or opt=='-h': - serverUrl = arg - if serverUrl=="": - print "hostname is required" - sys.exit(1) - return serverUrl - - - @staticmethod - def forceGet(model, pkName, pk): - try: - filters = {pkName:pk} - return model.objects.get(**filters) - except: - return None - - @staticmethod - def forceDelete(model, pkName,pk): - try: - filters = {pkName:pk} - model.objects.get(**filters).delete() - except: - pass - - @staticmethod - def forcePost(model, pkName, data): - u = model(**data) - u.save() - return u.__dict__[pkName] diff --git a/common/testSamples.py b/common/testSamples.py index 88e7c827..6fcad5c4 100644 --- a/common/testSamples.py +++ b/common/testSamples.py @@ -1,6 +1,9 @@ from apikey.models import ApiKey from partner.models import Partner +from party.models import Party +from authentication.models import Credential import copy +import hashlib def forcePost(model, pkName, data): u = model(**data) @@ -16,3 +19,98 @@ class CommonApiKeySample(): def forcePost(self,data): return forcePost(self.model, self.pkName, data) + +# a template sample class for all the test case sample classes +class GenericSample(): + url = None + path = 'path' + data = { + 'key':'value' + } + pkName = '' + model = Party # just a placeholder, need to be the corresponding ClassName + + def __init__(self, serverUrl): + self.url = serverUrl+self.path + + def forcePost(self,data): + return forcePost(self.model, self.pkName, data) + +class CommonPartnerSample(): + url = None + path = 'partners/' + data = { + 'partnerId':'phoenix', + 'name':'PhoenixBio', + } + pkName = 'partnerId' + model = Partner + + def __init__(self, serverUrl): + self.url = serverUrl + self.path + + def forcePost(self,data): + return forcePost(self.model, self.pkName, data) + +class CommonUserPartySample(): + path = 'parties/' + url = None + data = { + 'partyType':'user', + 'name':'phoenix_admin', + } + pkName = 'partyId' + model = Party + + def __init__(self, serverUrl): + self.url = serverUrl+self.path + + def forcePost(self,data): + return forcePost(self.model, self.pkName, data) + +class CommonCredentialSample(): + url = None + path = 'credentials/' + USERNAME = 'phoenix_admin' + FIRSTNAME = 'Phoenix' + LASTNAME = 'Bioinformatics' + PASSWORD = 'phoenix123' + EMAIL = 'info@phoenixbioinformatics.org' + INSTITUTION = 'Phoenix Bioinformatics' + USER_IDENTIFIER = 1 + data = { + 'username': USERNAME, + 'firstName': FIRSTNAME, + 'lastName': LASTNAME, + 'password': PASSWORD, + 'email': EMAIL, + 'institution': INSTITUTION, + 'userIdentifier': USER_IDENTIFIER, + 'partnerId': None, + 'partyId': None + } + pkName = 'id' + model = Credential + + def __init__(self, serverUrl): + self.url = serverUrl + self.path + + def setPartnerId(self, partnerId): + self.data['partnerId'] = partnerId + + def setPartyId(self, partyId): + self.data['partyId'] = partyId + + def forcePost(self,data): + postData = copy.deepcopy(data) + postData['partyId'] = Party.objects.get(partyId=self.data['partyId']) + postData['partnerId'] = Partner.objects.get(partnerId=self.data['partnerId']) + postData['password'] = self.hashPassword(self.data['password']) + return forcePost(self.model, self.pkName, postData) + + def hashPassword(self, password): + return Credential.generatePasswordHash(password) + + def getSecretKey(self): + # this has dependency on authentication.views regarding argument + return Credential.generateSecretKey(self.data['partyId'], self.hashPassword(self.data['password'])) diff --git a/common/tests.py b/common/tests.py new file mode 100644 index 00000000..9d753c86 --- /dev/null +++ b/common/tests.py @@ -0,0 +1,253 @@ +import json +import sys +import pytz +from datetime import datetime +import urllib.request, urllib.parse, urllib.error +from .testSamples import CommonApiKeySample, CommonPartnerSample, CommonUserPartySample, CommonCredentialSample, GenericSample +from partner.models import Partner +from apikey.models import ApiKey +from django.test import Client +from http.cookies import SimpleCookie + +class TestGenericInterfaces: + @staticmethod + def getHost(): + return "/" + + @staticmethod + def forceGet(model, pkName, pk): + try: + filters = {pkName:pk} + return model.objects.get(**filters) + except: + return None + + @staticmethod + def forceDelete(model, pkName,pk): + try: + filters = {pkName:pk} + model.objects.get(**filters).delete() + except: + pass + + @staticmethod + def forcePost(model, pkName, data): + u = model(**data) + u.save() + return u.__dict__[pkName] + +class GenericTest(object): + apiKeySample = CommonApiKeySample() + apiKey = None + + def setUp(self): + self.apiKeyId = self.apiKeySample.forcePost(self.apiKeySample.data) + self.apiKey = self.apiKeySample.data['apiKey'] + + def getUrl(self, url, pkName = None, pk = None): + fullUrl = url + if pkName and pk: + fullUrl = url + '?%s=%s' % (pkName, str(pk)) + return fullUrl + +# create a credential record to login for every test +class LoginRequiredTest(GenericTest): + credentialId = None + secretKey = None + + def setUp(self): + super(LoginRequiredTest, self).setUp() + serverUrl = TestGenericInterfaces.getHost() + credentialSample = CommonCredentialSample(serverUrl) + + partnerSample = CommonPartnerSample(serverUrl) + partnerId = partnerSample.forcePost(partnerSample.data) + credentialSample.setPartnerId(partnerId) + + userPartySample = CommonUserPartySample(serverUrl) + partyId = userPartySample.forcePost(userPartySample.data) + credentialSample.setPartyId(partyId) + + credentialSample.forcePost(credentialSample.data) + + self.credentialId = partyId + self.secretKey = credentialSample.getSecretKey() + + def getUrl(self, url, pkName = None, pk = None): + secretKey = urllib.parse.quote(self.secretKey) + fullUrl = url + '?credentialId=%s&secretKey=%s' % (self.credentialId, secretKey) + if pkName and pk: + fullUrl = '%s&%s=%s' % (fullUrl, pkName, str(pk)) + return fullUrl + +# This function checks if sampleData is within the array of data retrieved +# from API call +def checkMatch(sampleData, retrievedData, pkName, pk): + hasMatch = False + if not isinstance(retrievedData, list): + retrievedData = [retrievedData] + for item in retrievedData: + # find the entry from dataArray that has the same PK + # as the entry created + if item[pkName] == pk: + hasMatch = True + for key in sampleData: + # makes sure that all contents from sample is the + # same as content retrieved from request + if not key in item or not (checkValueMatch(sampleData[key], item[key])): + hasMatch = False + break + if not hasMatch: + print("\nERROR: sample data %s and retrieved data %s does not match" % (sampleData, retrievedData)) + return hasMatch + +# This function checks if sampleData is within the array of data retrieved +# from the database for the same pk +def checkMatchDB(sampleData, model, pkName, pk): + dbRecord = TestGenericInterfaces.forceGet(model, pkName, pk) + if not dbRecord: + if not sampleData: + return True + print("\nERROR: cannot find database record %s with %s = %s" % (model, pkName, pk)) + return False + hasMatch = True + for key in sampleData: + # makes sure that all keys from sample have the same value as their + # corresponding fields in the db record if exist + if (key in dbRecord.__dict__ and not checkValueMatch(sampleData[key], dbRecord.__dict__[key])): + print("\nERROR: sample data %s:%s and database record %s:%s does not match" % (key, sampleData[key], key, dbRecord.__dict__[key])) + hasMatch = False + break + if not hasMatch: + print("\nERROR: sample data %s and database record %s does not match" % (sampleData, dbRecord.__dict__)) + return hasMatch + +def checkValueMatch(feedValue, dbValue): + if isinstance(feedValue, datetime): + if isinstance(dbValue, datetime): + return feedValue == dbValue + else: + return feedValue == getDateTime(dbValue) + else: + if isinstance(dbValue, datetime): + return getDateTime(feedValue) == dbValue + return feedValue == dbValue or float(feedValue) == float(dbValue) + +def getDateTime(str): + try: + return datetime.strptime(str, '%Y-%m-%dT%H:%M:%S.%fZ').replace(tzinfo=pytz.UTC) + except ValueError: + try: + return datetime.strptime(str, '%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=pytz.UTC) + except ValueError: + return None + +## This function checks if sampleData is within the array of data retrieved +# from API call, and skip common key we use for authentication, such as apiKey, +# credential, party etc. +def filterAndCheckMatch(sampleData, retrievedDataArray, pkName, pk, commonKeyName, commonKeyValue): + filteredArray = [] + for item in retrievedDataArray: + if item[commonKeyName] == commonKeyValue: + continue; + else: + filteredArray.append(item) + return checkMatch(sampleData, filteredArray, pkName, pk) + +class GenericGETOnlyTest(GenericTest): + + def test_for_get_all(self): + sample = self.sample + url = self.getUrl(sample.url) + self.getAllHelper(url) + + def test_for_get(self): + sample = self.sample + pk = sample.forcePost(sample.data) + url = self.getUrl(sample.url, sample.pkName, pk) + if self.apiKey: + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + self.assertEqual(checkMatch(sample.data, json.loads(res.content), sample.pkName, pk), True) + + def getAllHelper(self, url, commonKeyName = None, commonKeyValue = None): + sample = self.sample + pk = sample.forcePost(sample.data) + if self.apiKey: + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + + res = self.client.get(url) + self.assertEqual(res.status_code, 200) + if commonKeyName and commonKeyValue: + self.assertEqual(filterAndCheckMatch(sample.data, json.loads(res.content), sample.pkName, pk, commonKeyName, commonKeyValue), True) + else: + self.assertEqual(checkMatch(sample.data, json.loads(res.content), sample.pkName, pk), True) + +class GenericCRUDTest(GenericGETOnlyTest): + sample = GenericSample(serverUrl) + + # GET tests defined in GenericGETOnlyTest class + + def test_for_create(self): + sample = self.sample + url = self.getUrl(sample.url) + if self.apiKey: + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + + res = self.client.post(url, sample.data) + + self.assertEqual(res.status_code, 201) + pk = json.loads(res.content)[sample.pkName] + self.assertEqual(checkMatchDB(sample.data, sample.model, sample.pkName, pk), True) + + def test_for_update(self): + sample = self.sample + pk = sample.forcePost(sample.data) + url = self.getUrl(sample.url, sample.pkName, pk) + if sample.pkName in sample.updateData: + pk = sample.updateData[sample.pkName] + if self.apiKey: + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + + # the default content type for put is 'application/octet-stream' + res = self.client.put(url, json.dumps(sample.updateData), content_type='application/json') + + self.assertEqual(res.status_code, 200) + self.assertEqual(checkMatch(sample.updateData, json.loads(res.content), sample.pkName, pk), True) + self.assertEqual(checkMatchDB(sample.updateData, sample.model, sample.pkName, pk), True) + + def test_for_delete(self): + sample = self.sample + pk = sample.forcePost(sample.data) + url = self.getUrl(sample.url, sample.pkName, pk) + if self.apiKey: + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + + res = self.client.delete(url) + + self.assertIsNone(TestGenericInterfaces.forceGet(sample.model,sample.pkName,pk)) + +class LoginRequiredGETOnlyTest(LoginRequiredTest, GenericGETOnlyTest): + # just inherit methods from two parent classes + pass + +class LoginRequiredCRUDTest(LoginRequiredTest, GenericCRUDTest): + # just inherit methods from two parent classes + pass + +class ManualTest(object): + path = "" + testMethodStr = "" + + def test_warning(self): + print("\n----------------------------------------------------------------------") + print("\nWARNING: Please manually test API end point %s if necessary.\n\ + If you've \n\ + (1) upgraded Python version or\n\ + (2) upgraded Django version or\n\ + (3) updated module or setting params related to this end point\n\ + Please make sure you test this end point by %s" % (self.path, self.testMethodStr)) + print("\n----------------------------------------------------------------------") \ No newline at end of file diff --git a/common/utils/__init__.py b/common/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/common/utils/test_runner.py b/common/utils/test_runner.py new file mode 100644 index 00000000..98a1f020 --- /dev/null +++ b/common/utils/test_runner.py @@ -0,0 +1,12 @@ +import logging + +from django.test.runner import DiscoverRunner +from django.conf import settings + +class NoLoggingTestRunner(DiscoverRunner): + def run_tests(self, test_labels, extra_tests=None, **kwargs): + + # Don't show logging messages while testing + logging.disable(logging.CRITICAL) + + return super(NoLoggingTestRunner, self).run_tests(test_labels, extra_tests, **kwargs) diff --git a/common/views.py b/common/views.py index 59b59f33..96392950 100644 --- a/common/views.py +++ b/common/views.py @@ -1,5 +1,6 @@ from rest_framework.response import Response from rest_framework import generics, status +from django.db import models class GenericCRUDView(generics.GenericAPIView): requireApiKey = True @@ -7,13 +8,19 @@ def get_queryset(self): queryset = super(GenericCRUDView, self).get_queryset() params = self.request.GET for key in params: - if key in queryset.model._meta.get_all_field_names(): + # fully backward compatiable version on get_fields method can be found here: + # https://docs.djangoproject.com/en/1.10/ref/models/meta/#migrating-from-the-old-api + try: + f = queryset.model._meta.get_field(key) value = params[key] filters = {key:value} try: queryset = queryset.filter(**filters) except ValueError: return [] + except models.FieldDoesNotExist: + # do nothing, continue to check next key + continue return queryset def get(self, request, format=None): @@ -30,13 +37,27 @@ def put(self, request, format=None): if not params: return Response({'error':'does not allow update without query parameters'}) obj = self.get_queryset() - ret = [] + errorRes = [] + serializerList = [] + valid = True for entry in obj: serializer = serializer_class(entry, data=request.data) - if serializer.is_valid(): - serializer.save() - ret.append(serializer.data) - return Response(ret) + if not serializer.is_valid(): + valid = False + errorRes.append(serializer.errors) + else: + serializerList.append(serializer) + + if not valid: + return Response(errorRes, status=status.HTTP_400_BAD_REQUEST) + + successRes = [] + + for serializer in serializerList: + serializer.save() + successRes.append(serializer.data) + + return Response(successRes) def post(self, request, format=None): serializer_class = self.get_serializer_class() diff --git a/dependencies.list b/dependencies.list new file mode 100644 index 00000000..366ef5d4 --- /dev/null +++ b/dependencies.list @@ -0,0 +1,14 @@ +Django==2.2.5 +djangorestframework==3.10.2 +djangorestframework-jwt==1.11.0 +django-rest-swagger==2.2.0 +django-cors-headers==3.0.2 +ipaddress==1.0.22 +mysql-connector-python==8.0.17 +netaddr==0.7.19 +stripe==2.58.0 +sqlparse==0.3.1 +pandas==0.24.2 +xlsxwriter==2.0.0 +xlrd==2.0.1 +pycryptodome==3.15.0 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..89c90b16 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3' + +services: + paywall-api-python3: + build: . + image: paywall-api-python3:1.0 + platform: linux/amd64 + container_name: paywall-api-container-python3 + volumes: + - ".:/var/www/api-python" + ports: + - "9001:80" + diff --git a/docker_config/vhosts.conf b/docker_config/vhosts.conf new file mode 100644 index 00000000..8fe32702 --- /dev/null +++ b/docker_config/vhosts.conf @@ -0,0 +1,36 @@ +#top level folder of the django project +WSGIPythonPath /var/www/api-python + +NameVirtualHost *:80 + + + ServerName 172.* + WSGIScriptAlias / /var/www/api-python/paywall2/wsgi.py + DocumentRoot /var/www/api-python + + AllowOverride None + Require all granted + + + Options Indexes FollowSymLinks + AllowOverride None + + + + + Order allow,deny + Allow from all + + + + Alias /static /usr/local/lib/python3.6/site-packages/rest_framework/static + + + Order allow,deny + Allow from all + + + + ErrorLog /var/log/httpd/api.error_log + CustomLog /var/log/httpd/api.access_log combined + \ No newline at end of file diff --git a/docker_config/wsgi.conf b/docker_config/wsgi.conf new file mode 100644 index 00000000..19f35675 --- /dev/null +++ b/docker_config/wsgi.conf @@ -0,0 +1 @@ +LoadModule wsgi_module modules/mod_wsgi.so diff --git a/ipranges/tests.py b/ipranges/tests.py index 7ce503c2..7d959cf4 100644 --- a/ipranges/tests.py +++ b/ipranges/tests.py @@ -1,3 +1,73 @@ +import django +import unittest +import sys +import json + from django.test import TestCase +from common.tests import TestGenericInterfaces, GenericTest +from http.cookies import SimpleCookie # Create your tests here. +django.setup() +serverUrl = TestGenericInterfaces.getHost() + +# test for API end point /ipranges/validateip +class ValidateIpTest(GenericTest, TestCase): + VALID_PUBLIC_IPV4 = '74.9.8.38' + VALID_PRIVATE_IPV4 = '172.16.5.0' + VALID_IPV6_FULL = '2001:0db8:0000:0000:0000:8a2e:0370:7334' + VALID_IPV6_SIMPLIFIED = '2001:db8::8a2e:370:7334' + INVALID_IP_I = '67.2.6' + INVALID_IP_II = '123.276.4.1' + INVALID_IP_III = '124.89.7.65.1' + INVALID_IP_IV = 'random_string' + INVALID_IP_V = 99999 + + def test_for_get(self): + self.assert_for_valid_ip(self.VALID_PUBLIC_IPV4, 4) + self.assert_for_valid_ip(self.VALID_PRIVATE_IPV4, 4) + self.assert_for_valid_ip(self.VALID_IPV6_FULL, 6) + self.assert_for_valid_ip(self.VALID_IPV6_SIMPLIFIED, 6) + self.assert_for_invalid_ip(self.INVALID_IP_I) + self.assert_for_invalid_ip(self.INVALID_IP_II) + self.assert_for_invalid_ip(self.INVALID_IP_III) + self.assert_for_invalid_ip(self.INVALID_IP_IV) + self.assert_for_invalid_ip(self.INVALID_IP_V) + + def assert_for_valid_ip(self, ip, ipVersion): + res = self.get_response(ip) + + self.assertEqual(res.status_code, 200) + # The raw response will be bytes so need to convert to string and then compare + self.assertEqual(res.content.decode(), '{"ip version": %s}' % ipVersion) + + def assert_for_invalid_ip(self, ip): + res = self.get_response(ip) + + self.assertEqual(res.status_code, 200) + # The raw response will be bytes so need to convert to string and then compare + self.assertEqual(res.content.decode(), '{"ip": "invalid"}') + + # TODO: find example that can trigger this response + def assert_for_error_input(self, ip): + res = self.get_response(ip) + + self.assertEqual(res.status_code, 200) + # The raw response will be bytes so need to convert to string and then compare + self.assertEqual(res.content.decode(), '{"ip": "error"}') + + def get_response(self, ip): + url = '%sipranges/validateip/?ip=%s' % (serverUrl, ip) + + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + res = self.client.get(url) + + return res + +print("Running unit tests on IP range web services API.........") + +if __name__ == '__main__': + sys.argv[1:] = [] + unittest.main() + ret = not runner.run(suite).wasSuccessful() + sys.exit(ret) \ No newline at end of file diff --git a/loggingapp/migrations/0001_initial.py b/loggingapp/migrations/0001_initial.py index 0ef91eb0..7e11c772 100644 --- a/loggingapp/migrations/0001_initial.py +++ b/loggingapp/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations import datetime @@ -19,7 +19,7 @@ class Migration(migrations.Migration): ('uri', models.CharField(max_length=250)), ('pageViewDate', models.DateTimeField(default=datetime.datetime.utcnow)), ('sessionId', models.CharField(max_length=250)), - ('partyId', models.ForeignKey(db_column=b'partyId', to='party.Party', null=True)), + ('partyId', models.ForeignKey(db_column='partyId', to='party.Party', null=True, on_delete=models.PROTECT)), ], options={ 'db_table': 'PageView', diff --git a/loggingapp/migrations/0002_pageview_ip.py b/loggingapp/migrations/0002_pageview_ip.py index 742ca3d4..09b62485 100644 --- a/loggingapp/migrations/0002_pageview_ip.py +++ b/loggingapp/migrations/0002_pageview_ip.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/loggingapp/migrations/0003_PageView_NullableSessionId.py b/loggingapp/migrations/0003_PageView_NullableSessionId.py index e9c70e25..29d7c204 100644 --- a/loggingapp/migrations/0003_PageView_NullableSessionId.py +++ b/loggingapp/migrations/0003_PageView_NullableSessionId.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/loggingapp/migrations/0004_increase_pageview_uri_length.py b/loggingapp/migrations/0004_increase_pageview_uri_length.py index ef48bc84..142ac769 100644 --- a/loggingapp/migrations/0004_increase_pageview_uri_length.py +++ b/loggingapp/migrations/0004_increase_pageview_uri_length.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/loggingapp/migrations/0005_added_3_fields.py b/loggingapp/migrations/0005_added_3_fields.py index 555cfe25..4f646228 100644 --- a/loggingapp/migrations/0005_added_3_fields.py +++ b/loggingapp/migrations/0005_added_3_fields.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -20,11 +20,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name='pageview', name='isPaidContent', - field=models.NullBooleanField(), + field=models.BooleanField(null=True), ), migrations.AddField( model_name='pageview', name='partnerId', - field=models.ForeignKey(db_column=b'partnerId', to='partner.Partner', null=True), + field=models.ForeignKey(db_column='partnerId', to='partner.Partner', null=True, on_delete=models.PROTECT), ), ] diff --git a/loggingapp/migrations/0006_pageview_new_fields_PHX-497.py b/loggingapp/migrations/0006_pageview_new_fields_PHX-497.py index 54f94c5e..4d476796 100644 --- a/loggingapp/migrations/0006_pageview_new_fields_PHX-497.py +++ b/loggingapp/migrations/0006_pageview_new_fields_PHX-497.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/loggingapp/migrations/0007_choices_meterstatus_PHX-497.py b/loggingapp/migrations/0007_choices_meterstatus_PHX-497.py index 26642c42..50d120d2 100644 --- a/loggingapp/migrations/0007_choices_meterstatus_PHX-497.py +++ b/loggingapp/migrations/0007_choices_meterstatus_PHX-497.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -14,6 +14,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='pageview', name='meterStatus', - field=models.CharField(max_length=1, null=True, choices=[(b'W', b'Warning'), (b'B', b'Block'), (b'M', b'Must subscribe'), (b'N', b'Not metered')]), + field=models.CharField(max_length=1, null=True, choices=[('W', 'Warning'), ('B', 'Block'), ('M', 'Must subscribe'), ('N', 'Not metered')]), ), ] diff --git a/loggingapp/models.py b/loggingapp/models.py index e3b894f4..e5344d0d 100644 --- a/loggingapp/models.py +++ b/loggingapp/models.py @@ -9,13 +9,13 @@ class PageView(models.Model): pageViewId = models.AutoField(primary_key=True) uri = models.CharField(max_length=2000) - partyId = models.ForeignKey('party.Party', db_column='partyId', null=True) + partyId = models.ForeignKey('party.Party', db_column='partyId', null=True, on_delete=models.PROTECT) pageViewDate = models.DateTimeField(default=datetime.datetime.utcnow) sessionId = models.CharField(max_length=250, null=True) ip = models.GenericIPAddressField(max_length=200) ipList = models.CharField(max_length=250, null=True) - partnerId = models.ForeignKey(Partner, db_column='partnerId', null=True) - isPaidContent = models.NullBooleanField() + partnerId = models.ForeignKey(Partner, db_column='partnerId', null=True, on_delete=models.PROTECT) + isPaidContent = models.BooleanField(null=True) # meter choices METER_WARNING_STATUS = 'W' diff --git a/loggingapp/pyTests.py b/loggingapp/pyTests.py deleted file mode 100644 index 7b272afc..00000000 --- a/loggingapp/pyTests.py +++ /dev/null @@ -1,103 +0,0 @@ -#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. - -import django -import unittest -import sys, getopt -from unittest import TestCase -from party.models import Party -from partner.models import Partner -from loggingapp.models import PageView - -from loggingapp.testSamples import PageViewSample -from party.testSamples import PartySample -from partner.testSamples import PartnerSample -import requests -import json -from common.pyTests import PyTestGenerics, GenericCRUDTest, GenericTest - -from rest_framework import status - - -# Create your tests here. -django.setup() -serverUrl = PyTestGenerics.initPyTest() -print "using server url %s" % serverUrl - -# ---------------------- UNIT TEST FOR BASIC CRUD OPERATIONS ------------- - -class PageViewCRUD(GenericCRUDTest, TestCase): - sample = PageViewSample(serverUrl) - partySample = PartySample(serverUrl) - - def setUp(self): - super(PageViewCRUD, self).setUp() - self.partyId = self.partySample.forcePost(self.partySample.data) - self.sample.data['partyId']=self.sample.updateData['partyId']=self.partyId - - # update and delete are not allowed in this CRUD - def test_for_update(self): - pass - - def test_for_delete(self): - pass - - def tearDown(self): - super(PageViewCRUD, self).tearDown() - PyTestGenerics.forceDelete(self.partySample.model, self.partySample.pkName, self.sample.data['partyId']) - -# ----------------- END OF BASIC CRUD OPERATIONS ---------------------- - -class SessionCountViewTest(GenericTest, TestCase): - url = serverUrl + 'session-logs/sessions/counts/' - # sessionId, pageViewDate - sampleData = [ - (1, '1979-01-01T00:00:00Z'), - (1, '1980-01-01T00:00:00Z'), - (1, '1981-01-01T00:00:00Z'), - (1, '1982-01-01T00:00:00Z'), - (2, '1980-01-01T00:00:00Z'), - (3, '1980-01-01T00:00:00Z'), - (3, '1981-01-01T00:00:00Z'), - ] - pageViewIdList = [] - sample = PageViewSample(serverUrl) - partySample = PartySample(serverUrl) - def setUp(self): - super(SessionCountViewTest, self).setUp() - self.partyId = self.partySample.forcePost(self.partySample.data) - self.sample.data['partyId']=self.sample.updateData['partyId']=self.partyId - for entry in self.sampleData: - self.sample.data['sessionId'] = str(entry[0]) - self.sample.data['pageViewDate'] = entry[1] - self.pageViewIdList.append(self.sample.forcePost(self.sample.data)) - - def runTest(self, startDate, endDate, expectedCount): - url = self.url + '?apiKey=%s' % self.apiKey - if startDate: - url += '&startDate=%s' % startDate - if endDate: - url += '&endDate=%s' % endDate - cookies = {'apiKey':self.apiKey} - req = requests.get(url,cookies=cookies) - self.assertEqual(req.status_code, 200) - count = req.json()['count'] - self.assertEqual(count, expectedCount) - - def test_for_get(self): - self.runTest('1978-05-01T00:00:00Z', '1979-05-01T00:00:00Z', 1) #only sessionId 1 - self.runTest('1980-05-01T00:00:00Z', '1981-05-01T00:00:00Z', 2) #sessionId 1, 3 - self.runTest('1979-05-01T00:00:00Z', '1980-05-01T00:00:00Z', 3) #sessionId 1, 2, and 3 - - def tearDown(self): - super(SessionCountViewTest, self).setUp() - PyTestGenerics.forceDelete(self.partySample.model, self.partySample.pkName, self.sample.data['partyId']) - for entry in self.pageViewIdList: - PyTestGenerics.forceDelete(self.sample.model, self.sample.pkName, entry) - -print "Running unit tests on party web services API........." - -if __name__ == '__main__': - sys.argv[1:] = [] - unittest.main() - ret = not runner.run(suite).wasSuccessful() - sys.exit(ret) diff --git a/loggingapp/testSamples.py b/loggingapp/testSamples.py index 0f14c699..4b0fed5f 100644 --- a/loggingapp/testSamples.py +++ b/loggingapp/testSamples.py @@ -1,35 +1,45 @@ import django import unittest -import sys, getopt -from unittest import TestCase -from subscription.models import Subscription, SubscriptionTransaction +import sys +import copy from loggingapp.models import PageView from party.models import Party from partner.models import Partner -import requests -import json - -import copy -from common.pyTests import PyTestGenerics +from django.test import TestCase +from common.tests import TestGenericInterfaces -genericForcePost = PyTestGenerics.forcePost +genericForcePost = TestGenericInterfaces.forcePost class PageViewSample(): + SESSION_ID_I = '9A417B49536A8395936C4717E80E005C' + SESSION_ID_II = 'BC71B65BF137AD7924600A00D3F03C1C' + SESSION_ID_III = '51AD64E5EDEF1C59CAF5C30EFA2F4783' path = 'session-logs/page-views/' url = None + data = { - 'uri':'test123', + 'uri':'test_url_1', 'partyId':None, - 'pageViewDate':'2013-08-31T00:00:00Z', - 'sessionId':'abcdefg', + 'partnerId':None, + 'pageViewDate':'2018-08-31T00:00:00Z', + 'sessionId':SESSION_ID_I, 'ip':'123.45.67.8', + 'ipList':'111.11.22.1, 123.45.67.8', + 'isPaidContent':False, + 'meterStatus':PageView.METER_NOT_METERED_STATUS + } updateData = { - 'uri':'test234', + 'uri':'test_url_2', 'partyId':None, - 'pageViewDate':'2020-08-31T00:00:00Z', - 'sessionId':'efghijk', + 'partnerId':None, + 'pageViewDate':'2019-08-31T17:34:26Z', + 'sessionId':SESSION_ID_II, 'ip':'12.34.5.67', + 'ipList':'111.11.22.23, 123.45.67.8, 12.34.5.67', + 'isPaidContent':True, + 'meterStatus':PageView.METER_BLOCK_STATUS + } pkName = 'pageViewId' model = PageView @@ -40,6 +50,7 @@ def __init__(self, serverUrl): def forcePost(self,data): postData = copy.deepcopy(data) postData['partyId'] = Party.objects.get(partyId=data['partyId']) + postData['partnerId'] = Partner.objects.get(partnerId=data['partnerId']) return genericForcePost(self.model, self.pkName, postData) diff --git a/loggingapp/tests.py b/loggingapp/tests.py index 7ce503c2..52678506 100644 --- a/loggingapp/tests.py +++ b/loggingapp/tests.py @@ -1,3 +1,100 @@ -from django.test import TestCase +#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. -# Create your tests here. +import django +import unittest +import sys, getopt +import json +import copy +from django.test import TestCase, Client +from partner.testSamples import PartnerSample +from party.testSamples import UserPartySample +from common.tests import TestGenericInterfaces, GenericCRUDTest, GenericTest +from .testSamples import PageViewSample +from http.cookies import SimpleCookie + +# Create your tests here. +django.setup() +serverUrl = TestGenericInterfaces.getHost() + +# ---------------------- UNIT TEST FOR BASIC CRUD OPERATIONS ------------- + +# test for API end point /session-logs/page-views/ +class PageViewCRUD(GenericCRUDTest, TestCase): + sample = PageViewSample(serverUrl) + partySample = UserPartySample(serverUrl) + partnerSample = PartnerSample(serverUrl); + + def setUp(self): + super(PageViewCRUD, self).setUp() + self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) + self.partyId = self.partySample.forcePost(self.partySample.data) + self.sample.data['partnerId']=self.sample.updateData['partnerId']=self.partnerId + self.sample.data['partyId']=self.sample.updateData['partyId']=self.partyId + + # update and delete are not allowed in this CRUD + def test_for_update(self): + pass + + def test_for_delete(self): + pass + +# ----------------- END OF BASIC CRUD OPERATIONS ---------------------- + +# test for API end point /session-logs/sessions/counts/ +class SessionCountViewTest(GenericTest, TestCase): + url = serverUrl + 'session-logs/sessions/counts/' + + # sessionId, pageViewDate + sampleData = [ + (PageViewSample.SESSION_ID_I, '1980-01-01T00:35:00Z'), + (PageViewSample.SESSION_ID_I, '1980-01-01T00:55:00Z'), + (PageViewSample.SESSION_ID_I, '1980-01-01T00:57:00Z'), + (PageViewSample.SESSION_ID_I, '1980-01-01T01:04:00Z'), + (PageViewSample.SESSION_ID_II, '1981-01-01T00:00:00Z'), + (PageViewSample.SESSION_ID_III, '1982-01-01T00:00:00Z'), + (PageViewSample.SESSION_ID_III, '1982-01-01T00:02:00Z'), + ] + pageViewIdList = [] + sample = PageViewSample(serverUrl) + partySample = UserPartySample(serverUrl) + partnerSample = PartnerSample(serverUrl) + + def setUp(self): + super(SessionCountViewTest, self).setUp() + self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) + self.partyId = self.partySample.forcePost(self.partySample.data) + self.sample.data['partnerId']=self.partnerId + self.sample.data['partyId']=self.partyId + for entry in self.sampleData: + self.sample.data['sessionId'] = entry[0] + self.sample.data['pageViewDate'] = entry[1] + self.pageViewIdList.append(self.sample.forcePost(self.sample.data)) + + def test_for_get(self): + self.runTest('1979-05-01T00:00:00Z', '1980-05-01T00:00:00Z', 1) #only session I + self.runTest('1979-05-01T00:00:00Z', '1981-05-01T00:00:00Z', 2) #session I,II + self.runTest('1979-05-01T00:00:00Z', '1982-05-01T00:00:00Z', 3) #sessionId I, II and III + + def runTest(self, startDate, endDate, expectedCount): + url = self.url + '?apiKey=%s' % self.apiKey + if startDate: + url += '&startDate=%s' % startDate + if endDate: + url += '&endDate=%s' % endDate + + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + count = json.loads(res.content)['count'] + self.assertEqual(count, expectedCount) + +# skipped testing for /session-logs/page-views/csv/ since no external resource uses its + +print("Running unit tests on session logs web services API.........") + +if __name__ == '__main__': + sys.argv[1:] = [] + unittest.main() + ret = not runner.run(suite).wasSuccessful() + sys.exit(ret) diff --git a/metering/migrations/0001_initial.py b/metering/migrations/0001_initial.py index d5ee29ac..b0236b8b 100644 --- a/metering/migrations/0001_initial.py +++ b/metering/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -17,7 +17,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('ip', models.GenericIPAddressField(db_index=True)), ('count', models.IntegerField(default=1)), - ('partnerId', models.ForeignKey(to='partner.Partner', db_column=b'partnerId')), + ('partnerId', models.ForeignKey(to='partner.Partner', db_column='partnerId', on_delete=models.PROTECT)), ], options={ 'db_table': 'IPAddressCount', @@ -28,7 +28,7 @@ class Migration(migrations.Migration): fields=[ ('limitId', models.AutoField(serialize=False, primary_key=True)), ('val', models.IntegerField()), - ('partnerId', models.ForeignKey(to='partner.Partner', db_column=b'partnerId')), + ('partnerId', models.ForeignKey(to='partner.Partner', db_column='partnerId', on_delete=models.PROTECT)), ], options={ 'db_table': 'LimitValue', diff --git a/metering/migrations/0002_LimitValue_tbl_add_pattern_column_PW-287.py b/metering/migrations/0002_LimitValue_tbl_add_pattern_column_PW-287.py index ffee22d7..4232039d 100644 --- a/metering/migrations/0002_LimitValue_tbl_add_pattern_column_PW-287.py +++ b/metering/migrations/0002_LimitValue_tbl_add_pattern_column_PW-287.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models @@ -14,6 +14,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='limitvalue', name='pattern', - field=models.CharField(default=b'', max_length=5000), + field=models.CharField(default='', max_length=5000), ), ] diff --git a/metering/migrations/0003_MeterBlacklist_newTable_PW287.py b/metering/migrations/0003_MeterBlacklist_newTable_PW287.py index eb784c8b..7ac74483 100644 --- a/metering/migrations/0003_MeterBlacklist_newTable_PW287.py +++ b/metering/migrations/0003_MeterBlacklist_newTable_PW287.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models @@ -15,8 +15,8 @@ class Migration(migrations.Migration): name='MeterBlacklist', fields=[ ('meterBlackListId', models.AutoField(serialize=False, primary_key=True)), - ('partnerId', models.CharField(default=b'', max_length=200)), - ('pattern', models.CharField(default=b'', max_length=5000)), + ('partnerId', models.CharField(default='', max_length=200)), + ('pattern', models.CharField(default='', max_length=5000)), ], options={ 'db_table': 'MeterBlacklist', diff --git a/metering/migrations/0004_LimitValue_tbl_drop_patter_columns_PW287.py b/metering/migrations/0004_LimitValue_tbl_drop_patter_columns_PW287.py index 760ca05c..1f5ea60f 100644 --- a/metering/migrations/0004_LimitValue_tbl_drop_patter_columns_PW287.py +++ b/metering/migrations/0004_LimitValue_tbl_drop_patter_columns_PW287.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/metering/models.py b/metering/models.py index 193bf551..2f358267 100644 --- a/metering/models.py +++ b/metering/models.py @@ -6,7 +6,7 @@ class IpAddressCount(models.Model): ip = models.GenericIPAddressField(max_length=200, db_index=True) count = models.IntegerField(default=1) - partnerId = models.ForeignKey(Partner, db_column="partnerId") + partnerId = models.ForeignKey(Partner, db_column="partnerId", on_delete=models.PROTECT) class Meta: db_table = "IPAddressCount" def __str__(self): @@ -15,7 +15,7 @@ def __str__(self): class LimitValue(models.Model): limitId = models.AutoField(primary_key=True) val = models.IntegerField() - partnerId = models.ForeignKey(Partner, db_column="partnerId") + partnerId = models.ForeignKey(Partner, db_column="partnerId", on_delete=models.PROTECT) class Meta: db_table = "LimitValue" def __str__(self): diff --git a/metering/pyTests.py b/metering/pyTests.py deleted file mode 100644 index f66f0a07..00000000 --- a/metering/pyTests.py +++ /dev/null @@ -1,130 +0,0 @@ -#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. - -import django -import unittest -import sys, getopt -from unittest import TestCase -from metering.models import IpAddressCount, LimitValue -from partner.models import Partner -import requests -import json -from testSamples import LimitValueSample, IpAddressCountSample -from partner.testSamples import PartnerSample -import copy -from common.pyTests import PyTestGenerics, GenericCRUDTest, GenericTest -from rest_framework import status - - -# Create your tests here. -django.setup() -serverUrl = PyTestGenerics.initPyTest() -print "using server url %s" % serverUrl - -# ---------------------- UNIT TEST FOR BASIC CRUD OPERATIONS ------------- - -class IpAddressCountCRUD(GenericCRUDTest, TestCase): - - sample = IpAddressCountSample(serverUrl) - partnerSample = PartnerSample(serverUrl) - - def setUp(self): - super(IpAddressCountCRUD,self).setUp() - Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() - self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) - self.sample.partnerId=self.sample.data['partnerId']=self.sample.updateData['partnerId']=self.partnerId - - def tearDown(self): - super(IpAddressCountCRUD,self).tearDown() - PyTestGenerics.forceDelete(self.partnerSample.model, self.partnerSample.pkName, self.partnerId) - -class LimitValueCRUD(GenericCRUDTest, TestCase): - - sample = LimitValueSample(serverUrl) - partnerSample = PartnerSample(serverUrl) - - def setUp(self): - super(LimitValueCRUD,self).setUp() - Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() - self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) - self.sample.partnerId=self.sample.data['partnerId']=self.sample.updateData['partnerId']=self.partnerId - - def tearDown(self): - super(LimitValueCRUD,self).tearDown() - PyTestGenerics.forceDelete(self.partnerSample.model, self.partnerSample.pkName, self.partnerId) - -# ----------------- END OF BASIC CRUD OPERATIONS ---------------------- - -class IncrementMeteringCountTest(GenericTest, TestCase): - ipAddressCountSample = IpAddressCountSample(serverUrl) - limitValueSample = LimitValueSample(serverUrl) - partnerSample = PartnerSample(serverUrl) - def setUp(self): - super(IncrementMeteringCountTest, self).setUp() - Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() - self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) - self.ipAddressCountSample.data['partnerId'] = self.partnerId - self.ipAddressCountSample.data['count'] = 1 - self.ipAddressCountId = self.ipAddressCountSample.forcePost(self.ipAddressCountSample.data) - self.limitValueSample.data['partnerId'] = self.partnerId - self.limitValueId = self.limitValueSample.forcePost(self.limitValueSample.data) - - def test_for_increment(self): - currentCount = self.ipAddressCountSample.data['count'] - url = '%smeters/ip/%s/increment/?partnerId=%s' % (serverUrl, self.ipAddressCountSample.data['ip'], self.partnerId) - cookies = {'apiKey':self.apiKey} - req = requests.post(url, cookies=cookies) - newCount = IpAddressCount.objects.get(id=self.ipAddressCountId).count - self.assertEqual(currentCount+1, newCount) - - def tearDown(self): - super(IncrementMeteringCountTest,self).tearDown() - PyTestGenerics.forceDelete(self.partnerSample.model, self.partnerSample.pkName, self.partnerId) - PyTestGenerics.forceDelete(self.ipAddressCountSample.model, self.ipAddressCountSample.pkName, self.ipAddressCountId) - PyTestGenerics.forceDelete(self.limitValueSample.model, self.limitValueSample.pkName, self.limitValueId) - -class CheckLimitTest(GenericTest, TestCase): - successIpAddressCountSample = IpAddressCountSample(serverUrl) - failIpAddressCountSample = IpAddressCountSample(serverUrl) - limitValueSample = LimitValueSample(serverUrl) - partnerSample = PartnerSample(serverUrl) - successIp = '123.45.6.7' - failIp = '123.45.6.8' - - def setUp(self): - super(CheckLimitTest, self).setUp() - Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() - self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) - self.successIpAddressCountSample.data['partnerId'] = self.partnerId - self.successIpAddressCountSample.data['count'] = 1 - self.successIpAddressCountSample.data['ip'] = self.successIp - self.successIpAddressCountId = self.successIpAddressCountSample.forcePost(self.successIpAddressCountSample.data) - self.failIpAddressCountSample.data['partnerId'] = self.partnerId - self.failIpAddressCountSample.data['ip'] = self.failIp - self.failIpAddressCountSample.data['count'] = 10000 - self.failIpAddressCountId = self.failIpAddressCountSample.forcePost(self.failIpAddressCountSample.data) - self.limitValueSample.data['partnerId'] = self.partnerId - self.limitValueId = self.limitValueSample.forcePost(self.limitValueSample.data) - - def test_for_check_limit(self): - url = '%smeters/ip/%s/limit/?partnerId=%s' % (serverUrl, self.successIp, self.partnerId) - cookies = {'apiKey':self.apiKey} - req = requests.get(url, cookies=cookies) - self.assertEqual(req.json()['status'], 'OK') - url = '%smeters/ip/%s/limit/?partnerId=%s' % (serverUrl, self.failIp, self.partnerId) - req = requests.get(url, cookies=cookies) - self.assertEqual(req.json()['status'], 'Block') - - def tearDown(self): - super(CheckLimitTest,self).tearDown() - PyTestGenerics.forceDelete(self.partnerSample.model, self.partnerSample.pkName, self.partnerId) - PyTestGenerics.forceDelete(self.successIpAddressCountSample.model, self.successIpAddressCountSample.pkName, self.successIpAddressCountId) - PyTestGenerics.forceDelete(self.failIpAddressCountSample.model, self.failIpAddressCountSample.pkName, self.failIpAddressCountId) - PyTestGenerics.forceDelete(self.limitValueSample.model, self.limitValueSample.pkName, self.limitValueId) - -print "Running unit tests on subscription web services API........." - -if __name__ == '__main__': - sys.argv[1:] = [] - unittest.main() - ret = not runner.run(suite).wasSuccessful() - sys.exit(ret) diff --git a/metering/testSamples.py b/metering/testSamples.py index f17f468b..b0b286d6 100644 --- a/metering/testSamples.py +++ b/metering/testSamples.py @@ -1,59 +1,118 @@ import django -import unittest -import sys, getopt -from unittest import TestCase -from metering.models import IpAddressCount, LimitValue +import copy +from metering.models import IpAddressCount, LimitValue, MeterBlacklist from partner.models import Partner -import requests -import json +from common.tests import TestGenericInterfaces -import copy -from common.pyTests import PyTestGenerics +genericForcePost = TestGenericInterfaces.forcePost -genericForcePost = PyTestGenerics.forcePost +class LimitValueSample(): + path = 'meters/limits/' + url = None + UNDER_LIMIT_VAL = 5 + WARNING_LIMIT_VAL = 10 + BLOCKING_LIMIT_VAL = 20 + INCREASE_BLOCKING_LIMIT_VAL = 30 + TYPE_WARNING = 'Warning' + TYPE_EXCEED = 'Block' + data = { + 'val': BLOCKING_LIMIT_VAL, + 'partnerId': None, + } + updateData = { + 'val': INCREASE_BLOCKING_LIMIT_VAL, + 'partnerId': None, + } + pkName = 'limitId' + model = LimitValue + + def __init__(self, serverUrl): + self.url = serverUrl+self.path + # create instance data + self.data = copy.deepcopy(self.data) + + def setLimitVal(self, val): + self.data['val'] = val + + def setPartnerId(self, partnerId): + self.data['partnerId'] = partnerId + + def forcePost(self,data): + postData = copy.deepcopy(data) + postData['partnerId'] = Partner.objects.get(partnerId=data['partnerId']) + return genericForcePost(self.model, self.pkName, postData) class IpAddressCountSample(): path = 'meters/' url = None + UNDER_LIMIT_IP = '123.45.6.7' + WARNING_HIT_IP = '123.45.6.8' + BLOCKING_HIT_IP = '123.45.6.9' data = { - 'ip':'123.45.6.7', - 'count':1, - 'partnerId':None, + 'ip': UNDER_LIMIT_IP, + 'count': LimitValueSample.UNDER_LIMIT_VAL, + 'partnerId': None, } updateData = { - 'ip':'123.45.6.7', - 'count':5, - 'partnerId':None, + 'ip': UNDER_LIMIT_IP, + 'count': LimitValueSample.BLOCKING_LIMIT_VAL, + 'partnerId': None, } pkName = 'id' model = IpAddressCount def __init__(self, serverUrl): self.url = serverUrl+self.path + self.data = copy.deepcopy(self.data) + + def setPartnerId(self, partnerId): + self.data['partnerId'] = partnerId + + def setCount(self, count): + self.data['count'] = count + + def setIp(self, ip): + self.data['ip'] = ip + + def getCount(self): + return self.data['count'] + + def getIp(self): + return self.data['ip'] def forcePost(self,data): postData = copy.deepcopy(data) postData['partnerId'] = Partner.objects.get(partnerId=data['partnerId']) return genericForcePost(self.model, self.pkName, postData) -class LimitValueSample(): - path = 'meters/limits/' +class MeterBlacklistSample(): + path = 'meters/meterblacklist/' url = None + UNBLOCKED_URI = 'test_uri_unblocked' + BLOCKED_URI = 'test_uri_blocked' + BLOCKED_URI_II = 'test_uri_blocked_II' data = { - 'val':8, - 'partnerId':None, + 'pattern': BLOCKED_URI, + 'partnerId': None } updateData = { - 'val':12, - 'partnerId':None, + 'pattern': BLOCKED_URI_II, + 'partnerId': None } - pkName = 'limitId' - model = LimitValue + pkName = 'meterBlackListId' + model = MeterBlacklist def __init__(self, serverUrl): self.url = serverUrl+self.path + self.data = copy.deepcopy(self.data) + + def setPartnerId(self, partnerId): + self.data['partnerId'] = partnerId + + def setPattern(self, pattern): + self.data['pattern'] = pattern def forcePost(self,data): postData = copy.deepcopy(data) - postData['partnerId'] = Partner.objects.get(partnerId=data['partnerId']) + # partnerId for MeterBlacklist model is not foreign key return genericForcePost(self.model, self.pkName, postData) diff --git a/metering/tests.py b/metering/tests.py index e6ec8d66..2e44f11f 100644 --- a/metering/tests.py +++ b/metering/tests.py @@ -1,114 +1,172 @@ -#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. - - - - - -from django.test import TestCase - -from .models import ipAddr -import requests -# Create your tests here. -serverUrl = 'http://52.4.106.144:8080/' -testIP = '786.786.786.786' - -class TestForIpAddr(TestCase): -# def test_for_getIPList(self): -# forcePostIP(testIP) -# req = requests.get(serverUrl+'meters/ip/') -# boolean = False -# for ips in req.json(): -# if ips['ip']==testIP: -# boolean = True -# self.assertEqual(boolean, True) -# forceDeleteIP(testIP) -# -# def test_for_getIPDetail(self): -# forcePostIP(testIP) -# req = requests.get(serverUrl+'meters/ip/'+testIP) -# self.assertEqual(req.status_code, 200) -# forceDeleteIP(testIP) - -# def test_for_deleteIP(self): -# forcePostIP(testIP) -# req = requests.delete(serverUrl+'/meters/ip/'+testIP) -# self.assertEqual(req.status_code, 204) - - def test_for_postIP(self): - data = {'ip': testIP} - req = requests.post(serverUrl+'meters/ip/', data=data) - self.assertEqual(req.status_code, 201) - ip = forceGetIP(testIP) - self.assertEqual(ip, not None) - self.assertEqual(ip.ip, testIP) - self.assertEqual(ip.count, 1) - forceDeleteIP(testIP) - -# def test_for_incrementIP(self): -# forcePostIP(testIP) -# req = requests.get(serverUrl+'/meters/ip/'+testIP+'/increment') -# self.assertEqual(req.status_code, 200) -# ip = forceGetIP(testIP) -# self.assertEqual(ip.count, 2) -# forceDeleteIP(testIP) - -# def test_for_atWarningLimit(self): -# forcePostIP(testIP) -# req = requests.get(serverUrl+'/meters/ip/'+testIP+'/atWarningLimit') -# self.assertEqual(req.json()['bool'], False) -# forceSetIP(testIP, 9) -# req = requests.get(serverUrl+'/meters/ip/'+testIP+'/atWarningLimit') -# self.assertEqual(req.json()['bool'],True) -# forceDeleteIP(testIP) -# -# def test_for_atMeteringLimit(self): -# forcePostIP(testIP) -# req = requests.get(serverUrl+'/meters/ip/'+testIP+'/atMeteringLimit') -# self.assertEqual(req.json()['bool'], False) -# forceSetIP(testIP, 11) -# req = requests.get(serverUrl+'/meters/ip/'+testIP+'/atMeteringLimit') -# self.assertEqual(req.json()['bool'],True) -# forceDeleteIP(testIP) - - -class TestForLimits(TestCase): - def test_for_getWarningLimit(self): - pass - - def test_for_getMeteringLimit(self): - pass - - def test_for_setWarningLimit(self): - pass - - def test_for_setMeteringLimit(self): - pass - - -#Auxillary functions -def forcePostIP(ip): - #try: - # ipAddr.objects.get(ip=ip) - #except: - u = ipAddr(ip=ip, count=1) - u.save() - -def forceDeleteIP(ip): - try: - u = ipAddr.objects.get(ip=ip) - u.delete() - except: - pass - -def forceGetIP(ip): - try: - u = ipAddr.objects.get(ip=ip) - return u - except: - return None - -def forceSetIP(ip, count): - u = ipAddr.objects.get(ip=ip) - u.count = count - u.save() +#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. +import django +import unittest +import sys, getopt +import json +import copy +from django.test import TestCase, Client +from metering.models import IpAddressCount, LimitValue +from partner.testSamples import PartnerSample +from partner.models import Partner +from common.tests import TestGenericInterfaces, GenericCRUDTest, GenericTest +from .testSamples import LimitValueSample, IpAddressCountSample, MeterBlacklistSample +from http.cookies import SimpleCookie + +# Create your tests here. +django.setup() +serverUrl = TestGenericInterfaces.getHost() + +# ---------------------- UNIT TEST FOR BASIC CRUD OPERATIONS ------------- + +# test for API endpoint /meters/ +class IpAddressCountCRUD(GenericCRUDTest, TestCase): + + sample = IpAddressCountSample(serverUrl) + partnerSample = PartnerSample(serverUrl) + + def setUp(self): + super(IpAddressCountCRUD,self).setUp() + Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() + self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) + self.sample.partnerId=self.sample.data['partnerId']=self.sample.updateData['partnerId']=self.partnerId + +# test for API endpoint /meters/limits +class LimitValueCRUD(GenericCRUDTest, TestCase): + + sample = LimitValueSample(serverUrl) + partnerSample = PartnerSample(serverUrl) + + def setUp(self): + super(LimitValueCRUD,self).setUp() + Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() + self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) + self.sample.partnerId=self.sample.data['partnerId']=self.sample.updateData['partnerId']=self.partnerId + +# test for API endpoint /meters/meterblacklist/ +class MeterBlacklistCRUD(GenericCRUDTest, TestCase): + + sample = MeterBlacklistSample(serverUrl) + partnerSample = PartnerSample(serverUrl) + + def setUp(self): + super(MeterBlacklistCRUD,self).setUp() + Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() + self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) + self.sample.partnerId=self.sample.data['partnerId']=self.sample.updateData['partnerId']=self.partnerId + + +# ----------------- END OF BASIC CRUD OPERATIONS ---------------------- + +# test for API endpoint /meters/ip//increment/ +class IncrementMeteringCountTest(GenericTest, TestCase): + client = Client() + + partnerSample = PartnerSample(serverUrl) + limitValueSample = LimitValueSample(serverUrl) + ipAddressCountSample = IpAddressCountSample(serverUrl) + + def setUp(self): + super(IncrementMeteringCountTest, self).setUp() + Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() + self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) + + self.limitValueSample.setPartnerId(self.partnerId) + self.limitValueId = self.limitValueSample.forcePost(self.limitValueSample.data) + + self.ipAddressCountSample.setPartnerId(self.partnerId) + self.ipAddressCountSample.setIp(IpAddressCountSample.UNDER_LIMIT_IP) + self.ipAddressCountSample.setCount(LimitValueSample.UNDER_LIMIT_VAL) + self.ipAddressCountId = self.ipAddressCountSample.forcePost(self.ipAddressCountSample.data) + + def test_for_increment(self): + currentCount = self.ipAddressCountSample.getCount() + # cannot use reverse method since we have ip value in the middle of uri + url = '%smeters/ip/%s/increment/?partnerId=%s' % (serverUrl, self.ipAddressCountSample.getIp(), self.partnerId) + + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + res = self.client.post(url) + + newCount = IpAddressCount.objects.get(id=self.ipAddressCountId).count + self.assertEqual(currentCount+1, newCount) + +# test for API endpoint /meters/ip//limit/ +class CheckLimitTest(GenericTest, TestCase): + client = Client() + + partnerSample = PartnerSample(serverUrl) + + limitValueSampleWarning = LimitValueSample(serverUrl) + limitValueSampleExceed = LimitValueSample(serverUrl) + + successIpAddressCountSample = IpAddressCountSample(serverUrl) + limitWarningIpAddressCountSample = IpAddressCountSample(serverUrl) + limitBlockedIpAddressCountSample = IpAddressCountSample(serverUrl) + + meterBlacklistSample = MeterBlacklistSample(serverUrl) + + def setUp(self): + super(CheckLimitTest, self).setUp() + Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() + self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) + + self.limitValueSampleWarning.setPartnerId(self.partnerId) + self.limitValueSampleWarning.setLimitVal(LimitValueSample.WARNING_LIMIT_VAL) + self.limitValueSampleWarning.forcePost(self.limitValueSampleWarning.data) + + self.limitValueSampleExceed.setPartnerId(self.partnerId) + self.limitValueSampleExceed.setLimitVal(LimitValueSample.BLOCKING_LIMIT_VAL) + self.limitValueSampleExceed.forcePost(self.limitValueSampleExceed.data) + + self.successIpAddressCountSample.setPartnerId(self.partnerId) + self.successIpAddressCountSample.setIp(IpAddressCountSample.UNDER_LIMIT_IP) + self.successIpAddressCountSample.setCount(LimitValueSample.UNDER_LIMIT_VAL) + self.successIpAddressCountSample.forcePost(self.successIpAddressCountSample.data) + + self.limitWarningIpAddressCountSample.setPartnerId(self.partnerId) + self.limitWarningIpAddressCountSample.setIp(IpAddressCountSample.WARNING_HIT_IP) + self.limitWarningIpAddressCountSample.setCount(LimitValueSample.WARNING_LIMIT_VAL) + self.limitWarningIpAddressCountSample.forcePost(self.limitWarningIpAddressCountSample.data) + + self.limitBlockedIpAddressCountSample.setPartnerId(self.partnerId) + self.limitBlockedIpAddressCountSample.setIp(IpAddressCountSample.BLOCKING_HIT_IP) + self.limitBlockedIpAddressCountSample.setCount(LimitValueSample.BLOCKING_LIMIT_VAL) + self.limitBlockedIpAddressCountSample.forcePost(self.limitBlockedIpAddressCountSample.data) + + self.meterBlacklistSample.setPartnerId(self.partnerId) + self.meterBlacklistSample.setPattern(MeterBlacklistSample.BLOCKED_URI) + self.meterBlacklistSample.forcePost(self.meterBlacklistSample.data) + + def test_for_check_limit(self): + uri = MeterBlacklistSample.UNBLOCKED_URI + # test for under limit + # cannot use reverse method since we have ip value in the middle of uri + url = '%smeters/ip/%s/limit/?partnerId=%s&uri=%s' % (serverUrl, self.successIpAddressCountSample.getIp(), self.partnerId, uri) + self.assert_check_limit(url, 'OK') + # test for hit limit warning + url = '%smeters/ip/%s/limit/?partnerId=%s&uri=%s' % (serverUrl, self.limitWarningIpAddressCountSample.getIp(), self.partnerId, uri) + self.assert_check_limit(url, 'Warning') + # test for over limit + url = '%smeters/ip/%s/limit/?partnerId=%s&uri=%s' % (serverUrl, self.limitBlockedIpAddressCountSample.getIp(), self.partnerId, uri) + self.assert_check_limit(url, 'Block') + + def assert_check_limit(self, url, status): + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + res = self.client.get(url) + if res.status_code == 200: + self.assertEqual(json.loads(res.content)['status'], status) + else: + self.fail('failed to run test, response code is %s' % res.status_code) + + def test_for_blacklisted_url(self): + uri = MeterBlacklistSample.BLOCKED_URI + url = '%smeters/ip/%s/limit/?partnerId=%s&uri=%s' % (serverUrl, self.successIpAddressCountSample.getIp(), self.partnerId, uri) + self.assert_check_limit(url, 'BlackListBlock') + +print("Running unit tests on metering web services API.........") + +if __name__ == '__main__': + sys.argv[1:] = [] + unittest.main() + ret = not runner.run(suite).wasSuccessful() + sys.exit(ret) \ No newline at end of file diff --git a/metering/views.py b/metering/views.py index 949bda4b..f26f5ddf 100644 --- a/metering/views.py +++ b/metering/views.py @@ -78,13 +78,13 @@ class check_limit(APIView): ''' def get(self, request, ip, format=None): partnerId = request.GET.get('partnerId') - uri = request.GET.get('uri').decode('utf8') + uri = request.GET.get('uri') """PW-287 Change the check_limit() function to get the patterns for the specified partner by partnerId and iterate through them to find any matches, returning status: Block if matched and going on to the current logic if not matched. """ - + """ Matching Versus Searching http://www.tutorialspoint.com/python/python_reg_expressions.htm Python offers two different primitive operations based on regular expressions: @@ -104,7 +104,7 @@ def get(self, request, ip, format=None): ret = {'status': "BlackListBlock"} logger.info("Metering check_limit %s%s %s%s %s%s %s" % ("ip:",ip,"partnerId:",partnerId,"uri:",uri,ret)) return HttpResponse(json.dumps(ret), content_type="application/json", status=200) - + if IpAddressCount.objects.filter(ip=ip).filter(partnerId=partnerId).exists(): currIp = IpAddressCount.objects.get(ip=ip,partnerId=partnerId) if (currIp.count >= LimitValue.objects.filter(partnerId=partnerId).aggregate(Max('val'))['val__max']): diff --git a/nullservice/apps.py b/nullservice/apps.py index ef89634e..a6c0cebe 100644 --- a/nullservice/apps.py +++ b/nullservice/apps.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + from django.apps import AppConfig diff --git a/nullservice/manualTests.py b/nullservice/manualTests.py new file mode 100644 index 00000000..6672f807 --- /dev/null +++ b/nullservice/manualTests.py @@ -0,0 +1,30 @@ +import django +import unittest +import sys +import json + +from django.test import TestCase +from common.tests import TestGenericInterfaces, GenericTest +from http.cookies import SimpleCookie + +# Create your tests here. +django.setup() +serverUrl = TestGenericInterfaces.getHost() + +# test for API end point /nullservice +class NullServiceTest(GenericTest, TestCase): + def test_for_get(self): + url = '%snullservice/' % (serverUrl) + + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + +print("Running unit tests on nullservice web services API.........") + +if __name__ == '__main__': + sys.argv[1:] = [] + unittest.main() + ret = not runner.run(suite).wasSuccessful() + sys.exit(ret) \ No newline at end of file diff --git a/nullservice/models.py b/nullservice/models.py index bd4b2abe..19512613 100644 --- a/nullservice/models.py +++ b/nullservice/models.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals + from django.db import models diff --git a/nullservice/tests.py b/nullservice/tests.py index 7ce503c2..d303c58c 100644 --- a/nullservice/tests.py +++ b/nullservice/tests.py @@ -1,3 +1,6 @@ from django.test import TestCase +from common.tests import ManualTest -# Create your tests here. +class NullServiceTest(ManualTest, TestCase): + path = "/nullservice/" + testMethodStr = "running ./manage.py test nullservice.manualTests" \ No newline at end of file diff --git a/partner/migrations/0001_initial.py b/partner/migrations/0001_initial.py index 5dac28d1..209755da 100644 --- a/partner/migrations/0001_initial.py +++ b/partner/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -27,7 +27,7 @@ class Migration(migrations.Migration): ('partnerPatternId', models.AutoField(serialize=False, primary_key=True)), ('sourceUri', models.CharField(max_length=200)), ('targetUri', models.CharField(max_length=200)), - ('partnerId', models.ForeignKey(to='partner.Partner', db_column=b'partnerId')), + ('partnerId', models.ForeignKey(to='partner.Partner', db_column='partnerId', on_delete=models.PROTECT)), ], options={ 'db_table': 'PartnerPattern', @@ -40,7 +40,7 @@ class Migration(migrations.Migration): ('period', models.IntegerField()), ('price', models.DecimalField(max_digits=6, decimal_places=2)), ('groupDiscountPercentage', models.DecimalField(max_digits=6, decimal_places=2)), - ('partnerId', models.ForeignKey(to='partner.Partner', db_column=b'partnerId')), + ('partnerId', models.ForeignKey(to='partner.Partner', db_column='partnerId', on_delete=models.PROTECT)), ], options={ 'db_table': 'SubscriptionTerm', diff --git a/partner/migrations/0002_subscriptiondescription_subscriptiondescriptionitem.py b/partner/migrations/0002_subscriptiondescription_subscriptiondescriptionitem.py index 065f42a2..4744479a 100644 --- a/partner/migrations/0002_subscriptiondescription_subscriptiondescriptionitem.py +++ b/partner/migrations/0002_subscriptiondescription_subscriptiondescriptionitem.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -16,8 +16,8 @@ class Migration(migrations.Migration): fields=[ ('subscriptionDescriptionId', models.AutoField(serialize=False, primary_key=True)), ('header', models.CharField(max_length=200)), - ('descriptionType', models.CharField(default=b'Default', max_length=200)), - ('partnerId', models.ForeignKey(to='partner.Partner', db_column=b'partnerId')), + ('descriptionType', models.CharField(default='Default', max_length=200)), + ('partnerId', models.ForeignKey(to='partner.Partner', db_column='partnerId', on_delete=models.PROTECT)), ], options={ 'db_table': 'SubscriptionDescription', @@ -27,7 +27,7 @@ class Migration(migrations.Migration): name='SubscriptionDescriptionItem', fields=[ ('subscriptionDescriptionItemId', models.AutoField(serialize=False, primary_key=True)), - ('subscriptionDescriptionId', models.ForeignKey(to='partner.SubscriptionDescription', db_column=b'subscriptionDescriptionId')), + ('subscriptionDescriptionId', models.ForeignKey(to='partner.SubscriptionDescription', db_column='subscriptionDescriptionId', on_delete=models.PROTECT)), ], options={ 'db_table': 'SubscriptionDescriptionItem', diff --git a/partner/migrations/0003_subscriptiondescriptionitem_text.py b/partner/migrations/0003_subscriptiondescriptionitem_text.py index 582f6c4c..76e2e4cb 100644 --- a/partner/migrations/0003_subscriptiondescriptionitem_text.py +++ b/partner/migrations/0003_subscriptiondescriptionitem_text.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/partner/migrations/0004_subscriptionterm_description.py b/partner/migrations/0004_subscriptionterm_description.py index 89d8913d..70ef8e16 100644 --- a/partner/migrations/0004_subscriptionterm_description.py +++ b/partner/migrations/0004_subscriptionterm_description.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/partner/migrations/0005_partner_termofserviceuri.py b/partner/migrations/0005_partner_termofserviceuri.py index 56e8593e..29e6330f 100644 --- a/partner/migrations/0005_partner_termofserviceuri.py +++ b/partner/migrations/0005_partner_termofserviceuri.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/partner/migrations/0006_HomePageUri.py b/partner/migrations/0006_HomePageUri.py index 0b9a9e9f..348dbfe5 100644 --- a/partner/migrations/0006_HomePageUri.py +++ b/partner/migrations/0006_HomePageUri.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/partner/migrations/0007_Partner_description_addColumn_PW271.py b/partner/migrations/0007_Partner_description_addColumn_PW271.py index cdfc4453..0bb9e9a4 100644 --- a/partner/migrations/0007_Partner_description_addColumn_PW271.py +++ b/partner/migrations/0007_Partner_description_addColumn_PW271.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/partner/migrations/0008_Partner_tbl_add_login_register_link_colums_PW-336.py b/partner/migrations/0008_Partner_tbl_add_login_register_link_colums_PW-336.py index 26c9e295..18899f8a 100644 --- a/partner/migrations/0008_Partner_tbl_add_login_register_link_colums_PW-336.py +++ b/partner/migrations/0008_Partner_tbl_add_login_register_link_colums_PW-336.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/partner/migrations/0009_Partner_tbl_rename_colums_PW-336.py b/partner/migrations/0009_Partner_tbl_rename_colums_PW-336.py index 69ab181e..b1513a7d 100644 --- a/partner/migrations/0009_Partner_tbl_rename_colums_PW-336.py +++ b/partner/migrations/0009_Partner_tbl_rename_colums_PW-336.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/partner/migrations/0010_added_subscriptionListDesc_field_to_partner_PW-332.py b/partner/migrations/0010_added_subscriptionListDesc_field_to_partner_PW-332.py index 77ac5234..6decbd4b 100644 --- a/partner/migrations/0010_added_subscriptionListDesc_field_to_partner_PW-332.py +++ b/partner/migrations/0010_added_subscriptionListDesc_field_to_partner_PW-332.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/partner/migrations/0011_Partner_registerText_column_PW321.py b/partner/migrations/0011_Partner_registerText_column_PW321.py index 411596cd..190fab9b 100644 --- a/partner/migrations/0011_Partner_registerText_column_PW321.py +++ b/partner/migrations/0011_Partner_registerText_column_PW321.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/partner/migrations/0012_Partner_ForgotUIEmail_columns_PW342.py b/partner/migrations/0012_Partner_ForgotUIEmail_columns_PW342.py index 8869b417..a919dc57 100644 --- a/partner/migrations/0012_Partner_ForgotUIEmail_columns_PW342.py +++ b/partner/migrations/0012_Partner_ForgotUIEmail_columns_PW342.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/partner/migrations/0013_Partner_ForgotUIEmail_body_columns_PW342.py b/partner/migrations/0013_Partner_ForgotUIEmail_body_columns_PW342.py index e8799fee..66f97557 100644 --- a/partner/migrations/0013_Partner_ForgotUIEmail_body_columns_PW342.py +++ b/partner/migrations/0013_Partner_ForgotUIEmail_body_columns_PW342.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/partner/migrations/0014_PartnerUIDPWDPlaceHoldersPW348.py b/partner/migrations/0014_PartnerUIDPWDPlaceHoldersPW348.py index 501ee172..7c3d92f4 100644 --- a/partner/migrations/0014_PartnerUIDPWDPlaceHoldersPW348.py +++ b/partner/migrations/0014_PartnerUIDPWDPlaceHoldersPW348.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models @@ -24,11 +24,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name='partner', name='loginPasswordFieldPrompt', - field=models.CharField(default=b'Password', max_length=20), + field=models.CharField(default='Password', max_length=20), ), migrations.AddField( model_name='partner', name='loginUserNameFieldPrompt', - field=models.CharField(default=b'Username', max_length=20), + field=models.CharField(default='Username', max_length=20), ), ] diff --git a/partner/migrations/0015_partner_forgotusernametext.py b/partner/migrations/0015_partner_forgotusernametext.py index ffa6e9ad..2333b564 100644 --- a/partner/migrations/0015_partner_forgotusernametext.py +++ b/partner/migrations/0015_partner_forgotusernametext.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models @@ -11,9 +11,4 @@ class Migration(migrations.Migration): ] operations = [ - migrations.AddField( - model_name='partner', - name='forgotUserNameText', - field=models.CharField(max_length=200, null=True), - ), ] diff --git a/partner/migrations/0016_Partner_resetpwdemailbody_column_PW357.py b/partner/migrations/0016_Partner_resetpwdemailbody_column_PW357.py index b576eacd..5d002ebe 100644 --- a/partner/migrations/0016_Partner_resetpwdemailbody_column_PW357.py +++ b/partner/migrations/0016_Partner_resetpwdemailbody_column_PW357.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/partner/migrations/0017_Partner_loginRedirectErrorText_PW360.py b/partner/migrations/0017_Partner_loginRedirectErrorText_PW360.py index 6e2f058f..8f008732 100644 --- a/partner/migrations/0017_Partner_loginRedirectErrorText_PW360.py +++ b/partner/migrations/0017_Partner_loginRedirectErrorText_PW360.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/partner/migrations/0018_added_defaultLoginRedirect_PW-373.py b/partner/migrations/0018_added_defaultLoginRedirect_PW-373.py index abe98100..9ca42fb1 100644 --- a/partner/migrations/0018_added_defaultLoginRedirect_PW-373.py +++ b/partner/migrations/0018_added_defaultLoginRedirect_PW-373.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/partner/migrations/0019_added_uiUri_uiMeterUri_PW-376.py b/partner/migrations/0019_added_uiUri_uiMeterUri_PW-376.py index edcd8a8e..fde603ca 100644 --- a/partner/migrations/0019_added_uiUri_uiMeterUri_PW-376.py +++ b/partner/migrations/0019_added_uiUri_uiMeterUri_PW-376.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/partner/migrations/0020_Partner_GuideURI_columns_PW419.py b/partner/migrations/0020_Partner_GuideURI_columns_PW419.py index f9691a52..a0fd0a2e 100644 --- a/partner/migrations/0020_Partner_GuideURI_columns_PW419.py +++ b/partner/migrations/0020_Partner_GuideURI_columns_PW419.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import migrations, models diff --git a/partner/models.py b/partner/models.py index 43042616..4315dd2c 100644 --- a/partner/models.py +++ b/partner/models.py @@ -30,23 +30,23 @@ class Partner(models.Model): resetPasswordEmailBody = models.CharField(max_length=2000, null=True) loginRedirectErrorText = models.CharField(max_length=100, null=True) guideUri = models.CharField(max_length=200, null=True) - + class Meta: db_table = "Partner" class PartnerPattern(models.Model): partnerPatternId = models.AutoField(primary_key=True) - partnerId = models.ForeignKey('Partner', db_column='partnerId') + partnerId = models.ForeignKey('Partner', db_column='partnerId', on_delete=models.PROTECT) sourceUri = models.CharField(max_length=200) targetUri = models.CharField(max_length=200) - + class Meta: db_table = "PartnerPattern" class SubscriptionTerm(models.Model): subscriptionTermId = models.AutoField(primary_key=True) description = models.CharField(max_length=200) - partnerId = models.ForeignKey('partner.Partner', db_column="partnerId") + partnerId = models.ForeignKey('partner.Partner', db_column="partnerId", on_delete=models.PROTECT) period = models.IntegerField() price = models.DecimalField(decimal_places=2,max_digits=6) groupDiscountPercentage = models.DecimalField(decimal_places=2,max_digits=6) @@ -56,7 +56,7 @@ class Meta: class SubscriptionDescriptionItem(models.Model): subscriptionDescriptionItemId = models.AutoField(primary_key=True) - subscriptionDescriptionId = models.ForeignKey('SubscriptionDescription', db_column='subscriptionDescriptionId') + subscriptionDescriptionId = models.ForeignKey('SubscriptionDescription', db_column='subscriptionDescriptionId', on_delete=models.PROTECT) text = models.CharField(max_length=2048) class Meta: @@ -65,7 +65,7 @@ class Meta: class SubscriptionDescription(models.Model): subscriptionDescriptionId = models.AutoField(primary_key=True) header = models.CharField(max_length=200) - partnerId = models.ForeignKey('Partner', db_column='partnerId') + partnerId = models.ForeignKey('Partner', db_column='partnerId', on_delete=models.PROTECT) descriptionType = models.CharField(max_length=200, default='Default') # Default, Individual, Institution, Commercial class Meta: diff --git a/partner/pyTests.py b/partner/pyTests.py deleted file mode 100644 index fed0cadd..00000000 --- a/partner/pyTests.py +++ /dev/null @@ -1,74 +0,0 @@ -#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. -import sys -import django -import unittest -from unittest import TestCase -from partner.models import Partner, PartnerPattern, SubscriptionTerm, SubscriptionDescription, SubscriptionDescriptionItem -import copy -from common.pyTests import PyTestGenerics, GenericCRUDTest, GenericTest -from testSamples import PartnerSample, PartnerPatternSample, SubscriptionTermSample, SubscriptionDescriptionSample, SubscriptionDescriptionItemSample -import requests - -initPyTest = PyTestGenerics.initPyTest -genericForceDelete = PyTestGenerics.forceDelete - -# Create your tests here. -django.setup() -serverUrl = initPyTest() - -print "using server url %s" % serverUrl - -class PartnerCRUD(GenericCRUDTest, TestCase): - sample = PartnerSample(serverUrl) - - def test_for_create(self): - Partner.objects.filter(partnerId=self.sample.data['partnerId']).delete() - super(PartnerCRUD, self).test_for_create() - - def test_for_getByUri(self): - partnerSample = PartnerSample(serverUrl) - Partner.objects.filter(partnerId=partnerSample.data['partnerId']).delete() - partnerId = partnerSample.forcePost(partnerSample.data) - - partnerPatternSample = PartnerPatternSample(serverUrl) - partnerPatternSample.data['partnerId'] = partnerId - partnerPatternId = partnerPatternSample.forcePost(partnerPatternSample.data) - url = self.sample.url + "?uri=%s" % (partnerPatternSample.data['sourceUri']) - cookies = {'apiKey':self.apiKey} - req = requests.get(url,cookies=cookies) - self.assertEqual(len(req.json()) > 0, True) - genericForceDelete(partnerPatternSample.model, partnerPatternSample.pkName, partnerPatternId) - genericForceDelete(partnerSample.model, partnerSample.pkName, partnerId) - -class PartnerPatternCRUD(GenericCRUDTest, TestCase): - sample = PartnerPatternSample(serverUrl) - partnerSample = PartnerSample(serverUrl) - def setUp(self): - super(PartnerPatternCRUD,self).setUp() - Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() - self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) - self.sample.partnerId=self.sample.data['partnerId']=self.sample.updateData['partnerId']=self.partnerId - - def tearDown(self): - super(PartnerPatternCRUD,self).tearDown() - genericForceDelete(self.partnerSample.model, self.partnerSample.pkName, self.partnerId) - -class SubscriptionTermCRUD(GenericCRUDTest, TestCase): - sample = SubscriptionTermSample(serverUrl) - partnerSample = PartnerSample(serverUrl) - def setUp(self): - super(SubscriptionTermCRUD,self).setUp() - Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() - self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) - self.sample.partnerId=self.sample.data['partnerId']=self.sample.updateData['partnerId']=self.partnerId - def tearDown(self): - super(SubscriptionTermCRUD,self).tearDown() - genericForceDelete(self.partnerSample.model, self.partnerSample.pkName, self.partnerId) - -print "Running unit tests on partner web services API........." - -if __name__ == '__main__': - sys.argv[1:] = [] - unittest.main() - ret = not runner.run(suite).wasSuccessful() - sys.exit(ret) diff --git a/partner/testSamples.py b/partner/testSamples.py index 9ac2132d..7cf0d538 100644 --- a/partner/testSamples.py +++ b/partner/testSamples.py @@ -1,42 +1,40 @@ import sys import django import unittest -from unittest import TestCase +from django.test import TestCase from partner.models import Partner, PartnerPattern, SubscriptionTerm, SubscriptionDescription, SubscriptionDescriptionItem import copy -from common.pyTests import PyTestGenerics +from common.tests import TestGenericInterfaces -genericForcePost = PyTestGenerics.forcePost +genericForcePost = TestGenericInterfaces.forcePost class PartnerSample(): url = None path = 'partners/' data = { - 'partnerId':'test', - 'name':'testPartner', + 'partnerId':'phoenix', + 'name':'phoenixTestPartner', 'logoUri':'randomuri.com', 'termOfServiceUri':'anotherrandomuri.com', 'description':'Genome database for the reference plant Arabidopsis thaliana', + 'resetPasswordEmailBody': 'Test Partner username: %s (%s). Your temp password is %s.' } updateData = { - 'partnerId':'test', - 'name':'testPartner2', - 'logoUri':'randomuri.com', - 'termOfServiceUri':'anotherrandomuri.com', - 'description':'Genome database for the reference plant Arabidopsis thaliana2', + 'partnerId':'phoenix', + 'name':'phoenixTestPartner2', + 'logoUri':'randomuri2.com', + 'termOfServiceUri':'anotherrandomuri2.com', + 'description':'Updated description: Genome database for the reference plant Arabidopsis thaliana', } pkName = 'partnerId' model = Partner def __init__(self, serverUrl): self.url = serverUrl+self.path + self.data = copy.deepcopy(self.data) - #delete possible entries that we use as test case - try: - filters={self.pkName:self.data['patternId']} - self.model.objects.get(**filters).delete() - except: - pass + def setDifferentPartnerId(self): + self.data['partnerId'] = 'test2' def forcePost(self,data): return genericForcePost(self.model, self.pkName, data) @@ -46,13 +44,13 @@ class PartnerPatternSample(): path = 'partners/patterns/' data = { 'partnerId':None, - 'sourceUri':'https://paywall2.steveatgetexp.com', - 'targetUri':'https://back-prod.steveatgetexp.com', + 'sourceUri':'https://www.arabidopsis.org', + 'targetUri':'https://back-prod.arabidopsis.org', } updateData = { 'partnerId':None, - 'sourceUri':'https://paywall2a.steveatgetexp.com', - 'targetUri':'https://back-prod.steveatgetexp.com', + 'sourceUri':'https://uat.arabidopsis.org', + 'targetUri':'https://back-uat.arabidopsis.org', } pkName = 'partnerPatternId' model = PartnerPattern @@ -71,15 +69,15 @@ class SubscriptionTermSample(): data = { 'partnerId':None, 'period':180, - 'price':360.00, - 'groupDiscountPercentage':0.7, + 'price':180.00, + 'groupDiscountPercentage': 1.5, 'description':'test' } updateData = { 'partnerId':None, 'period':365, - 'price':180.00, - 'groupDiscountPercentage':0.8, + 'price':360.00, + 'groupDiscountPercentage': 2.5, 'description':'test2' } pkName = 'subscriptionTermId' @@ -88,6 +86,9 @@ class SubscriptionTermSample(): def __init__(self, serverUrl): self.url = serverUrl+self.path + def setPartnerId(self, partnerId): + self.data['partnerId'] = partnerId + def forcePost(self,data): postData = copy.deepcopy(data) postData['partnerId'] = Partner.objects.get(partnerId=data['partnerId']) @@ -97,14 +98,14 @@ class SubscriptionDescriptionSample(): url = None path = 'partners/descriptions/' data = { + 'partnerId':None, 'header':'Commercial Description', 'descriptionType':'Commercial', - 'partnerId':None, } updateData = { - 'header':'Commercial Description2', - 'descriptionType':'Institution', 'partnerId':None, + 'header':'Institution Description', + 'descriptionType':'Institution', } pkName = 'subscriptionDescriptionId' model = SubscriptionDescription @@ -121,10 +122,12 @@ class SubscriptionDescriptionItemSample(): url = None path = 'partners/descriptionItems/' data = { - 'subscriptionDescriptionId':None, + 'subscriptionDescriptionId': None, + 'text': 'Benefit' } updateData = { - 'subscriptionDescriptionId':None, + 'subscriptionDescriptionId': None, + 'text': 'Updated Benefit' } pkName = 'subscriptionDescriptionItemId' model = SubscriptionDescriptionItem @@ -134,5 +137,5 @@ def __init__(self, serverUrl): def forcePost(self,data): postData = copy.deepcopy(data) - postData['subscriptionDescritpionId'] = SubscriptionDescription.objects.get(subscriptionDescriptionId=data['subscriptionDescriptionId']) - return genericForcePost(self.model, self.pkName, postData) + postData['subscriptionDescriptionId'] = SubscriptionDescription.objects.get(subscriptionDescriptionId=data['subscriptionDescriptionId']) + return genericForcePost(self.model, self.pkName, postData) \ No newline at end of file diff --git a/partner/tests.py b/partner/tests.py index 7ce503c2..01b7dcbf 100644 --- a/partner/tests.py +++ b/partner/tests.py @@ -1,3 +1,86 @@ -from django.test import TestCase +#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. +import sys +import django +import unittest +import copy +import json +from django.test import TestCase, Client +from partner.models import Partner, PartnerPattern, SubscriptionTerm, SubscriptionDescription, SubscriptionDescriptionItem +from common.tests import TestGenericInterfaces, GenericCRUDTest, GenericGETOnlyTest, checkMatch +from .testSamples import PartnerSample, PartnerPatternSample, SubscriptionTermSample, SubscriptionDescriptionSample, SubscriptionDescriptionItemSample -# Create your tests here. +# Create your tests here. +django.setup() +serverUrl = TestGenericInterfaces.getHost() + +# test for API end point /partners/ +class PartnerCRUD(GenericGETOnlyTest, TestCase): + sample = PartnerSample(serverUrl) + +# test for API end point /partners/patterns/ +class PartnerPatternCRUD(GenericCRUDTest, TestCase): + sample = PartnerPatternSample(serverUrl) + partnerSample = PartnerSample(serverUrl) + + def setUp(self): + super(PartnerPatternCRUD,self).setUp() + self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) + self.sample.data['partnerId']=self.sample.updateData['partnerId']=self.partnerId + + # cannot perform get all since for GET action sourceUri is required + def test_for_get_all(self): + pass + + def test_for_get(self): + sample = self.sample + pk = sample.forcePost(sample.data) + url = sample.url + '?%s=%s' % ('sourceUri', sample.data['sourceUri']) + + # no cookie needed + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + self.assertEqual(checkMatch(sample.data, json.loads(res.content), sample.pkName, pk), True) + + +# test for API end point /partners/terms/ +class SubscriptionTermCRUD(GenericGETOnlyTest, TestCase): + sample = SubscriptionTermSample(serverUrl) + partnerSample = PartnerSample(serverUrl) + + def setUp(self): + super(SubscriptionTermCRUD,self).setUp() + self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) + self.sample.data['partnerId']=self.sample.updateData['partnerId']=self.partnerId + +# test for API end point /partners/descriptions/ +# TODO: test for case when GET request is sent with includeText param +class SubscriptionDescriptionCRUD(GenericGETOnlyTest, TestCase): + sample = SubscriptionDescriptionSample(serverUrl) + partnerSample = PartnerSample(serverUrl) + + def setUp(self): + super(SubscriptionDescriptionCRUD,self).setUp() + self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) + self.sample.data['partnerId']=self.sample.updateData['partnerId']=self.partnerId + +# test for API end point /partners/descriptionItems/ +class SubscriptionDescriptionItemCRUD(GenericCRUDTest, TestCase): + sample = SubscriptionDescriptionItemSample(serverUrl) + partnerSample = PartnerSample(serverUrl) + descriptionSample = SubscriptionDescriptionSample(serverUrl) + + def setUp(self): + super(SubscriptionDescriptionItemCRUD,self).setUp() + self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) + self.descriptionSample.data['partnerId']=self.partnerId + self.descriptionId = self.descriptionSample.forcePost(self.descriptionSample.data) + self.sample.data['subscriptionDescriptionId']=self.sample.updateData['subscriptionDescriptionId']=self.descriptionId + +print("Running unit tests on partner web services API.........") + +if __name__ == '__main__': + sys.argv[1:] = [] + unittest.main() + ret = not runner.run(suite).wasSuccessful() + sys.exit(ret) diff --git a/partner/views.py b/partner/views.py index fddf5dcd..57c150d9 100644 --- a/partner/views.py +++ b/partner/views.py @@ -4,8 +4,8 @@ from rest_framework.views import APIView from rest_framework import generics -from models import Partner, PartnerPattern, SubscriptionTerm, SubscriptionDescription, SubscriptionDescriptionItem -from serializers import PartnerSerializer, PartnerPatternSerializer, SubscriptionTermSerializer, SubscriptionDescriptionSerializer, SubscriptionDescriptionItemSerializer +from .models import Partner, PartnerPattern, SubscriptionTerm, SubscriptionDescription, SubscriptionDescriptionItem +from .serializers import PartnerSerializer, PartnerPatternSerializer, SubscriptionTermSerializer, SubscriptionDescriptionSerializer, SubscriptionDescriptionItemSerializer import json diff --git a/party/manualTests.py b/party/manualTests.py new file mode 100644 index 00000000..dc1eb784 --- /dev/null +++ b/party/manualTests.py @@ -0,0 +1,50 @@ +import django +import unittest +import sys +import json +from django.test import TestCase +from .testSamples import UsageSample +from django.core.mail import send_mail +from django.test.utils import override_settings + +# test for API end point /parties/usage/ +# API used for requesting usage for consortiums/institutions +@override_settings(EMAIL_BACKEND='django.core.mail.backends.smtp.EmailBackend') +class GetUsageRequestTest(TestCase): + sample = UsageSample() + + def test_for_send_usage_request(self): + self.send_usage_email(self.sample.institutionData) + self.send_usage_email(self.sample.consortiumData) + + # this code is copied from party/views.py implementation + # since the original implementation hard coded recipient email + def send_usage_email(self, data): + partyName = '' + partyTypeName = '' + if data['institution']: + partyName = data['institution'] + partyTypeName = 'Institution' + elif data['consortium']: + partyName = data['consortium'] + partyTypeName = 'Consortium' + subject = "%s Usage Request For %s" % (partyTypeName,partyName) + message = "Partner: %s\n" \ + "%s: %s\n" \ + "User: %s\n" \ + "Email: %s\n" \ + "Start date: %s\n" \ + "End date: %s\n" \ + "Comments: %s\n" \ + % (data['partner'], partyTypeName, partyName, data['name'], data['email'], data['startDate'], data['endDate'], data['comments']) + from_email = "subscriptions@phoenixbioinformatics.org" + recipient_list = [self.sample.RECEIPIENT_EMAIL] + self.assertEqual(send_mail(subject=subject, message=message, from_email=from_email, recipient_list=recipient_list, fail_silently=False), 1) + +print("Running unit tests on consortium usage email request.........") + +if __name__ == '__main__': + sys.argv[1:] = [] + unittest.main() + ret = not runner.run(suite).wasSuccessful() + sys.exit(ret) \ No newline at end of file diff --git a/party/migrations/0001_initial.py b/party/migrations/0001_initial.py index 17199e10..4ac5c205 100644 --- a/party/migrations/0001_initial.py +++ b/party/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -25,7 +25,7 @@ class Migration(migrations.Migration): name='Party', fields=[ ('partyId', models.AutoField(serialize=False, primary_key=True)), - ('partyType', models.CharField(default=b'user', max_length=200)), + ('partyType', models.CharField(default='user', max_length=200)), ], options={ 'db_table': 'Party', @@ -34,6 +34,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='iprange', name='partyId', - field=models.ForeignKey(to='party.Party', db_column=b'partyId'), + field=models.ForeignKey(to='party.Party', db_column='partyId', on_delete=models.PROTECT), ), ] diff --git a/party/migrations/0002_auto_20150817_2225.py b/party/migrations/0002_auto_20150817_2225.py index 44eacff0..026d59d2 100644 --- a/party/migrations/0002_auto_20150817_2225.py +++ b/party/migrations/0002_auto_20150817_2225.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -29,11 +29,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name='party', name='name', - field=models.CharField(default=b'', max_length=200), + field=models.CharField(default='', max_length=200), ), migrations.AddField( model_name='party', name='country', - field=models.ForeignKey(db_column=b'countryId', default=334, to='party.Country', null=True), + field=models.ForeignKey(db_column='countryId', default=334, to='party.Country', null=True, on_delete=models.PROTECT), ), ] diff --git a/party/migrations/0003_iprange_label.py b/party/migrations/0003_iprange_label.py index 8d4d1163..dd180be9 100644 --- a/party/migrations/0003_iprange_label.py +++ b/party/migrations/0003_iprange_label.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/party/migrations/0004_auto_20150901_1757.py b/party/migrations/0004_auto_20150901_1757.py index a18a51bc..c418d6ea 100644 --- a/party/migrations/0004_auto_20150901_1757.py +++ b/party/migrations/0004_auto_20150901_1757.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -14,6 +14,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='party', name='country', - field=models.ForeignKey(db_column=b'countryId', default=117, to='party.Country', null=True), + field=models.ForeignKey(db_column='countryId', default=117, to='party.Country', null=True, on_delete=models.PROTECT), ), ] diff --git a/party/migrations/0004_auto_20150916_2317.py b/party/migrations/0004_auto_20150916_2317.py index 7fa91ec7..f20907b8 100644 --- a/party/migrations/0004_auto_20150916_2317.py +++ b/party/migrations/0004_auto_20150916_2317.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -14,11 +14,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name='party', name='consortium', - field=models.ForeignKey(to='party.Party', null=True), + field=models.ForeignKey(to='party.Party', null=True, on_delete=models.PROTECT), ), migrations.AlterField( model_name='party', name='country', - field=models.ForeignKey(db_column=b'countryId', default=10, to='party.Country', null=True), + field=models.ForeignKey(db_column='countryId', default=10, to='party.Country', null=True, on_delete=models.PROTECT), ), ] diff --git a/party/migrations/0005_remove_party_consortium.py b/party/migrations/0005_remove_party_consortium.py index 5d3fd56b..447199d6 100644 --- a/party/migrations/0005_remove_party_consortium.py +++ b/party/migrations/0005_remove_party_consortium.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/party/migrations/0006_party_consortium.py b/party/migrations/0006_party_consortium.py index d47412d1..cb2dfe86 100644 --- a/party/migrations/0006_party_consortium.py +++ b/party/migrations/0006_party_consortium.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -14,6 +14,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='party', name='consortium', - field=models.ForeignKey(to='party.Party', null=True), + field=models.ForeignKey(to='party.Party', null=True, on_delete=models.PROTECT), ), ] diff --git a/party/migrations/0007_auto_20150917_0023.py b/party/migrations/0007_auto_20150917_0023.py index d712c2b0..aaec2fdc 100644 --- a/party/migrations/0007_auto_20150917_0023.py +++ b/party/migrations/0007_auto_20150917_0023.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -14,6 +14,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='party', name='country', - field=models.ForeignKey(db_column=b'countryId', default=219, to='party.Country', null=True), + field=models.ForeignKey(db_column='countryId', default=219, to='party.Country', null=True, on_delete=models.PROTECT), ), ] diff --git a/party/migrations/0008_merge.py b/party/migrations/0008_merge.py index 10827537..a657091e 100644 --- a/party/migrations/0008_merge.py +++ b/party/migrations/0008_merge.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/party/migrations/0009_auto_20150924_0050.py b/party/migrations/0009_auto_20150924_0050.py index c1810471..eb61efd2 100644 --- a/party/migrations/0009_auto_20150924_0050.py +++ b/party/migrations/0009_auto_20150924_0050.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -14,6 +14,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='party', name='country', - field=models.ForeignKey(db_column=b'countryId', default=219, to='party.Country', null=True), + field=models.ForeignKey(db_column='countryId', default=219, to='party.Country', null=True, on_delete=models.PROTECT), ), ] diff --git a/party/migrations/0010_change_party_model.py b/party/migrations/0010_change_party_model.py index 6e943e04..673b1493 100644 --- a/party/migrations/0010_change_party_model.py +++ b/party/migrations/0010_change_party_model.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -24,17 +24,17 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='party', name='country', - field=models.ForeignKey(db_column=b'countryId', to='party.Country', null=True), + field=models.ForeignKey(db_column='countryId', to='party.Country', null=True, on_delete=models.PROTECT), ), migrations.AddField( model_name='affiliation', name='consortiumId', - field=models.ForeignKey(related_name='consortiumId', db_column=b'consortiumId', to='party.Party'), + field=models.ForeignKey(related_name='consortiumId', db_column='consortiumId', to='party.Party', on_delete=models.PROTECT), ), migrations.AddField( model_name='affiliation', name='institutionId', - field=models.ForeignKey(related_name='institutionId', db_column=b'institutionId', to='party.Party'), + field=models.ForeignKey(related_name='institutionId', db_column='institutionId', to='party.Party', on_delete=models.PROTECT), ), migrations.AddField( model_name='party', diff --git a/party/migrations/0011_remove_affiliation.py b/party/migrations/0011_remove_affiliation.py index 771ec7c9..38cd21c0 100644 --- a/party/migrations/0011_remove_affiliation.py +++ b/party/migrations/0011_remove_affiliation.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/party/migrations/0012_create_partyaffiliation.py b/party/migrations/0012_create_partyaffiliation.py index 62aa0725..72979d04 100644 --- a/party/migrations/0012_create_partyaffiliation.py +++ b/party/migrations/0012_create_partyaffiliation.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -15,8 +15,8 @@ class Migration(migrations.Migration): name='PartyAffiliation', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('childPartyId', models.ForeignKey(related_name='childPartyId', db_column=b'childPartyId', to='party.Party')), - ('parentPartyId', models.ForeignKey(related_name='parentPartyId', db_column=b'parentPartyId', to='party.Party')), + ('childPartyId', models.ForeignKey(related_name='childPartyId', db_column='childPartyId', to='party.Party', on_delete=models.PROTECT)), + ('parentPartyId', models.ForeignKey(related_name='parentPartyId', db_column='parentPartyId', to='party.Party', on_delete=models.PROTECT)), ], options={ 'db_table': 'PartyAffiliation', diff --git a/party/migrations/0013_rename_primary_key_partyaffiliation.py b/party/migrations/0013_rename_primary_key_partyaffiliation.py index 05c84c2a..bc101ea4 100644 --- a/party/migrations/0013_rename_primary_key_partyaffiliation.py +++ b/party/migrations/0013_rename_primary_key_partyaffiliation.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations import datetime diff --git a/party/migrations/0014_add_label_field_to_party.py b/party/migrations/0014_add_label_field_to_party.py index 0185a4e4..21c63de3 100644 --- a/party/migrations/0014_add_label_field_to_party.py +++ b/party/migrations/0014_add_label_field_to_party.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/party/migrations/0015_label_allow_blank-PWL-749.py b/party/migrations/0015_label_allow_blank-PWL-749.py index 8d71f2e5..04d4d09e 100644 --- a/party/migrations/0015_label_allow_blank-PWL-749.py +++ b/party/migrations/0015_label_allow_blank-PWL-749.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/party/migrations/0016_create_image_info_table.py b/party/migrations/0016_create_image_info_table.py index 36b5fe32..f1f0e0b7 100644 --- a/party/migrations/0016_create_image_info_table.py +++ b/party/migrations/0016_create_image_info_table.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations import django.utils.timezone @@ -16,7 +16,7 @@ class Migration(migrations.Migration): name='ImageInfo', fields=[ ('imageInfoId', models.AutoField(serialize=False, primary_key=True)), - ('partyId', models.ForeignKey(to='party.Party', db_column=b'partyId')), + ('partyId', models.ForeignKey(to='party.Party', db_column='partyId', on_delete=models.PROTECT)), ('name', models.CharField(max_length=200)), ('imageUrl', models.CharField(max_length=500)), ('createdAt', models.DateTimeField(default=django.utils.timezone.now)), diff --git a/party/models.py b/party/models.py index 953172cd..3063c0f0 100644 --- a/party/models.py +++ b/party/models.py @@ -5,6 +5,9 @@ from netaddr import IPAddress from django.utils import timezone from common.common import validateIpRange +import hashlib +from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ import logging logger = logging.getLogger('phoenix.api.party') @@ -19,10 +22,21 @@ class Party(models.Model): partyType = models.CharField(max_length=200, default='user') name = models.CharField(max_length=200, default='') display = models.BooleanField(default=True) - country = models.ForeignKey('Country', null=True, db_column="countryId") + country = models.ForeignKey('Country', null=True, db_column="countryId", on_delete=models.PROTECT) consortiums = models.ManyToManyField('self', through="PartyAffiliation", through_fields=('childPartyId', 'parentPartyId'), symmetrical=False, related_name="PartyAffiliation") label = models.CharField(max_length=64, null=True) + class Meta: + db_table = "Party" + + def clean(self, *args, **kwargs): + self.validateInstitutionCountry() + super(Party, self).clean(*args, **kwargs) + + def save(self, *args, **kwargs): + self.clean() + super(Party, self).save(*args, **kwargs) + @staticmethod def getByIp(ipAddress): partyList = [] @@ -42,13 +56,20 @@ def getById(partyId): partyList.extend(consortiums) return partyList - class Meta: - db_table = "Party" + # this is the same as the method in Credential class + @staticmethod + def generatePasswordHash(password): + return hashlib.sha1(password.encode()).hexdigest() + + def validateInstitutionCountry(self): + if self.partyType == 'organization': + if not self.country: + raise serializers.ValidationError({'Party': _('Country field is required')}) class PartyAffiliation(models.Model): partyAffiliationId = models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True) - childPartyId = models.ForeignKey(Party, related_name="childPartyId", db_column="childPartyId") - parentPartyId = models.ForeignKey(Party, related_name="parentPartyId", db_column="parentPartyId") + childPartyId = models.ForeignKey(Party, related_name="childPartyId", db_column="childPartyId", on_delete=models.PROTECT) + parentPartyId = models.ForeignKey(Party, related_name="parentPartyId", db_column="parentPartyId", on_delete=models.PROTECT) class Meta: db_table = "PartyAffiliation" @@ -58,7 +79,7 @@ class IpRange(models.Model): ipRangeId = models.AutoField(primary_key=True) start = models.GenericIPAddressField() end = models.GenericIPAddressField() - partyId = models.ForeignKey('Party', db_column="partyId") + partyId = models.ForeignKey('Party', db_column="partyId", on_delete=models.PROTECT) label = models.CharField(max_length=64, null=True, blank=True) class Meta: @@ -93,7 +114,7 @@ def getByIp(ipAddress): except Exception: logger.error("Party IpRange %s, %s" % (obj.end, "invalid end ip")) pass - + if inputIpAddress >= start and inputIpAddress <= end: objList.append(obj) return objList @@ -107,10 +128,10 @@ class Meta: class ImageInfo(models.Model): imageInfoId = models.AutoField(primary_key=True); - partyId = models.ForeignKey(Party, db_column="partyId") + partyId = models.ForeignKey(Party, db_column="partyId", on_delete=models.PROTECT) name = models.CharField(max_length=200) imageUrl = models.CharField(max_length=500) createdAt = models.DateTimeField(default=timezone.now) class Meta: - db_table = "ImageInfo" \ No newline at end of file + db_table = "ImageInfo" diff --git a/party/pyTests.py b/party/pyTests.py deleted file mode 100644 index a2c13631..00000000 --- a/party/pyTests.py +++ /dev/null @@ -1,69 +0,0 @@ -#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. - -import django -import unittest -import sys, getopt -from unittest import TestCase -from models import Party, IpRange -import requests -import json -from testSamples import PartySample, IpRangeSample, PartyAffiliationSample -from common.pyTests import PyTestGenerics, GenericCRUDTest, GenericTest - - -# Create your tests here. -django.setup() -serverUrl = PyTestGenerics.initPyTest() -print "using server url %s" % serverUrl - -# ---------------------- UNIT TEST FOR BASIC CRUD OPERATIONS ------------- - -class PartyCRUD(GenericCRUDTest, TestCase): - sample = PartySample(serverUrl) - -class IpRangeCRUD(GenericCRUDTest, TestCase): - sample = IpRangeSample(serverUrl) - partySample = PartySample(serverUrl) - - def setUp(self): - super(IpRangeCRUD,self).setUp() - partyId = self.partySample.forcePost(self.partySample.data) - self.sample.data['partyId']=self.sample.updateData['partyId']=partyId - - def tearDown(self): - super(IpRangeCRUD,self).tearDown() - PyTestGenerics.forceDelete(self.partySample.model, self.partySample.pkName, self.sample.data['partyId']) - -class PartyAffiliationCRUD(GenericCRUDTest, TestCase): - sample = PartyAffiliationSample(serverUrl) - - def setUp(self): - pass - - def test_for_update(self): - pass - - def test_for_get(self): - pass - - def test_for_get_all(self): - pass - - def test_for_create(self): - pass - - def test_for_delete(self): - pass - - def tearDown(self): - pass - -# ----------------- END OF BASIC CRUD OPERATIONS ---------------------- - -print "Running unit tests on party web services API........." - -if __name__ == '__main__': - sys.argv[1:] = [] - unittest.main() - ret = not runner.run(suite).wasSuccessful() - sys.exit(ret) diff --git a/party/testSamples.py b/party/testSamples.py index 94bb63b2..cf30f37c 100644 --- a/party/testSamples.py +++ b/party/testSamples.py @@ -1,25 +1,49 @@ import django import unittest -import sys, getopt +import sys +import copy from unittest import TestCase -from party.models import Party, IpRange, PartyAffiliation +from party.models import Country, Party, IpRange, PartyAffiliation, ImageInfo from partner.models import Partner +from common.tests import TestGenericInterfaces +from datetime import datetime, timedelta -import copy -from common.pyTests import PyTestGenerics +genericForcePost = TestGenericInterfaces.forcePost + +class CountrySample(): + path = 'parties/countries/' + url = None + data = { + 'name': 'United States' + } + updateData = { + 'name': 'China' + } + pkName = 'countryId' + model = Country + + def __init__(self, serverUrl): + self.url = serverUrl+self.path -genericForcePost = PyTestGenerics.forcePost + def getName(self): + return self.data['name'] -class PartySample(): + def forcePost(self,data): + return genericForcePost(self.model, self.pkName, data) + +class UserPartySample(): path = 'parties/' url = None + PARTY_TYPE_USER = 'user' + PARTY_TYPE_STAFF = 'staff' + PARTY_TYPE_ADMIN = 'admin' data = { - 'partyType':'user', - 'name':'test', + 'partyType':PARTY_TYPE_USER, + 'name':'test_user', } updateData = { - 'partyType':'organization', - 'name':'test1', + 'partyType':PARTY_TYPE_USER, + 'name':'test_user_II', } pkName = 'partyId' model = Party @@ -30,21 +54,128 @@ def __init__(self, serverUrl): def forcePost(self,data): return genericForcePost(self.model, self.pkName, data) -# TODO add sample for IPv6 tests +class OrganizationPartySample(): + path = 'parties/' + url = None + PARTY_TYPE_ORG = 'organization' + data = { + 'partyType':PARTY_TYPE_ORG, + 'name':'test_organization', + 'display': True, + 'country': None, + 'label': 'Test Organization Label' + } + updateData = { + 'partyType':PARTY_TYPE_ORG, + 'name':'test_organization_II', + 'display': False, + 'country': None, + 'label': 'Test Organization Label II' + } + updateData_invalid = { + 'partyType':PARTY_TYPE_ORG, + 'name': True, + 'display': False, + 'country': None, + 'label': 'Test Consortium Label III' + } + pkName = 'partyId' + model = Party + + def __init__(self, serverUrl): + self.url = serverUrl+self.path + + def getName(self): + return self.data['name'] + + def getPartyType(self): + return self.data['partyType'] + + def setCountry(self, countryId): + self.data['country'] = countryId + + def forcePost(self,data): + postData = copy.deepcopy(data) + postData['country'] = Country.objects.get(countryId=data['country']) + return genericForcePost(self.model, self.pkName, postData) + +class InstitutionPartySample(OrganizationPartySample): + path = 'parties/institutions/' + +class ConsortiumPartySample(): + path = 'parties/consortiums/' + url = None + PARTY_TYPE_CONSORTIUM = 'consortium' + data = { + 'partyType':PARTY_TYPE_CONSORTIUM, + 'name':'test_consortium', + 'display': True, + 'country': None, + 'label': 'Test Consortium Label' + } + updateData = { + 'partyType':PARTY_TYPE_CONSORTIUM, + 'name':'test_consortium_II', + 'display': False, + 'country': None, + 'label': 'Test Consortium Label II' + } + updateData_invalid = { + 'partyType':PARTY_TYPE_CONSORTIUM, + 'name': True, + 'display': False, + 'country': None, + 'label': 'Test Consortium Label III' + } + pkName = 'partyId' + model = Party + + def __init__(self, serverUrl): + self.url = serverUrl+self.path + + def setCountry(self, countryId): + self.data['country'] = countryId + + def getName(self): + return self.data['name'] + + def getPartyType(self): + return self.data['partyType'] + + def forcePost(self,data): + postData = copy.deepcopy(data) + postData['country'] = Country.objects.get(countryId=data['country']) + return genericForcePost(self.model, self.pkName, postData) + +# TODO: add sample for IPv6 tests +# TODO: test for 'ip range too large' type of IP ranges - API needs to be +# updated. Should not throw unwrapped error class IpRangeSample(): path = 'parties/ipranges/' url = None data = { - 'start':'120.0.0.0', - 'end':'120.255.255.255', - 'partyId':1, - 'label': 'testlabel', + 'start':'120.10.20.0', + 'end':'120.10.22.255', + 'partyId': None, + 'label': 'test_label', } updateData = { - 'start':'120.0.0.0', - 'end':'120.255.211.200', - 'partyId':1, - 'label': 'labeltest', + 'start':'120.10.20.0', + 'end':'120.10.23.80', + 'partyId': None, + 'label': 'test_label_II', + } + invalidData_private = { + 'start':'192.168.1.0', + 'end':'192.168.255.255', + 'partyId': None, + 'label': 'test_label_III', + } + invalidData_oversize = { + 'start':'120.10.0.0', + 'end':'120.11.255.255', + 'partyId': None, + 'label': 'test_label_IV', } pkName = 'ipRangeId' model = IpRange @@ -52,6 +183,24 @@ class IpRangeSample(): def __init__(self, serverUrl): self.url = serverUrl+self.path + def setPartyId(self, partyId): + self.data['partyId'] = partyId + + def getPartyId(self): + return self.data['partyId'] + + def getInRangeIp(self): + return '120.10.21.231' + + def getOutRangeIp(self): + return '133.1.8.52' + + def getOutRangeIPErrorMessage(self): + return 'IP range too large: %s - %s' % (self.invalidData_oversize['start'], self.invalidData_oversize['end']) + + def getPrivateRangeIPErrorMessage(self): + return 'IP range contains private IP: %s - %s' % (self.invalidData_private['start'], self.invalidData_private['end']) + def forcePost(self,data): postData = copy.deepcopy(data) postData['partyId'] = Party.objects.get(partyId=data['partyId']) @@ -61,14 +210,82 @@ class PartyAffiliationSample(): path = 'parties/affiliations/' url = None data = { - 'parentPartyId': Party.objects.all().get(partyId=33342), - 'childPartyId' : Party.objects.all().get(partyId=33343), + 'parentPartyId': None, + 'childPartyId' : None, } pkName = 'partyAffiliationId'; model = PartyAffiliation + def __init__(self, serverUrl): self.url = serverUrl+self.path + def setParentId(self, parentPartyId): + self.data['parentPartyId'] = parentPartyId + + def getParentId(self): + return self.data['parentPartyId'] + + def setChildId(self, childPartyId): + self.data['childPartyId'] = childPartyId + + def getChildId(self): + return self.data['childPartyId'] + def forcePost(self,data): - return genericForcePost(self.model, self.pkName, data) + postData = copy.deepcopy(data) + postData['parentPartyId'] = Party.objects.get(partyId=data['parentPartyId']) + postData['childPartyId'] = Party.objects.get(partyId=data['childPartyId']) + return genericForcePost(self.model, self.pkName, postData) + +class ImageInfoSample(): + data = { + 'partyId': None, + 'name' : 'Test Organization', + 'imageUrl': 'somerandomurl' + } + pkName = 'imageInfoId'; + model = ImageInfo + def setPartyId(self, partyId): + self.data['partyId'] = partyId + + def getName(self): + return self.data['name'] + + def getImageUrl(self): + return self.data['imageUrl'] + + def forcePost(self,data): + postData = copy.deepcopy(data) + postData['partyId'] = Party.objects.get(partyId=data['partyId']) + return genericForcePost(self.model, self.pkName, postData) + +class UsageSample(): + PARTNER = 'phoenix' + COMMENTS = 'Please send us the usage data' + NUM_DAYS_DELTA = 90 + RECEIPIENT_EMAIL = 'techteam@arabidopsis.org' + startDateObj = datetime.today() - timedelta(days=NUM_DAYS_DELTA) + startDate = startDateObj.strftime('%b %d, %Y') + endDateObj = datetime.today() + endDate = endDateObj.strftime('%b %d, %Y') + institutionData = { + 'institution': 'University of Mars', + 'consortium': None, + 'name': 'Science Library', + 'partner': PARTNER, + 'email': 'science@umars.edu', + 'startDate': startDate, + 'endDate': endDate, + 'comments': COMMENTS + } + consortiumData = { + 'institution': None, + 'consortium': 'Galaxy University Association', + 'name': 'Central Library', + 'partner': PARTNER, + 'email': 'library@gua.edu', + 'startDate': startDate, + 'endDate': endDate, + 'comments': COMMENTS + } diff --git a/party/tests.py b/party/tests.py index 7ce503c2..c143092f 100644 --- a/party/tests.py +++ b/party/tests.py @@ -1,3 +1,890 @@ -from django.test import TestCase +#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. +import django +import unittest +import sys +import json +import copy +from django.test import TestCase, Client +from .testSamples import CountrySample, UserPartySample, OrganizationPartySample, InstitutionPartySample, ConsortiumPartySample, IpRangeSample, PartyAffiliationSample +from partner.testSamples import PartnerSample +from subscription.testSamples import SubscriptionSample +from authentication.testSamples import CredentialSample +from common.tests import TestGenericInterfaces, GenericGETOnlyTest, GenericCRUDTest, LoginRequiredGETOnlyTest, LoginRequiredCRUDTest, LoginRequiredTest, ManualTest, checkMatch, checkMatchDB +from http.cookies import SimpleCookie -# Create your tests here. +django.setup() +serverUrl = TestGenericInterfaces.getHost() + +# ---------------------- UNIT TEST FOR BASIC CRUD OPERATIONS ------------- + +# test for API end point /parties with party type user +# TODO: test for party type staff, admin as well +class UserPartyCRUDTest(LoginRequiredCRUDTest, TestCase): + sample = UserPartySample(serverUrl) + + def test_for_get_all(self): + # get all by type + url = self.getUrl(self.sample.url, 'partyType', self.sample.PARTY_TYPE_USER) + self.getAllHelper(url, 'partyId', self.credentialId) + +# test for API end point /parties with party type organization +# TODO: test for party type consortium +class OrganizationPartyCRUDTest(LoginRequiredCRUDTest, TestCase): + sample = OrganizationPartySample(serverUrl) + countrySample = CountrySample(serverUrl) + + def setUp(self): + super(OrganizationPartyCRUDTest,self).setUp() + countryId = self.countrySample.forcePost(self.countrySample.data) + self.sample.data['country']=self.sample.updateData['country']=countryId + + def test_for_get_all(self): + # get all by type + url = self.getUrl(self.sample.url, 'partyType', self.sample.PARTY_TYPE_ORG) + self.getAllHelper(url) + +# test for API end point /parties/countries +class CountryGETOnlyTest(GenericGETOnlyTest, TestCase): + sample = CountrySample(serverUrl) + + # only method is test_for_get_all + def test_for_get(self): + pass + +# test for API end point /parties/ipranges/ +class IpRangeCRUDTest(LoginRequiredCRUDTest, TestCase): + sample = IpRangeSample(serverUrl) + partySample = OrganizationPartySample(serverUrl) + countrySample = CountrySample(serverUrl) + partyId = None + + def setUp(self): + super(IpRangeCRUDTest,self).setUp() + countryId = self.countrySample.forcePost(self.countrySample.data) + self.partySample.data['country']=countryId + self.partyId = self.partySample.forcePost(self.partySample.data) + self.sample.data['partyId']=self.sample.updateData['partyId']=self.partyId + self.sample.invalidData_oversize['partyId'] = self.partyId + self.sample.invalidData_private['partyId'] = self.partyId + + def test_for_update_private_ipRange(self): + sample = self.sample + pk = sample.forcePost(sample.data) + url = self.getUrl(sample.url, sample.pkName, pk) + if self.apiKey: + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + + # the default content type for put is 'application/octet-stream' + res = self.client.put(url, json.dumps(sample.invalidData_private), content_type='application/json') + + self.assertEqual(res.status_code, 400) + is_private = json.loads(res.content)['IP Range'] == sample.getPrivateRangeIPErrorMessage() + self.assertTrue(is_private, 'Private_IpRange_Test_for_Update: Expected \ + IpRange to have a private IP address, but failed.') + + def test_for_update_oversize_ipRange(self): + sample = self.sample + pk = sample.forcePost(sample.data) + url = self.getUrl(sample.url, sample.pkName, pk) + if self.apiKey: + self.client.cookies = SimpleCookie({'apiKey':self.apiKey}) + + # the default content type for put is 'application/octet-stream' + res = self.client.put(url, json.dumps(sample.invalidData_oversize), content_type='application/json') + + self.assertEqual(res.status_code, 400) + is_oversize = json.loads(res.content)['IP Range'] == sample.getOutRangeIPErrorMessage() + self.assertTrue(is_oversize, 'Oversize_IpRange_Test_for_Update: Expected \ + IpRange to be out of range, but failed.') + + def test_for_create_private_ipRange(self): + sample = self.sample + url = self.getUrl(sample.url) + if self.apiKey: + self.client.cookies = SimpleCookie({'apikey': self.apiKey}) + + res = self.client.post(url, sample.invalidData_private) + + self.assertEqual(res.status_code, 400) + is_private = json.loads(res.content)['IP Range'] == sample.getPrivateRangeIPErrorMessage() + self.assertTrue(is_private, 'Private_IpRange_Test_for_Create: Expected \ + IpRange to have a private IP address, but failed.') + + def test_for_create_oversize_ipRange(self): + sample = self.sample + url = self.getUrl(sample.url) + if self.apiKey: + self.client.cookies = SimpleCookie({'apikey': self.apiKey}) + + res = self.client.post(url, sample.invalidData_oversize) + + self.assertEqual(res.status_code, 400) + is_oversize = json.loads(res.content)['IP Range'] == sample.getOutRangeIPErrorMessage() + self.assertTrue(is_oversize, 'Oversize_IpRange_Test_for_Create: Expected \ + IpRange to be out of range, but failed.') + + def test_for_get(self): + pass + + def getUrl(self, url, pkName = None, pk = None): + # need partyId for filter + fullUrl = super(IpRangeCRUDTest,self).getUrl(url, pkName, pk) + '&%s=%s' % ('partyId', self.partyId) + return fullUrl + +# test for API end point /parties/consortiums/ +# returns consortium info and associated credential info +class ConsortiumPartyCRUDTest(LoginRequiredCRUDTest, TestCase): + sample = ConsortiumPartySample(serverUrl) + partnerId = None + + def setUp(self): + super(ConsortiumPartyCRUDTest,self).setUp() + + countrySample = CountrySample(serverUrl) + countryId = countrySample.forcePost(countrySample.data) + self.sample.data['country']=self.sample.updateData['country']=countryId + + partnerSample = PartnerSample(serverUrl) + self.partnerId = partnerSample.forcePost(partnerSample.data) + + + def test_for_get(self): + sample = self.sample + # note this should not override the parent credential sample used for login + # so use a different name + consortiumCredentialSample = self.initForcePostConsortiumCredentialSample(serverUrl) + pk = self.forcePostConsortiumItem(consortiumCredentialSample) + + url = self.getUrl(sample.url, sample.pkName, pk) + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + + resObj = json.loads(res.content) + self.assertConsortiumItem(sample.data, consortiumCredentialSample.data, consortiumCredentialSample, resObj, sample.pkName, pk) + self.assertEqual(resObj[0]['hasIpRange'], False) + + # test for case where ip range is associated + ipRangeSample = IpRangeSample(serverUrl) + ipRangeSample.data['partyId'] = pk + ipRangeSample.forcePost(ipRangeSample.data) + + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + + resObj = json.loads(res.content) + self.assertEqual(resObj[0]['hasIpRange'], True) + + + # testcase 5 : throw error & does not save when credential serializer failed + def test_for_put_case5(self): + # create new consortium party and get partyId "pk" + sample = self.sample + pk = sample.forcePost(sample.data) + + # initialize data from credentialSample + consortiumCredentialSample = CredentialSample(serverUrl) + consortiumCredentialSample.updateData_invalid['partnerId'] = self.partnerId + consortiumCredentialSample.data['partnerId'] = self.partnerId + consortiumCredentialSample.data['partyId'] = pk + consortiumCredentialSample.updateData_invalid['partyId'] = pk + + # construct credential on Database + consortiumCredentialSample.forcePost(consortiumCredentialSample.data) + + # combine updateData from partySample and invalid updateData from CredentialSample into one data "putData" + putData = self.composeConsortiumPostData(sample.updateData, consortiumCredentialSample.updateData_invalid) + url = self.getUrl(sample.url, sample.pkName, pk) + + # pushing putData into Database + res = self.client.put(url, json.dumps(putData), content_type='application/json') + + # test failing to push into the database + self.assertEqual(res.status_code, 400) + # test data from party on Database == sample.data + self.assertEqual(checkMatchDB(sample.data, sample.model, sample.pkName, pk), True) + + # hashing the password and test data from credential on Database == consortiumCredentialSample.data + consortiumCredentialSample.data['password'] = consortiumCredentialSample.hashPassword(consortiumCredentialSample.data['password']) + self.assertEqual(checkMatchDB(consortiumCredentialSample.data, consortiumCredentialSample.model, sample.pkName, pk), True) + + # testcase 4 : throw error & does not save when party serializer failed + def test_for_put_case4(self): + # create new consortium party and get partyId "pk" + sample = self.sample + pk = sample.forcePost(sample.data) + + # initialize data from credentialSample + consortiumCredentialSample = CredentialSample(serverUrl) + consortiumCredentialSample.updateData['partnerId'] = self.partnerId + consortiumCredentialSample.data['partnerId'] = self.partnerId + consortiumCredentialSample.data['partyId'] = pk + consortiumCredentialSample.updateData['partyId'] = pk + + # construct credential on Database + consortiumCredentialSample.forcePost(consortiumCredentialSample.data) + + # combine invalid updateData from partySample and updateData from CredentialSample into one data "putData" + putData = self.composeConsortiumPostData(sample.updateData_invalid, consortiumCredentialSample.updateData) + url = self.getUrl(sample.url, sample.pkName, pk) + + # pushing putData into Database + res = self.client.put(url, json.dumps(putData), content_type='application/json') + + # test failing to push into the database + self.assertEqual(res.status_code, 400) + # test data from party on Database == sample.data + self.assertEqual(checkMatchDB(sample.data, sample.model, sample.pkName, pk), True) + + # hashing the password and test data from credential on Database == consortiumCredentialSample.data + consortiumCredentialSample.data['password'] = consortiumCredentialSample.hashPassword(consortiumCredentialSample.data['password']) + self.assertEqual(checkMatchDB(consortiumCredentialSample.data, consortiumCredentialSample.model, sample.pkName, pk), True) + + # testcase 3: update party only + def test_for_put_case3(self): + # create new consortium party and get partyId "pk" + sample = self.sample + pk = sample.forcePost(sample.data) + + # set putData to exclude update data from credential + putData = copy.deepcopy(sample.updateData) + putData['partyId'] = pk + + url = self.getUrl(sample.url, sample.pkName, pk) + + # pushing putData into Database + res = self.client.put(url, json.dumps(putData), content_type='application/json') + + # test pushing successfully into the database + self.assertEqual(res.status_code, 200) + # test party data from the database = party sample's updateData + self.assertEqual(checkMatchDB(sample.updateData, sample.model, sample.pkName, pk), True) + + # testcase 2: throw error if credential not exist & username/pwd not exist + def test_for_put_case2(self): + # create new consortium party and get partyId "pk" + sample = self.sample + pk = sample.forcePost(sample.data) + + # initialize data from credentialSample + consortiumCredentialSample = CredentialSample(serverUrl) + consortiumCredentialSample.updateData_no_pwd['partnerId'] = self.partnerId + consortiumCredentialSample.updateData_no_pwd['partyId'] = pk + + # combine updateData from partySample and updateData w/o pwd from CredentialSample into one data "putData" + putData = self.composeConsortiumPostData(sample.updateData, consortiumCredentialSample.updateData_no_pwd) + url = self.getUrl(sample.url, sample.pkName, pk) + + # pushing putData into Database + res = self.client.put(url, json.dumps(putData), content_type='application/json') + + # test failing to push into the database + self.assertEqual(res.status_code, 400) + # test party data from the database = party sample's data + self.assertEqual(checkMatchDB(sample.data, sample.model, sample.pkName, pk), True) + # test credential does not exist + self.assertEqual(checkMatchDB(None, consortiumCredentialSample.model, sample.pkName, pk), True) + + # testcase 1: create credential when no exist + def test_for_put_case1(self): + # create new consortium party and get partyId "pk" + sample = self.sample + pk = sample.forcePost(sample.data) + + # initialize data from credentialSample + consortiumCredentialSample = CredentialSample(serverUrl) + consortiumCredentialSample.updateData['partnerId'] = self.partnerId + consortiumCredentialSample.data['partyId'] = pk + consortiumCredentialSample.updateData['partyId'] = pk + + # combine two updateData from partySample and CredentialSample into one data "putData" + putData = self.composeConsortiumPostData(sample.updateData, consortiumCredentialSample.updateData) + url = self.getUrl(sample.url, sample.pkName, pk) + + # pushing putData into Database + res = self.client.put(url, json.dumps(putData), content_type='application/json') + + # test pushing successfully into the database + self.assertEqual(res.status_code, 200) + # test if party data from the database = party sample's updateData + self.assertEqual(checkMatchDB(sample.updateData, sample.model, sample.pkName, pk), True) + + # hashing the password and test data from credential on Database == consortiumCredentialSample.updateData + consortiumCredentialSample.updateData['password'] = consortiumCredentialSample.hashPassword(consortiumCredentialSample.updateData['password']) + self.assertEqual(checkMatchDB(consortiumCredentialSample.updateData, consortiumCredentialSample.model, sample.pkName, pk), True) + + def test_for_get_all(self): + pass + + # API end point requires to create consortium and its user together + def test_for_create(self): + sample = self.sample + consortiumCredentialSample = CredentialSample(serverUrl) + consortiumCredentialSample.data['partnerId'] = self.partnerId + postData = self.composeConsortiumPostData(sample.data, consortiumCredentialSample.data) + + url = self.getUrl(sample.url) + res = self.client.post(url, postData) + + self.assertEqual(res.status_code, 201) + resObj = json.loads(res.content) + pk = resObj[0][sample.pkName] + # manipulate sample data to match the test condition + consortiumCredentialSample.data['partyId'] = pk + self.assertConsortiumItem(sample.data, consortiumCredentialSample.data, consortiumCredentialSample, resObj, sample.pkName, pk) + + def test_for_update(self): + sample = self.sample + consortiumCredentialSample = self.initForcePostConsortiumCredentialSample(serverUrl) + pk = self.forcePostConsortiumItem(consortiumCredentialSample) + + # create put data with both party update data and credential update data + putData = self.composeConsortiumPostData(sample.updateData, consortiumCredentialSample.updateData) + + url = self.getUrl(sample.url, sample.pkName, pk) + + # the default content type for put is 'application/octet-stream' + res = self.client.put(url, json.dumps(putData), content_type='application/json') + + self.assertEqual(res.status_code, 200) + resObj = json.loads(res.content) + # manipulate sample update data to match the test condition + self.assertConsortiumItem(sample.updateData, consortiumCredentialSample.updateData, consortiumCredentialSample, resObj, sample.pkName, pk) + + def initForcePostConsortiumCredentialSample(self, serverUrl): + consortiumCredentialSample = CredentialSample(serverUrl) + + consortiumCredentialSample.data['partnerId'] = consortiumCredentialSample.updateData['partnerId'] = self.partnerId + return consortiumCredentialSample + + # alternative method is to post data by call create API + def forcePostConsortiumItem(self, consortiumCredentialSample): + sample = self.sample + pk = sample.forcePost(sample.data) + + consortiumCredentialSample.data['partyId'] = consortiumCredentialSample.updateData['partyId'] = pk + consortiumCredentialSample.forcePost(consortiumCredentialSample.data) + + return pk + + # create post/put data with both consortium data and credential data + def composeConsortiumPostData(self, consortiumSampleData, consortiumCredentialSampleData): + postData = copy.deepcopy(consortiumSampleData) + + for key in consortiumCredentialSampleData: + if consortiumCredentialSampleData[key] is not None: + postData[key] = consortiumCredentialSampleData[key] + + return postData + + def assertConsortiumItem(self, consortiumSampleData, consortiumCredentialSampleData, consortiumCredentialSample, resObj, pkName, pk): + self.assertEqual(checkMatch(consortiumSampleData, resObj[0], pkName, pk), True) + self.assertEqual(checkMatchDB(consortiumSampleData, self.sample.model, pkName, pk), True) + # manipulate sample data to match the test condition + consortiumCredentialSampleData['password'] = consortiumCredentialSample.hashPassword(consortiumCredentialSampleData['password']) + self.assertEqual(checkMatch(consortiumCredentialSampleData, resObj[1], pkName, pk), True) + self.assertEqual(checkMatchDB(consortiumCredentialSampleData, consortiumCredentialSample.model, pkName, pk), True) + +# test for API end point /parties/institutions/ +# returns organization info and associated credential info +# difference between this API and /parties API is this API will include +# affliated consortium info and credential info +class InstitutionPartyCRUDTest(LoginRequiredCRUDTest, TestCase): + sample = InstitutionPartySample(serverUrl) + partnerId = None + consortiumPartyId = None + + def setUp(self): + super(InstitutionPartyCRUDTest,self).setUp() + consortiumSample = ConsortiumPartySample(serverUrl) + + countrySample = CountrySample(serverUrl) + countryId = countrySample.forcePost(countrySample.data) + self.sample.data['country']=self.sample.updateData['country']=countryId + consortiumSample.data['country']=countryId + + partnerSample = PartnerSample(serverUrl) + self.partnerId = partnerSample.forcePost(partnerSample.data) + + self.consortiumPartyId = consortiumSample.forcePost(consortiumSample.data) + + def test_for_get(self): + sample = self.sample + # note this should not override the parent credential sample used for login + # so use a different name + institutionCredentialSample = self.initForcePostInstitutionCredentialSample(serverUrl) + pk = self.forcePostInstitutionItem(institutionCredentialSample) + + url = self.getUrl(sample.url, sample.pkName, pk) + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + + resObj = json.loads(res.content) + self.assertInstitutionItem(sample.data, institutionCredentialSample.data, institutionCredentialSample, resObj, sample.pkName, pk) + self.assertEqual(resObj[0]['hasIpRange'], False) + self.assertEqual(resObj[0]['consortiums'][0], self.consortiumPartyId) + + # test for case where ip range is associated + ipRangeSample = IpRangeSample(serverUrl) + ipRangeSample.data['partyId'] = pk + ipRangeSample.forcePost(ipRangeSample.data) + + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + + resObj = json.loads(res.content) + self.assertEqual(resObj[0]['hasIpRange'], True) + + #update country to null and expect an error + def test_for_put_case6(self): + # create new institution party and get partyId "pk" + sample = self.sample + pk = sample.forcePost(sample.data) + + # set putData to exclude update data from credential + putData = copy.deepcopy(sample.updateData) + putData['country'] = None + url = self.getUrl(sample.url, sample.pkName, pk) + + # pushing putData into Database + res = self.client.put(url, json.dumps(putData), content_type='application/json') + + # test pushing successfully into the database + # need to check this + self.assertEqual(res.status_code, 400) + # test party data from the database = party sample's data + self.assertEqual(checkMatchDB(sample.data, sample.model, sample.pkName, pk), True) + + # testcase 5 : throw error & does not save when credential serializer failed + def test_for_put_case5(self): + # create new consortium party and get partyId "pk" + sample = self.sample + pk = sample.forcePost(sample.data) + + # initialize data from credentialSample + institutionCredentialSample = CredentialSample(serverUrl) + institutionCredentialSample.updateData_invalid['partnerId'] = self.partnerId + institutionCredentialSample.data['partnerId'] = self.partnerId + institutionCredentialSample.data['partyId'] = pk + institutionCredentialSample.updateData_invalid['partyId'] = pk + + # construct credential on Database + institutionCredentialSample.forcePost(institutionCredentialSample.data) + + # combine updateData from partySample and invalid updateData from CredentialSample into one data "putData" + putData = self.composeInstitutionPostData(sample.updateData, institutionCredentialSample.updateData_invalid) + url = self.getUrl(sample.url, sample.pkName, pk) + + # pushing putData into Database + res = self.client.put(url, json.dumps(putData), content_type='application/json') + + # test failing to push into the database + self.assertEqual(res.status_code, 400) + # test data from party on Database == sample.data + self.assertEqual(checkMatchDB(sample.data, sample.model, sample.pkName, pk), True) + + # hashing the password and test data from credential on Database == institutionCredentialSample.data + institutionCredentialSample.data['password'] = institutionCredentialSample.hashPassword(institutionCredentialSample.data['password']) + self.assertEqual(checkMatchDB(institutionCredentialSample.data, institutionCredentialSample.model, sample.pkName, pk), True) + + # testcase 4 : throw error & does not save when party serializer failed + def test_for_put_case4(self): + # create new consortium party and get partyId "pk" + sample = self.sample + pk = sample.forcePost(sample.data) + + # initialize data from credentialSample + institutionCredentialSample = CredentialSample(serverUrl) + institutionCredentialSample.updateData['partnerId'] = self.partnerId + institutionCredentialSample.data['partnerId'] = self.partnerId + institutionCredentialSample.data['partyId'] = pk + institutionCredentialSample.updateData['partyId'] = pk + + # construct credential on Database + institutionCredentialSample.forcePost(institutionCredentialSample.data) + + # combine invalid updateData from partySample and updateData from CredentialSample into one data "putData" + putData = self.composeInstitutionPostData(sample.updateData_invalid, institutionCredentialSample.updateData) + url = self.getUrl(sample.url, sample.pkName, pk) + + # pushing putData into Database + res = self.client.put(url, json.dumps(putData), content_type='application/json') + + # test failing to push into the database + self.assertEqual(res.status_code, 400) + # test data from party on Database == sample.data + self.assertEqual(checkMatchDB(sample.data, sample.model, sample.pkName, pk), True) + + # hashing the password and test data from credential on Database == institutionCredentialSample.data + institutionCredentialSample.data['password'] = institutionCredentialSample.hashPassword(institutionCredentialSample.data['password']) + self.assertEqual(checkMatchDB(institutionCredentialSample.data, institutionCredentialSample.model, sample.pkName, pk), True) + + # testcase 3: update party only + def test_for_put_case3(self): + # create new consortium party and get partyId "pk" + sample = self.sample + pk = sample.forcePost(sample.data) + + # set putData to exclude update data from credential + putData = copy.deepcopy(sample.updateData) + putData['partyId'] = pk + + url = self.getUrl(sample.url, sample.pkName, pk) + + # pushing putData into Database + res = self.client.put(url, json.dumps(putData), content_type='application/json') + + # test pushing successfully into the database + self.assertEqual(res.status_code, 200) + # test party data from the database = party sample's updateData + self.assertEqual(checkMatchDB(sample.updateData, sample.model, sample.pkName, pk), True) + + # testcase 2: throw error if credential not exist & username/pwd not exist + def test_for_put_case2(self): + # create new consortium party and get partyId "pk" + sample = self.sample + pk = sample.forcePost(sample.data) + + # initialize data from credentialSample + institutionCredentialSample = CredentialSample(serverUrl) + institutionCredentialSample.updateData_no_pwd['partnerId'] = self.partnerId + institutionCredentialSample.updateData_no_pwd['partyId'] = pk + + # combine updateData from partySample and updateData w/o pwd from CredentialSample into one data "putData" + putData = self.composeInstitutionPostData(sample.updateData, institutionCredentialSample.updateData_no_pwd) + url = self.getUrl(sample.url, sample.pkName, pk) + + # pushing putData into Database + res = self.client.put(url, json.dumps(putData), content_type='application/json') + + # test failing to push into the database + self.assertEqual(res.status_code, 400) + # test party data from the database = party sample's data + self.assertEqual(checkMatchDB(sample.data, sample.model, sample.pkName, pk), True) + # test credential does not exist + self.assertEqual(checkMatchDB(None, institutionCredentialSample.model, sample.pkName, pk), True) + + # testcase 1: create credential when no exist + def test_for_put_case1(self): + # create new consortium party and get partyId "pk" + sample = self.sample + pk = sample.forcePost(sample.data) + + # initialize data from credentialSample + institutionCredentialSample = CredentialSample(serverUrl) + institutionCredentialSample.updateData['partnerId'] = self.partnerId + institutionCredentialSample.data['partyId'] = pk + institutionCredentialSample.updateData['partyId'] = pk + + # combine two updateData from partySample and CredentialSample into one data "putData" + putData = self.composeInstitutionPostData(sample.updateData, institutionCredentialSample.updateData) + url = self.getUrl(sample.url, sample.pkName, pk) + + # pushing putData into Database + res = self.client.put(url, json.dumps(putData), content_type='application/json') + + # test pushing successfully into the database + self.assertEqual(res.status_code, 200) + # test if party data from the database = party sample's updateData + self.assertEqual(checkMatchDB(sample.updateData, sample.model, sample.pkName, pk), True) + + # hashing the password and test data from credential on Database == institutionCredentialSample.updateData + institutionCredentialSample.updateData['password'] = institutionCredentialSample.hashPassword(institutionCredentialSample.updateData['password']) + self.assertEqual(checkMatchDB(institutionCredentialSample.updateData, institutionCredentialSample.model, sample.pkName, pk), True) + + def test_for_get_all(self): + pass + + # Post test for creating a new consortium with no country + def test_for_post(self): + sample = self.sample + institutionCredentialSample = CredentialSample(serverUrl) + institutionCredentialSample.data['partnerId'] = self.partnerId + postData = self.composeInstitutionPostData(sample.data, institutionCredentialSample.data) + print(postData) + postData.pop('country',None) + print(postData) + url = self.getUrl(sample.url) + res = self.client.post(url, postData, content_type='application/json') + + self.assertEqual(res.status_code, 400) + + # API end point requires to create consortium and its user together + def test_for_create(self): + sample = self.sample + institutionCredentialSample = CredentialSample(serverUrl) + institutionCredentialSample.data['partnerId'] = self.partnerId + postData = self.composeInstitutionPostData(sample.data, institutionCredentialSample.data) + + url = self.getUrl(sample.url) + res = self.client.post(url, postData) + + self.assertEqual(res.status_code, 201) + resObj = json.loads(res.content) + pk = resObj[0][sample.pkName] + # manipulate sample data to match the test condition + institutionCredentialSample.data['partyId'] = pk + self.assertInstitutionItem(sample.data, institutionCredentialSample.data, institutionCredentialSample, resObj, sample.pkName, pk) + + def test_for_update(self): + sample = self.sample + institutionCredentialSample = self.initForcePostInstitutionCredentialSample(serverUrl) + pk = self.forcePostInstitutionItem(institutionCredentialSample) + + # create put data with both party update data and credential update data + putData = self.composeInstitutionPostData(sample.updateData, institutionCredentialSample.updateData) + + url = self.getUrl(sample.url, sample.pkName, pk) + + # the default content type for put is 'application/octet-stream' + res = self.client.put(url, json.dumps(putData), content_type='application/json') + + self.assertEqual(res.status_code, 200) + resObj = json.loads(res.content) + self.assertInstitutionItem(sample.updateData, institutionCredentialSample.updateData, institutionCredentialSample, resObj, sample.pkName, pk) + + def initForcePostInstitutionCredentialSample(self, serverUrl): + institutionCredentialSample = CredentialSample(serverUrl) + + institutionCredentialSample.data['partnerId'] = institutionCredentialSample.updateData['partnerId'] = self.partnerId + + return institutionCredentialSample + + # alternative method is to post data by call create API + def forcePostInstitutionItem(self, institutionCredentialSample): + sample = self.sample + pk = sample.forcePost(sample.data) + + institutionCredentialSample.data['partyId'] = institutionCredentialSample.updateData['partyId'] = pk + institutionCredentialSample.forcePost(institutionCredentialSample.data) + + partyAffiliationSample = PartyAffiliationSample(serverUrl) + partyAffiliationSample.setParentId(self.consortiumPartyId) + partyAffiliationSample.setChildId(pk) + partyAffiliationSample.forcePost(partyAffiliationSample.data) + + return pk + + # create post/put data with both consortium data and credential data + def composeInstitutionPostData(self, institutionSampleData, institutionCredentialSampleData): + postData = copy.deepcopy(institutionSampleData) + + for key in institutionCredentialSampleData: + if institutionCredentialSampleData[key] is not None: + postData[key] = institutionCredentialSampleData[key] + + return postData + + def assertInstitutionItem(self, institutionSampleData, institutionCredentialSampleData, institutionCredentialSample, resObj, pkName, pk): + self.assertEqual(checkMatch(institutionSampleData, resObj[0], pkName, pk), True) + self.assertEqual(checkMatchDB(institutionSampleData, self.sample.model, pkName, pk), True) + + # manipulate sample data to match the test condition + institutionCredentialSampleData['password'] = institutionCredentialSample.hashPassword(institutionCredentialSampleData['password']) + self.assertEqual(checkMatch(institutionCredentialSampleData, resObj[1], pkName, pk), True) + self.assertEqual(checkMatchDB(institutionCredentialSampleData, institutionCredentialSample.model, pkName, pk), True) + +# test for end point /parties/affiliations/ +# get_all and update methods unavailable, all available methods need to be override +# so no need to inherit from LoginRequiredCRUDTest +class PartyAffiliationCRUDTest(LoginRequiredTest, TestCase): + sample = PartyAffiliationSample(serverUrl) + parentPartySample = ConsortiumPartySample(serverUrl) + childPartySample = InstitutionPartySample(serverUrl) + + def setUp(self): + super(PartyAffiliationCRUDTest,self).setUp() + + countrySample = CountrySample(serverUrl) + countryId = countrySample.forcePost(countrySample.data) + self.parentPartySample.data['country']=self.childPartySample.data['country']=countryId + + parentPartyId = self.parentPartySample.forcePost(self.parentPartySample.data) + childPartyId = self.childPartySample.forcePost(self.childPartySample.data) + + self.sample.setParentId(parentPartyId) + self.sample.setChildId(childPartyId) + + def test_for_get(self): + sample = self.sample + sample.forcePost(sample.data) + + parentPartyId = sample.getParentId() + parentPartyType = self.parentPartySample.getPartyType() + + childPartyId = sample.getChildId() + childPartyType = self.childPartySample.getPartyType() + + # get by consortium (parent organization) + url = self.getUrl(sample.url) + url = '%s&partyId=%s&partyType=%s' % (url, parentPartyId, parentPartyType) + + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + resObj = json.loads(res.content) + self.assertEqual(checkMatch(self.childPartySample.data, resObj, 'partyId', childPartyId), True) + self.assertEqual(resObj[0]['consortiums'][0], parentPartyId) + + # get by institution (child organization) + url = self.getUrl(sample.url) + url = '%s&partyId=%s&partyType=%s' % (url, childPartyId, childPartyType) + + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + self.assertEqual(checkMatch(self.parentPartySample.data, json.loads(res.content), 'partyId', parentPartyId), True) + + def test_for_create(self): + sample = self.sample + parentPartyId = sample.getParentId() + childPartyId = sample.getChildId() + + url = self.getUrl(sample.url) + url = '%s&parentPartyId=%s&childPartyId=%s' % (url, parentPartyId, childPartyId) + + res = self.client.post(url, sample.data) + + self.assertEqual(res.status_code, 201) + # return content is child organization data + # resObj = json.loads(res.content) + # self.assertEqual(checkMatch(self.childPartySample.data, resObj, 'partyId', childPartyId), True) + # self.assertEqual(resObj['consortiums'][0], parentPartyId) + self.assertIsNotNone(TestGenericInterfaces.forceGet(sample.model,'parentPartyId',parentPartyId)) + self.assertIsNotNone(TestGenericInterfaces.forceGet(sample.model,'childPartyId',childPartyId)) + + def test_for_delete(self): + sample = self.sample + pk = sample.forcePost(sample.data) + + parentPartyId = sample.getParentId() + childPartyId = sample.getChildId() + + url = self.getUrl(sample.url) + url = '%s&parentPartyId=%s&childPartyId=%s' % (url, parentPartyId, childPartyId) + + res = self.client.delete(url) + + self.assertIsNone(TestGenericInterfaces.forceGet(sample.model,sample.pkName,pk)) + +# ----------------- END OF BASIC CRUD OPERATIONS ---------------------- + +# test for API end point /parties/org/ +# get organization by ip +class GetOrgByIpTest(TestCase): + sample = IpRangeSample(serverUrl) + partySample = OrganizationPartySample(serverUrl) + countrySample = CountrySample(serverUrl) + url = None + + def setUp(self): + countryId = self.countrySample.forcePost(self.countrySample.data) + self.partySample.data['country']=countryId + self.partyId = self.partySample.forcePost(self.partySample.data) + self.sample.data['partyId']=self.sample.updateData['partyId']=self.partyId + self.sample.forcePost(self.sample.data) + self.url = serverUrl + 'parties/org/?ip=%s' % self.sample.getInRangeIp() + + def test_for_get_by_ip(self): + res = self.client.get(self.url) + + self.assertEqual(res.status_code, 200) + # The raw response will be bytes so need to convert to string and then compare + self.assertEqual(res.content.decode(), self.partySample.getName()) + +# test for API end point /parties/orgstatus/ +# get organization and its subscription status to partner by ip +class GetOrgAndSubStatusTest(TestCase): + sample = IpRangeSample(serverUrl) + partnerSample = PartnerSample(serverUrl) + partySample = OrganizationPartySample(serverUrl) + countrySample = CountrySample(serverUrl) + subscriptionSample = SubscriptionSample(serverUrl) + url = None + partnerId = None + partyId = None + + def setUp(self): + self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) + countryId = self.countrySample.forcePost(self.countrySample.data) + self.partySample.data['country']=countryId + self.partyId = self.partySample.forcePost(self.partySample.data) + self.sample.data['partyId']=self.partyId + self.sample.forcePost(self.sample.data) + self.url = serverUrl + 'parties/orgstatus/?ip=%s&partnerId=%s' % (self.sample.getInRangeIp(), self.partnerId) + + def test_for_unsubscribed(self): + res = self.client.get(self.url) + + self.assertEqual(res.status_code, 200) + resObj = json.loads(res.content) + self.assertEqual(resObj['name'], self.partySample.getName()) + self.assertEqual(resObj['status'], 'not subscribed') + + def test_for_subscribed(self): + self.subscriptionSample.setPartnerId(self.partnerId) + self.subscriptionSample.setPartyId(self.partyId) + self.subscriptionSample.forcePost(self.subscriptionSample.data) + + res = self.client.get(self.url) + + self.assertEqual(res.status_code, 200) + resObj = json.loads(res.content) + self.assertEqual(resObj['name'], self.partySample.getName()) + self.assertEqual(resObj['status'], 'subscribed') + +# test for API end point /parties/organizations/ +# get subscribed organization list by partner +class GetSubOrgListByPartnerTest(TestCase): + sample = IpRangeSample(serverUrl) + partnerSample = PartnerSample(serverUrl) + partySample = OrganizationPartySample(serverUrl) + countrySample = CountrySample(serverUrl) + subscriptionSample = SubscriptionSample(serverUrl) + url = None + + def setUp(self): + partnerId = self.partnerSample.forcePost(self.partnerSample.data) + + countryId = self.countrySample.forcePost(self.countrySample.data) + + self.partySample.data['country']=countryId + partyId = self.partySample.forcePost(self.partySample.data) + + self.sample.data['partyId']=partyId + self.sample.forcePost(self.sample.data) + + self.subscriptionSample.setPartnerId(partnerId) + self.subscriptionSample.setPartyId(partyId) + self.subscriptionSample.forcePost(self.subscriptionSample.data) + + self.url = serverUrl + 'parties/organizations/?partnerId=%s' % partnerId + + def test_for_list(self): + res = self.client.get(self.url) + + self.assertEqual(res.status_code, 200) + resObj = json.loads(res.content)[0] + self.assertEqual(resObj[0], self.partySample.getName()) + self.assertEqual(resObj[1], self.countrySample.getName()) + +# test for API end point /parties/usage/ +# test in manualTests.py +# deprecated and replaced by POL functions +# class GetUsageRequestTest(ManualTest, TestCase): +# path = "/parties/usage/" +# testMethodStr = "running ./manage.py test party.manualTests (the UI invocation from librarians is not public yet)" + +# test for API end point /parties/consortiuminstitutions/{consortiumId} +# this endpoint is not working + +print("Running unit tests on party web services API.........") + +if __name__ == '__main__': + sys.argv[1:] = [] + unittest.main() + ret = not runner.run(suite).wasSuccessful() + sys.exit(ret) diff --git a/party/urls.py b/party/urls.py index 2f022301..f4ef2a67 100644 --- a/party/urls.py +++ b/party/urls.py @@ -8,7 +8,7 @@ # Basic CRUD operations url(r'^countries/$', views.CountryView.as_view()), url(r'^organizations/$', views.OrganizationView.as_view()),#PW-265 - + url(r'^ipranges/$', views.IpRangeCRUD.as_view()), #https://demoapi.arabidopsis.org/parties/?partyId=31627&credentialId=33197&secretKey=kZ5yK8hdSbncXwD4%2F2DJOxqFUds%3D url(r'^$', views.PartyCRUD.as_view()), @@ -20,11 +20,11 @@ #https://demoapi.arabidopsis.org/parties/institutions/?partyId=31627&credentialId=33197&secretKey=kZ5yK8hdSbncXwD4%2F2DJOxqFUds%3D url(r'^institutions/$', views.InstitutionCRUD.as_view()),#PW-161 url(r'^affiliations/$', views.AffiliationCRUD.as_view()), - + #PW-277 - accept IP, returns organization #https://demoapi.arabidopsis.org/parties/institutions/?IP=31627&credentialId=33197&secretKey=kZ5yK8hdSbncXwD4%2F2DJOxqFUds%3D url(r'^org/$', views.PartyOrgCRUD.as_view()), url(r'^orgstatus/$', views.PartyOrgStatusView.as_view()), - + ] urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/party/views.py b/party/views.py index 0a0a8a50..73314e75 100644 --- a/party/views.py +++ b/party/views.py @@ -20,8 +20,7 @@ from common.permissions import isPhoenix from common.permissions import ApiKeyPermission -import hashlib -import datetime +from django.utils import timezone from authentication.serializers import CredentialSerializer, CredentialSerializerNoPassword from genericpath import exists from django.db import connection @@ -53,8 +52,6 @@ def get_queryset(self): return super(PartyCRUD, self).get_queryset().filter(partyType=partyType) return [] - - # /org/ class PartyOrgCRUD(GenericCRUDView): requireApiKey = False @@ -137,7 +134,7 @@ def get(self, request, format=None): partyList = [] #SELECT partyId FROM phoenix_api.Subscription where partnerId = 'tair'; - now =datetime.datetime.now() + now = timezone.now() objs = Subscription.objects.all().filter(partnerId=partnerId).filter(startDate__lte=now).filter(endDate__gte=now).values('partyId') for entry in objs: partyList.append(entry['partyId']) @@ -292,52 +289,64 @@ def put(self, request, format=None): #http://stackoverflow.com/questions/18930234/django-modifying-the-request-object data = request.data.copy() - params = request.GET - - if not params: - return Response({'error':'PUT parties/consortiums/ does not allow update without query parameters'},status=status.HTTP_400_BAD_REQUEST) + out = [] - if 'partyId' not in request.data: - return Response({'error':'PUT parties/consortiums/ partyId required'},status=status.HTTP_400_BAD_REQUEST) + if 'partyId' not in data: + return Response({'error': 'PUT parties/consortiums/ partyId required'}, status=status.HTTP_400_BAD_REQUEST) - consortiumId = request.data['partyId'] + consortiumId = data['partyId'] #get party party = Party.objects.get(partyId = consortiumId) - partySerializer = PartySerializer(party, data=data) - if 'email' in data: - for partyId in Credential.objects.all().filter(email=data['email']).filter(partnerId='phoenix').values_list('partyId', flat=True): - if Party.objects.all().filter(partyId=partyId).filter(partyType='consortium').exists(): - return Response({'error':'This email is already used by another consortium.'}, status=status.HTTP_400_BAD_REQUEST) - if 'password' in request.data: - if (not data['password'] or data['password'] == ""): - return Response({'error': 'PUT parties/consortiums/ password must not be empty'}, status=status.HTTP_400_BAD_REQUEST) + + hasPartySerializerParam = any(param in PartySerializer.Meta.fields for param in data if param != 'partyId') + hasCredentialSerializerParam = any(param in CredentialSerializer.Meta.fields for param in data if param != 'partyId') + + if hasCredentialSerializerParam: + + partner = Partner.objects.get(partnerId='phoenix') + try: + credential = Credential.objects.get(partyId=party, partnerId=partner) + except: + for param in ('username', 'password'): + if param not in data: + return Response({'error', 'username and password required.'}, status=status.HTTP_400_BAD_REQUEST) + credential = Credential(partyId=party, partnerId=partner) + + if 'email' in data: + partyIdList = Credential.objects.all().filter(email=data['email']).filter(partnerId='phoenix').values_list('partyId', flat=True) + for partyId in partyIdList: + has_partyId = Party.objects.all().filter(partyId=partyId).filter(partyType='consortium').exists() + if has_partyId: + return Response({'error':'This email is already used by another consortium.'}, status=status.HTTP_400_BAD_REQUEST) + + if 'password' in data: + if data['password'] == None or data['password'] == "": + return Response({'error': 'PUT parties/consortiums/ password must not be empty'}, status=status.HTTP_400_BAD_REQUEST) + else: + newPwd = data['password'] + data['password'] = Credential.generatePasswordHash(newPwd) + credentialSerializer = CredentialSerializer(credential, data=data, partial=True) else: - newPwd = data['password'] - data['password'] = hashlib.sha1(newPwd).hexdigest() - try: - credential = Credential.objects.get(partyId=party) - credentialSerializer = CredentialSerializer(credential, data=data) - except Credential.DoesNotExist: - data['partnerId'] = 'phoenix' - credentialSerializer = CredentialSerializer(data=data) + credentialSerializer = CredentialSerializerNoPassword(credential, data=data, partial=True) - else: - credentialSerializer = CredentialSerializerNoPassword(credential, data=data, partial=True) #?? + partySerializer = PartySerializer(party, data=data, partial=True) - out = [] - if partySerializer.is_valid(): + if hasPartySerializerParam and not partySerializer.is_valid(): + return Response(partySerializer.errors, status=status.HTTP_400_BAD_REQUEST) + if hasCredentialSerializerParam and not credentialSerializer.is_valid(): + return Response(credentialSerializer.errors, status=status.HTTP_400_BAD_REQUEST) + + if hasPartySerializerParam: partySerializer.save() partyReturnData = partySerializer.data out.append(partyReturnData) - if credentialSerializer.is_valid(): - credentialSerializer.save() - credentialReturnData = credentialSerializer.data - out.append(credentialReturnData) - return HttpResponse(json.dumps(out), content_type="application/json") - else: - return Response(credentialSerializer.errors, status=status.HTTP_400_BAD_REQUEST) - else: - return Response(partySerializer.errors, status=status.HTTP_400_BAD_REQUEST) + + if hasCredentialSerializerParam: + credentialSerializer.save() + credentialReturnData = credentialSerializer.data + out.append(credentialReturnData) + + return HttpResponse(json.dumps(out), content_type="application/json") #PW-161 POST https://demoapi.arabidopsis.org/parties/consortiums/?credentialId=2&secretKey=7DgskfEF7jeRGn1h%2B5iDCpvIkRA%3D #NOTE ?/ in parties/consortiums/?credentialId= @@ -387,7 +396,7 @@ def post(self, request, format=None): if pwd == True: newPwd = data['password'] - data['password'] = hashlib.sha1(newPwd).hexdigest() + data['password'] = Party.generatePasswordHash(newPwd) credentialSerializer = CredentialSerializer(data=data) else: credentialSerializer = CredentialSerializerNoPassword(data=data) @@ -480,55 +489,66 @@ def get(self, request, format=None): # output data from both tables for a given partyId def put(self, request, format=None): if not isPhoenix(request): - return HttpResponse({'error':'credentialId and secretKey query parameters missing or invalid'},status=status.HTTP_400_BAD_REQUEST) + return HttpResponse({'error':'credentialId and secretKey query parameters missing or invalid'},status=status.HTTP_400_BAD_REQUEST) - params = request.GET data = request.data.copy() + out = [] - if not params: - return Response({'error':'does not allow update without query parameters'},status=status.HTTP_400_BAD_REQUEST) - - if 'partyId' not in request.data: + if 'partyId' not in data: return Response({'error':'partyId (aka institutionId) required'},status=status.HTTP_400_BAD_REQUEST) - institutionId = request.data['partyId'] - #get party + institutionId = data['partyId'] party = Party.objects.get(partyId = institutionId) - partySerializer = PartySerializer(party, data=data) - if 'email' in data: - for partyId in Credential.objects.all().filter(email=data['email']).filter(partnerId='phoenix').values_list('partyId', flat=True): - if Party.objects.all().filter(partyId=partyId).filter(partyType='organization').exists(): - return Response({'error':'This email is already used by another institution.'}, status=status.HTTP_400_BAD_REQUEST) - if 'password' in request.data: - if (not data['password'] or data['password'] == ""): - return Response({'error': 'PUT parties/institutions/ password must not be empty'}, status=status.HTTP_400_BAD_REQUEST) + hasPartySerializerParam = any(param in PartySerializer.Meta.fields for param in data if param != 'partyId') + hasCredentialSerializerParam = any(param in CredentialSerializer.Meta.fields for param in data if param != 'partyId') + + if hasCredentialSerializerParam: + + partner = Partner.objects.get(partnerId='phoenix') + try: + credential= Credential.objects.get(partyId=party, partnerId=partner) + except: + for param in ('username', 'password'): + if param not in data: + return Response({'error': 'username and password required.'}, status=status.HTTP_400_BAD_REQUEST) + credential= Credential(partyId=party, partnerId=partner) + + if 'email' in data: + partyIdList = Credential.objects.all().filter(email=data['email']).filter(partnerId='phoenix').values_list('partyId', flat=True) + for partyId in partyIdList: + has_partyId = Party.objects.all().filter(partyId=partyId).filter(partyType='organization').exists() + if has_partyId: + return Response({'error':'This email is already used by another institution.'}, status=status.HTTP_400_BAD_REQUEST) + + if 'password' in data: + if data['password'] == None or data['password'] == "": + return Response({'error': 'password must not be empty'}, status=status.HTTP_400_BAD_REQUEST) + else: + newPwd = data['password'] + data['password'] = Credential.generatePasswordHash(newPwd) + credentialSerializer = CredentialSerializer(credential, data=data, partial=True) else: - newPwd = data['password'] - data['password'] = hashlib.sha1(newPwd).hexdigest() - try: - credential = Credential.objects.get(partyId=party) - credentialSerializer = CredentialSerializer(credential, data=data) - except Credential.DoesNotExist: - data['partnerId'] = 'phoenix' - credentialSerializer = CredentialSerializer(data=data) - else: - credentialSerializer = CredentialSerializerNoPassword(credential, data=data, partial=True) #?? + credentialSerializer = CredentialSerializerNoPassword(credential, data=data, partial=True) - out = [] - if partySerializer.is_valid(): + partySerializer = PartySerializer(party, data=data, partial=True) + + if hasPartySerializerParam and not partySerializer.is_valid(): + return Response(partySerializer.errors, status=status.HTTP_400_BAD_REQUEST) + if hasCredentialSerializerParam and not credentialSerializer.is_valid(): + return Response(credentialSerializer.errors, status=status.HTTP_400_BAD_REQUEST) + + if hasPartySerializerParam: partySerializer.save() partyReturnData = partySerializer.data out.append(partyReturnData) - if credentialSerializer.is_valid(): - credentialSerializer.save() - credentialReturnData = credentialSerializer.data - out.append(credentialReturnData) - return HttpResponse(json.dumps(out), content_type="application/json") - else: - return Response(credentialSerializer.errors, status=status.HTTP_400_BAD_REQUEST) - else: - return Response(partySerializer.errors, status=status.HTTP_400_BAD_REQUEST) + + if hasCredentialSerializerParam: + credentialSerializer.save() + credentialReturnData = credentialSerializer.data + out.append(credentialReturnData) + + return HttpResponse(json.dumps(out), content_type="application/json") #PW-161 POST https://demoapi.arabidopsis.org/parties/institutions/?credentialId=2&secretKey=7DgskfEF7jeRGn1h%2B5iDCpvIkRA%3D #NOTE ?/ in parties/institutions/?credentialId= @@ -577,7 +597,7 @@ def post(self, request, format=None): if pwd == True: newPwd = data['password'] - data['password'] = hashlib.sha1(newPwd).hexdigest() + data['password'] = Party.generatePasswordHash(newPwd) credentialSerializer = CredentialSerializer(data=data) else: credentialSerializer = CredentialSerializerNoPassword(data=data) @@ -641,16 +661,17 @@ def get(self, request, format=None): return Response({'error':'does not allow get without partyType'}) obj = self.get_queryset() out = [] - if params['partyType'] == 'consortium': - for entry in obj.PartyAffiliation.all(): - serializer = serializer_class(entry) - out.append(serializer.data) - elif params['partyType'] == 'organization': - for entry in obj.consortiums.all(): - serializer = serializer_class(entry) - out.append(serializer.data) - else: - return Response({'error':'invalid partyType'}) + if obj: + if params['partyType'] == 'consortium': + for entry in obj.PartyAffiliation.all(): + serializer = serializer_class(entry) + out.append(serializer.data) + elif params['partyType'] == 'organization': + for entry in obj.consortiums.all(): + serializer = serializer_class(entry) + out.append(serializer.data) + else: + return Response({'error':'invalid partyType'}) return HttpResponse(json.dumps(out), content_type="application/json") def post(self, request, format=None): diff --git a/paywall2/settings.py.template.prod b/paywall2/settings.py.template.prod index 50ba2a60..253e6750 100644 --- a/paywall2/settings.py.template.prod +++ b/paywall2/settings.py.template.prod @@ -32,12 +32,31 @@ STRIPE_PRIVATE_KEY = None # ACTION: Add Stripe production private key here # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False -TEMPLATE_DEBUG = False +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'OPTIONS': { + 'debug': False, + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + 'loaders': [ + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader' + ], + } + } +] # Add error email setting ADMINS = [('Phoenix', 'techteam@arabidopsis.org'), ('Dev', 'dev@arabidopsis.org'), ('Xingguo', 'xingguo.chen@arabidopsis.org')] SERVER_EMAIL = 'dev@arabidopsis.org' # override the email address that error messages come from +TEST_RUNNER = 'common.utils.test_runner.NoLoggingTestRunner' + LOGGING = { 'version': 1, 'formatters': { @@ -72,7 +91,14 @@ LOGGING = { 'class': 'logging.FileHandler', 'filename': '/var/log/api/security_error.log', 'formatter': 'standard' - } + }, + # override default config + # see https://djangodeconstructed.com/2018/12/18/django-and-python-logging-in-plain-english/ + # https://github.com/django/django/blob/1939dd49d142b65fa22eb5f85cee0d20864d3730/django/utils/log.py#L18 + 'console': { + 'level': 'INFO', + 'class': 'logging.StreamHandler', + }, # 'sql_log': { # 'level': 'DEBUG', # 'class': 'logging.FileHandler', @@ -99,6 +125,10 @@ LOGGING = { 'propagate': True, 'level': 'WARNING', }, + 'django': { + 'handlers': ['console'], + 'level': 'INFO' + }, # SQL statement executed 'django.db.backends': { # 'handlers': ['sql_log'], @@ -138,31 +168,31 @@ INSTALLED_APPS = ( REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.AllowAny','common.permissions.ApiKeyPermission'), + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', 'PAGE_SIZE': 10 } -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', + #'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.security.SecurityMiddleware', -) +] # CORS Setting to allow ui sites to access the API server CORS_ORIGIN_WHITELIST = ( - 'testui.steveatgetexp.com', - 'azeem.steveatgetexp.com', ) CORS_ALLOW_CREDENTIALS = True CORS_ALLOW_METHODS = ( 'GET', + 'POST', + 'PUT', ) CORS_ALLOW_HEADERS = ( @@ -185,12 +215,15 @@ WSGI_APPLICATION = 'paywall2.wsgi.application' # ACTION: modify databse setting here DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': None, - 'USER': None, + 'ENGINE': 'mysql.connector.django', + 'NAME': 'phoenix_api', + 'USER': 'phoenix', 'PASSWORD': None, - 'HOST': None, + 'HOST': 'phoenix-api.cwyjt5kql77y.us-west-2.rds.amazonaws.com', 'PORT': '3306', + 'OPTIONS': { + 'use_pure': True + } } } @@ -241,3 +274,24 @@ EMAIL_PORT = 25 # For AWS SES, this will be the username/password of the IAM created for SES. EMAIL_HOST_USER = None EMAIL_HOST_PASSWORD = None + +# Credentials for CIPRES password decryption +CIPHER_CLIENT_SECRET = None +CIPHER_SALT = None +CIPHER_ITERATION = 200000 # 200,000 +# Credentials for CIPRES API end point +CIPRES_API_BASE_URL = "https://cipresci.ucsd.edu/portalrest/api" +CIPRES_BASIC_AUTH_TOKEN = None +CIPRES_ACCESS_TOKEN = None +# Email addresses for sending CIPRES sync failed email +CIPRES_ADMINS = ['techteam@arabidopsis.org', 'cipresadmin@sdsc.edu'] + +# Fill in parameters for CyVerse API call +CYVERSE_DOMAIN = 'https://kc.cyverse.org/auth' +CYVERSE_REALM = 'CyVerse' +CYVERSE_CLIENT_ID = 'phoenix-bio' +CYVERSE_SECRET = None + +# Email Subject for CyVerse purchase +CYVERSE_PURCHASE_EMAIL_SUBJECT = 'New Subscription Purchase' +CYVERSE_ADMINS = ['xingguo.chen@arabidopsis.org', 'support@cyverse.org'] diff --git a/paywall2/settings.py.template.test b/paywall2/settings.py.template.test index e5f89145..55a1122f 100644 --- a/paywall2/settings.py.template.test +++ b/paywall2/settings.py.template.test @@ -32,12 +32,31 @@ STRIPE_PRIVATE_KEY = 'sk_test_dXy85QkwH66s64bIWKbikyMt' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -TEMPLATE_DEBUG = True +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'OPTIONS': { + 'debug': True, + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + 'loaders': [ + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader' + ], + } + } +] # Add error email setting ADMINS = [('Phoenix', 'techteam@arabidopsis.org'), ('Dev', 'dev@arabidopsis.org'), ('Xingguo', 'xingguo.chen@arabidopsis.org')] SERVER_EMAIL = 'dev@arabidopsis.org' # override the email address that error messages come from +TEST_RUNNER = 'common.utils.test_runner.NoLoggingTestRunner' + LOGGING = { 'version': 1, 'formatters': { @@ -72,7 +91,14 @@ LOGGING = { 'class': 'logging.FileHandler', 'filename': '/var/log/api/security_error.log', 'formatter': 'standard' - } + }, + # override default config + # see https://djangodeconstructed.com/2018/12/18/django-and-python-logging-in-plain-english/ + # https://github.com/django/django/blob/1939dd49d142b65fa22eb5f85cee0d20864d3730/django/utils/log.py#L18 + 'console': { + 'level': 'INFO', + 'class': 'logging.StreamHandler', + }, # 'sql_log': { # 'level': 'DEBUG', # 'class': 'logging.FileHandler', @@ -99,6 +125,10 @@ LOGGING = { 'propagate': True, 'level': 'WARNING', }, + 'django': { + 'handlers': ['console'], + 'level': 'INFO' + }, # SQL statement executed 'django.db.backends': { # 'handlers': ['sql_log'], @@ -138,31 +168,31 @@ INSTALLED_APPS = ( REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.AllowAny','common.permissions.ApiKeyPermission'), + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination', 'PAGE_SIZE': 10 } -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', + #'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.security.SecurityMiddleware', -) +] # CORS Setting to allow ui sites to access the API server CORS_ORIGIN_WHITELIST = ( - 'testui.steveatgetexp.com', - 'azeem.steveatgetexp.com', ) CORS_ALLOW_CREDENTIALS = True CORS_ALLOW_METHODS = ( 'GET', + 'POST', + 'PUT', ) CORS_ALLOW_HEADERS = ( @@ -185,12 +215,15 @@ WSGI_APPLICATION = 'paywall2.wsgi.application' DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.mysql', + 'ENGINE': 'mysql.connector.django', 'NAME': 'phoenix_api', 'USER': 'phoenix', 'PASSWORD': '', - 'HOST': 'phoenix-api-test.cwyjt5kql77y.us-west-2.rds.amazonaws.com', + 'HOST': 'phoenix-api-uat.cwyjt5kql77y.us-west-2.rds.amazonaws.com', 'PORT': '3306', + 'OPTIONS': { + 'use_pure': True + } } } @@ -241,3 +274,24 @@ EMAIL_PORT = 25 # For AWS SES, this will be the username/password of the IAM created for SES. EMAIL_HOST_USER = None EMAIL_HOST_PASSWORD = None + +# Credentials for CIPRES password decryption +CIPHER_CLIENT_SECRET = None +CIPHER_SALT = None +CIPHER_ITERATION = 200000 # 200,000 +# Credentials for CIPRES API end point +CIPRES_API_BASE_URL = "https://ciprescibeta.ucsd.edu/portalrest/api" +CIPRES_BASIC_AUTH_TOKEN = None +CIPRES_ACCESS_TOKEN = None +# Email addresses for sending CIPRES sync failed email +CIPRES_ADMINS = ['techteam@arabidopsis.org'] + +# Fill in parameters for CyVerse API call +CYVERSE_DOMAIN = 'https://kc.cyverse.org/auth' +CYVERSE_REALM = 'CyVerse' +CYVERSE_CLIENT_ID = 'phoenix-bio' +CYVERSE_SECRET = None + +# Email Subject for CyVerse purchase +CYVERSE_PURCHASE_EMAIL_SUBJECT = 'New Subscription Purchase' +CYVERSE_ADMINS = ['xingguo.chen@arabidopsis.org', 'support@cyverse.org'] \ No newline at end of file diff --git a/paywall2/urls.py b/paywall2/urls.py index 57391699..cff46359 100644 --- a/paywall2/urls.py +++ b/paywall2/urls.py @@ -4,14 +4,14 @@ -from django.conf.urls import patterns, include, url +from django.conf.urls import include, url from django.contrib import admin from rest_framework import routers router = routers.DefaultRouter() -urlpatterns = patterns('', - url(r'^admin/', include(admin.site.urls)), +urlpatterns = [ + url(r'^admin/', admin.site.urls), url(r'^', include(router.urls)), url(r'^meters/', include('metering.urls')), url(r'^parties/', include('party.urls')), @@ -19,9 +19,9 @@ url(r'^authorizations/', include('authorization.urls')), url(r'^partners/', include('partner.urls')), url(r'^session-logs/', include('loggingapp.urls')), - url(r'^credentials/', include('authentication.urls', namespace="authentication")), + url(r'^credentials/', include('authentication.urls')), url(r'^apikeys/', include('apikey.urls')), url(r'^cookies/', include('cookies.urls')), url(r'^ipranges/', include('ipranges.urls')), url(r'^nullservice/', include('nullservice.urls')), -) +] diff --git a/scripts/IpRangeAddChange.py b/scripts/IpRangeAddChange.py index a1724eb7..5f1fc4e0 100644 --- a/scripts/IpRangeAddChange.py +++ b/scripts/IpRangeAddChange.py @@ -16,14 +16,14 @@ # Begin main program: # Open the source CSV file and load into memory. -IpRangeFilename = raw_input('Please enter a file name(*.csv) for ip range list:\n') +IpRangeFilename = input('Please enter a file name(*.csv) for ip range list:\n') with open(IpRangeFilename, 'rb') as f: reader = csv.reader(f) IpRangeListData = list(reader) # Processing Data -print 'Processing Data' +print('Processing Data') # count variable initialization ipRangeExists = 0 @@ -52,42 +52,42 @@ if not queryset.filter(name=institutionName).exists(): #create party if not Country.objects.all().filter(name=countryName): - print '[Country Not Found] ' + countryName + print('[Country Not Found] ' + countryName) continue elif Country.objects.all().filter(name=countryName).count() >1: - print '[More than one record found with country name] ' + countryName + print('[More than one record found with country name] ' + countryName) continue else: countryId = Country.objects.get(name=countryName).countryId partySerializer = PartySerializer(data={'name':institutionName, 'partyType': 'organization', 'country':countryId}, partial=True) if partySerializer.is_valid(): partySerializer.save() - print '[New Party Created] ' + institutionName + print('[New Party Created] ' + institutionName) else: - print '[Party serializer invalid] ' + \ + print('[Party serializer invalid] ' + \ 'type: ' + actionType + \ 'institution: ' + institutionName + \ 'start: ' + startIp + \ - 'end: ' + endIp + 'end: ' + endIp) ipRangeFailed += 1 continue partyId = partySerializer.data['partyId'] childParty = Party.objects.get(partyId = partyId) parentParty = Party.objects.get(partyId = consortiumId) PartyAffiliation.objects.create(childPartyId = childParty, parentPartyId = parentParty) - print '[PartyAffiliation Created] ' + \ + print('[PartyAffiliation Created] ' + \ 'institution: ' + institutionName + \ - 'consortium: ' + parentParty.name + 'consortium: ' + parentParty.name) partyCreated += 1 # when the party exists else: if queryset.filter(name=institutionName).count() > 1: - print '[More than one party found with institution name] ' + \ + print('[More than one party found with institution name] ' + \ 'type: ' + actionType + \ 'institution: ' + institutionName + \ 'start: ' + startIp + \ - 'end: ' + endIp + 'end: ' + endIp) ipRangeFailed += 1 continue partyId = queryset.filter(name=institutionName)[0].partyId @@ -95,11 +95,11 @@ nextIter = False for ipRange in ipRangeList: if ipRange.start == startIp and ipRange.end == endIp: - print '[Ip range already exists] ' + \ + print('[Ip range already exists] ' + \ 'type: ' + actionType + \ 'institution: ' + institutionName + \ 'start: ' + startIp + \ - 'end: ' + endIp + 'end: ' + endIp) nextIter = True ipRangeExists += 1 break @@ -109,39 +109,39 @@ ipRangeSerializer = IpRangeSerializer(data={'start': startIp, 'end': endIp, 'partyId': partyId}, partial=True) if ipRangeSerializer.is_valid(): ipRangeSerializer.save() - print '[IpRange Created] ' + \ + print('[IpRange Created] ' + \ 'type: ' + actionType + \ 'institution: ' + institutionName + \ 'start: ' + startIp + \ - 'end: ' + endIp + 'end: ' + endIp) ipRangeLoaded += 1 else: - print '[Ip range serializer invalid] ' + \ + print('[Ip range serializer invalid] ' + \ 'type: ' + actionType + \ 'institution: ' + institutionName + \ 'start: ' + startIp + \ - 'end: ' + endIp + 'end: ' + endIp) ipRangeFailed += 1 elif actionType == 'update': partyId = None # when the party doesn't exist if not queryset.filter(name=institutionName).exists(): - print '[Party does not exist] ' + \ + print('[Party does not exist] ' + \ 'type: ' + actionType + \ 'institution: ' + institutionName + \ 'start: ' + startIp + \ - 'end: ' + endIp + 'end: ' + endIp) ipRangeFailed += 1 continue # when the party exists elif queryset.filter(name=institutionName).count() > 1: - print '[More than one party found with institution name] ' + \ + print('[More than one party found with institution name] ' + \ 'type: ' + actionType + \ 'institution: ' + institutionName + \ 'start: ' + startIp + \ - 'end: ' + endIp + 'end: ' + endIp) ipRangeFailed +=1 continue else: @@ -154,22 +154,22 @@ ipRangeSerializer = IpRangeSerializer(data={'start': startIp, 'end': endIp, 'partyId': partyId}, partial=True) if ipRangeSerializer.is_valid(): ipRangeSerializer.save() - print '[IpRange Created] ' + \ + print('[IpRange Created] ' + \ 'type: ' + actionType + \ 'institution: ' + institutionName + \ 'start: ' + startIp + \ - 'end: ' + endIp + 'end: ' + endIp) ipRangeLoaded += 1 else: - print '[Ip range serializer invalid] ' + \ + print('[Ip range serializer invalid] ' + \ 'type: ' + actionType + \ 'institution: ' + institutionName + \ 'start: ' + startIp + \ - 'end: ' + endIp + 'end: ' + endIp) ipRangeFailed += 1 -print 'Loading Complete: ' + \ +print('Loading Complete: ' + \ 'ipRangeLoaded: ' + str(ipRangeLoaded) + \ 'ipRangeExists: ' + str(ipRangeExists) + \ 'ipRangeFailed: ' + str(ipRangeFailed) + \ - 'partyCreated: ' + str(partyCreated) + 'partyCreated: ' + str(partyCreated)) diff --git a/scripts/LoadCountryIdScript.py b/scripts/LoadCountryIdScript.py index ca72b643..5e80f0c5 100644 --- a/scripts/LoadCountryIdScript.py +++ b/scripts/LoadCountryIdScript.py @@ -14,14 +14,14 @@ # Begin main program: # Open the source CSV file and load into memory. -orgCountriesFilename = raw_input('Please enter a file name(*.csv) for organization countires list:\n') +orgCountriesFilename = input('Please enter a file name(*.csv) for organization countires list:\n') with open(orgCountriesFilename, 'rb') as f: reader = csv.reader(f) organizationCountryData = list(reader) # Processing Data -print 'Processing Data' +print('Processing Data') for entry in organizationCountryData: organizationName = entry[0] @@ -32,16 +32,16 @@ if Country.objects.all().filter(name=countryName).exists(): countryId = Country.objects.get(name=countryName).countryId else: - print 'cannot find country name: ' + countryName + print('cannot find country name: ' + countryName) continue if Party.objects.all().filter(name=organizationName).exists(): if Party.objects.all().filter(name=organizationName).count() > 1: - print 'more than one Party returned: ' + organizationName + print('more than one Party returned: ' + organizationName) continue party = Party.objects.get(name=organizationName) else: - print 'cannot find party: ' + organizationName + print('cannot find party: ' + organizationName) continue data = {'country':countryId} @@ -50,7 +50,7 @@ if serializer.is_valid(): serializer.save() else: - print "cannot save party: " + organizationName - print data + print("cannot save party: " + organizationName) + print(data) -print 'Loading Complete' +print('Loading Complete') diff --git a/scripts/basicMigrationScript.py b/scripts/basicMigrationScript.py index 791feeb0..8139dfae 100755 --- a/scripts/basicMigrationScript.py +++ b/scripts/basicMigrationScript.py @@ -13,7 +13,7 @@ from party.serializers import PartySerializer from authentication.serializers import CredentialSerializer -from tairData import TAIR +from .tairData import TAIR APIKEY = [ {'apiKey':'test123'}, @@ -109,7 +109,7 @@ def loadPartner(partner, paidAccessTypeId, loginAccessTypeId): if serializer.is_valid(): serializer.save() else: - print "Unable to create partner %s, exiting" % partnerId + print("Unable to create partner %s, exiting" % partnerId) exit() loadItem(PartnerPatternSerializer, partner['PartnerPattern'], partnerId) diff --git a/scripts/countryMigration.py b/scripts/countryMigration.py index dab4c5c8..aaefbdaa 100755 --- a/scripts/countryMigration.py +++ b/scripts/countryMigration.py @@ -17,7 +17,7 @@ def connect(): user="phoenix" password="password" dbName="demo1" - + conn = MySQLdb.connect(host=host, user=user, passwd=password, @@ -45,8 +45,8 @@ def connect(): # Does 500 queries per transaction for performance improvement. if batchCount >= 500: - print "total commit %s" % totalCount + print("total commit %s" % totalCount) conn.commit() batchCount = 0 - + conn.commit() diff --git a/scripts/createTestAccounts0329.py b/scripts/createTestAccounts0329.py index 648418f7..1c5789b4 100644 --- a/scripts/createTestAccounts0329.py +++ b/scripts/createTestAccounts0329.py @@ -30,9 +30,9 @@ serializer = CredentialSerializer(data=data, partial=True) if serializer.is_valid(): serializer.save() - print "user record loaded: " - print json.dumps(serializer.data) + print("user record loaded: ") + print(json.dumps(serializer.data)) else: - print "user record not added: " - print serializer.errors + print("user record not added: ") + print(serializer.errors) diff --git a/scripts/ltSetup.py b/scripts/ltSetup.py index a7825cb2..e7e527d7 100755 --- a/scripts/ltSetup.py +++ b/scripts/ltSetup.py @@ -13,7 +13,7 @@ from party.serializers import PartySerializer from authentication.serializers import CredentialSerializer -from tairData import TAIR +from .tairData import TAIR def loadTestUser(testUsers): diff --git a/scripts/organizationCountryMigrationScript.py b/scripts/organizationCountryMigrationScript.py index f7429d0e..78b1ac3d 100644 --- a/scripts/organizationCountryMigrationScript.py +++ b/scripts/organizationCountryMigrationScript.py @@ -14,7 +14,7 @@ # Begin main program: # Step1: Open the source CSV file and load into memory. -organizationFilename = raw_input("Please enter a file name(*.csv) for organization list(not organization_country):\n") +organizationFilename = input("Please enter a file name(*.csv) for organization list(not organization_country):\n") with open(organizationFilename, 'rb') as f: reader = csv.reader(f) @@ -25,7 +25,7 @@ organizationCountryData = list(reader) # Initializing organization country -print "Initializing Organization Country Array" +print("Initializing Organization Country Array") organizationCountryArray = {} for entry in organizationCountryData: organizationId = entry[0] @@ -47,11 +47,11 @@ countryId = None; organizationCountryArray[organizationId] = [countryId, display] -print "Processing Data" +print("Processing Data") count = 0 for entry in organizationData: count += 1 - print count + print(count) organizationId = entry[0] if not organizationId.isdigit(): continue @@ -76,15 +76,15 @@ 'display':display, 'country':countryId, } - + if Party.objects.all().filter(name=organizationName).exists(): for partyInstance in Party.objects.all().filter(name=organizationName): - + serializer = PartySerializer(partyInstance, data=data) if serializer.is_valid(): serializer.save() else: - print "CANNOT SAVE PARTY" - print data + print("CANNOT SAVE PARTY") + print(data) else: - print "organizationName NOT FOUND: "+organizationName + print("organizationName NOT FOUND: "+organizationName) diff --git a/scripts/personSubscriptionMigrationScript.py b/scripts/personSubscriptionMigrationScript.py index 82e0aaf8..774f5027 100755 --- a/scripts/personSubscriptionMigrationScript.py +++ b/scripts/personSubscriptionMigrationScript.py @@ -50,7 +50,7 @@ def parseTime(inString): partnerId = "tair" for entry in personData: count += 1 - print count + print(count) communityId = entry[0] if not communityId.isdigit(): continue @@ -60,9 +60,9 @@ def parseTime(inString): if len(cred) > 0: partyId = cred[0].partyId.partyId else: - print "not good %s" % communityId + print("not good %s" % communityId) continue - + startDate = parseTime("21-DEC-12") endDate = parseTime(expirationDate) data = { @@ -75,8 +75,8 @@ def parseTime(inString): if serializer.is_valid(): serializer.save() else: - print "CANNOT SAVE SUBSCRIPTION" - print data + print("CANNOT SAVE SUBSCRIPTION") + print(data) subscriptionId = serializer.data['subscriptionId'] data = { diff --git a/scripts/removeTestingOrganizationScript.py b/scripts/removeTestingOrganizationScript.py index 8995f253..5bf848f6 100644 --- a/scripts/removeTestingOrganizationScript.py +++ b/scripts/removeTestingOrganizationScript.py @@ -13,7 +13,7 @@ # Begin main program: # Step1: Open the source CSV file and load into memory. -partyListFilename = raw_input("Please enter a file name(*.csv) for party list(not organization_country):\n") +partyListFilename = input("Please enter a file name(*.csv) for party list(not organization_country):\n") with open(partyListFilename, 'rb') as f: reader = csv.reader(f) @@ -23,5 +23,5 @@ if Party.objects.all().filter(name=entry[0]).exists(): Party.objects.all().filter(name=entry[0]).delete() - print 'deleted: '+ entry[0] + print('deleted: '+ entry[0]) diff --git a/scripts/testPartyCountry.py b/scripts/runTestPartyCountry.py similarity index 93% rename from scripts/testPartyCountry.py rename to scripts/runTestPartyCountry.py index 44d3401f..3251120a 100644 --- a/scripts/testPartyCountry.py +++ b/scripts/runTestPartyCountry.py @@ -25,7 +25,7 @@ organizationCountryData = list(reader) # Initializing organization country -print "Initializing Organization Country Array" +print("Initializing Organization Country Array") organizationCountryArray = {} for entry in organizationCountryData: organizationId = entry[0] @@ -43,14 +43,14 @@ display = False if Party.objects.all().filter(name=organizationName).exists(): countryId = int(Party.objects.all().filter(name=organizationName)[0].country.countryId) - print organizationName + ", " + str(countryId) + "\n" + print(organizationName + ", " + str(countryId) + "\n") # if Party.objects.all().filter(name=organizationName)[1].exists(): # countryId = int(Party.objects.all().filter(name=organizationName)[1].country.countryId) # print organizationName + ", " + str(countryId) + "\n" # if Party.objects.all().filter(name=organizationName)[2].exists(): # countryId = int(Party.objects.all().filter(name=organizationName)[2].country.countryId) # print organizationName + ", " + str(countryId) + "\n" - + else: countryId = None; #print organizationName + ", " + countryId + "\n" diff --git a/scripts/setupDbScript.py b/scripts/setupDbScript.py index dcdd56ea..2236e46e 100755 --- a/scripts/setupDbScript.py +++ b/scripts/setupDbScript.py @@ -6,7 +6,7 @@ def connect(): user="phoenix" password="password" dbName="demo1" - + conn = MySQLdb.connect(host=host, user=user, passwd=password, diff --git a/scripts/subscriptionMigrationScript.py b/scripts/subscriptionMigrationScript.py index c42e6319..c9ba07a1 100755 --- a/scripts/subscriptionMigrationScript.py +++ b/scripts/subscriptionMigrationScript.py @@ -60,12 +60,12 @@ def parseTime(inString): timeArr = inArr[1].split('.') timeStr = "%s:%s:%s" % (timeArr[0], timeArr[1], timeArr[2]) - + outString = "%s-%s-%sT%sZ" % (year, month, day, timeStr) return outString # Initializing organization country -print "Initializing Organization Country Array" +print("Initializing Organization Country Array") organizationCountryArray = {} for entry in organizationCountryData: organizationId = entry[0] @@ -87,18 +87,18 @@ def parseTime(inString): countryId = None organizationCountryArray[organizationId] = [countryId, display] -print "Processing Data" +print("Processing Data") count = 0 for entry in organizationData: count += 1 - print count + print(count) organizationId = entry[0] if not organizationId.isdigit(): continue offset = 2 organizationName = entry[1] if Party.objects.all().filter(name=organizationName).exists(): - print 'organization already exists: '+'('+organizationId+')'+organizationName + print('organization already exists: '+'('+organizationId+')'+organizationName) continue while not entry[offset].isdigit(): organizationName = "%s,%s" % (organizationName, entry[offset]) @@ -113,7 +113,7 @@ def parseTime(inString): organizationName = organizationName.decode('utf8') startDate = entry[offset+1] endDate = entry[offset+2] - + if not endDate or endDate == "": endDate = "01-JAN-99 01.00.00.000000000 AM AMERICA/LOS_ANGELES" startDate = parseTime(startDate) @@ -142,8 +142,8 @@ def parseTime(inString): if serializer.is_valid(): serializer.save() else: - print "CANNOT SAVE SUBSCRIPTION" - print data + print("CANNOT SAVE SUBSCRIPTION") + print(data) subscriptionId = serializer.data['subscriptionId'] data = { @@ -156,12 +156,12 @@ def parseTime(inString): serializer = SubscriptionTransactionSerializer(data=data) if serializer.is_valid(): serializer.save() -print "DOING IP MIGRATION" +print("DOING IP MIGRATION") count = 0 for entry in ipData: count +=1 - print count + print(count) organizationId = entry[0] if not organizationId.isdigit(): continue @@ -170,7 +170,7 @@ def parseTime(inString): if organizationId in orgIdPartyId: partyId = orgIdPartyId[organizationId] else: - print 'organization already exists: '+ organizationId + print('organization already exists: '+ organizationId) continue data = { @@ -182,5 +182,5 @@ def parseTime(inString): if serializer.is_valid(): serializer.save() else: - print "BAD IP" - print data + print("BAD IP") + print(data) diff --git a/scripts/updateConsortiumSubscriptionFields.py b/scripts/updateConsortiumSubscriptionFields.py index 03b5c6fd..f915f037 100644 --- a/scripts/updateConsortiumSubscriptionFields.py +++ b/scripts/updateConsortiumSubscriptionFields.py @@ -63,7 +63,7 @@ continue # if the obj exists then update if not created: - for k, v in updateData.iteritems(): + for k, v in updateData.items(): if getattr(obj, k) != v: setattr(obj, k, v) obj.save() diff --git a/scripts/updatePartyPasswordsFromFile.py b/scripts/updatePartyPasswordsFromFile.py index 9b4e60b2..9ebdd2e9 100644 --- a/scripts/updatePartyPasswordsFromFile.py +++ b/scripts/updatePartyPasswordsFromFile.py @@ -17,7 +17,7 @@ def connect(): user="phoenix" password="password" dbName="phoenix_api" - + conn = MySQLdb.connect(host=host, user=user, passwd=password, @@ -52,14 +52,14 @@ def create_signature(password): if digestedPw.rstrip() == '': continue totalCount += 1 - + # Execute the update. try: cur.execute(passwordSql%(digestedPw,partyId,)) except: - print "Party {} -- exception: {}".format(partyId, sys.exc_info()[0]) + print("Party {} -- exception: {}".format(partyId, sys.exc_info()[0])) # Commit the transaction. conn.commit() -print "total passwords updated: %s" %totalCount +print("total passwords updated: %s" %totalCount) diff --git a/scripts/userMigrationScript.py b/scripts/userMigrationScript.py index 765e2fb5..5fa864cc 100755 --- a/scripts/userMigrationScript.py +++ b/scripts/userMigrationScript.py @@ -17,7 +17,7 @@ def connect(): user="phoenix" password="password" dbName="phoenix_api" - + conn = MySQLdb.connect(host=host, user=user, passwd=password, @@ -72,14 +72,14 @@ def create_signature(password): partyId = conn.insert_id() cur.execute(newUserSql%(username, digestedPw, email, partyId, partnerId, userIdentifier, firstName, lastName)) except: - print "{} -- exception: {}".format(username, sys.exc_info()[0]) + print("{} -- exception: {}".format(username, sys.exc_info()[0])) # Does 500 queries per transaction for performance improvement. if batchCount >= 500: - print "total commit %s" % totalCount + print("total commit %s" % totalCount) conn.commit() batchCount = 0 conn.commit() -print "total users migrated: %s" %totalCount +print("total users migrated: %s" %totalCount) diff --git a/subscription/controls.py b/subscription/controls.py index 0a100565..d03f2619 100644 --- a/subscription/controls.py +++ b/subscription/controls.py @@ -4,7 +4,7 @@ import stripe from partner.models import SubscriptionTerm, Partner from subscription.models import Subscription, SubscriptionTransaction, ActivationCode -from serializers import SubscriptionSerializer +from .serializers import SubscriptionSerializer from party.models import Party import datetime @@ -18,7 +18,7 @@ from django.conf import settings -import urllib +import urllib.request, urllib.parse, urllib.error class SubscriptionControl(): @@ -37,7 +37,7 @@ def createOrUpdateSubscription(partyId, partnerId, period): transactionType = None transactionStartDate = None transactionEndDate = None - + if subscription == None: # case1: new subscription partyObj = Party.objects.get(partyId=partyId) @@ -65,7 +65,7 @@ def createOrUpdateSubscription(partyId, partnerId, period): transactionType = 'renew' transactionStartDate = endDate transactionEndDate = subscription.endDate - + return (subscription, transactionType, transactionStartDate, transactionEndDate) class PaymentControl(): @@ -80,7 +80,7 @@ def tryCharge(secret_key, stripe_token, priceToCharge, partnerName, chargeDescri message['message'] = "Charge validation error" #message['status'] = False //PW-120 vet we will return 400 instead - see SubscriptionsPayment post - i.e. the caller return message - + stripe.api_key = secret_key charge = stripe.Charge.create( amount=int(priceToCharge*100), # stripe takes in cents; UI passes in dollars. multiply by 100 to convert. @@ -96,19 +96,19 @@ def tryCharge(secret_key, stripe_token, priceToCharge, partnerName, chargeDescri message['activationCodes'] = activationCodes try: pass - except stripe.error.InvalidRequestError, e: + except stripe.error.InvalidRequestError as e: status = False message['message'] = e.json_body['error']['message'] - except stripe.error.CardError, e: + except stripe.error.CardError as e: status = False message['message'] = e.json_body['error']['message'] - except stripe.error.AuthenticationError, e: + except stripe.error.AuthenticationError as e: status = False message['message'] = e.json_body['error']['message'] - except stripe.error.APIConnectionError, e: + except stripe.error.APIConnectionError as e: status = False message['message'] = e.json_body['error']['message'] - except Exception, e: + except Exception as e: status = False message['message'] = "Unexpected exception: %s" % (e) @@ -117,10 +117,10 @@ def tryCharge(secret_key, stripe_token, priceToCharge, partnerName, chargeDescri @staticmethod def getEmailInfo(activationCodes, partnerName, termId, quantity, payment, transactionId, email, firstname, lastname, institute, street, city, state, country, zip, hostname, redirect, vat, domain): - + termObj = SubscriptionTerm.objects.get(subscriptionTermId=termId) partnerObj = termObj.partnerId - loginURL = domain + partnerObj.loginUri + "?redirect=" + urllib.quote(domain + "/preferences.html", safe='~') + loginURL = domain + partnerObj.loginUri + "?redirect=" + urllib.parse.quote(domain + "/preferences.html", safe='~') registerURL = partnerObj.registerUri name = firstname+" "+lastname institute = institute @@ -165,7 +165,7 @@ def emailReceipt(emailInfo, termId): termObj = SubscriptionTerm.objects.get(subscriptionTermId=termId) partnerObj = termObj.partnerId - + html_message = partnerObj.activationEmailInstructionText % ( kwargs['partnerLogo'], kwargs['name'], @@ -187,19 +187,19 @@ def emailReceipt(emailInfo, termId): """+kwargs['addr2']+""",
"""+kwargs['addr3']+"""
""") - - + + subject = kwargs['subject'] from_email = kwargs['senderEmail'] recipient_list = kwargs['recipientEmails'] -# logging.basicConfig(filename="/home/ec2-user/logs/debug.log", -# format='%(asctime)s %(message)s' -# ) -# logger.info("------Sending individual email------") -# logger.info("%s" % recipient_list[0]) + logger.info("------Sending activation code email------") + logger.info("Receipient: %s" % recipient_list[0]) + logger.info("ActivationCodes:") + for l in kwargs['accessCodes']: + logger.info(l) send_mail(subject=subject, from_email=from_email, recipient_list=recipient_list, html_message=html_message, message=None) -# logger.info("------Done sending individual email------") - + logger.info("------Done sending activation code email------") + @staticmethod def isValidRequest(request, message): ret = True @@ -238,7 +238,7 @@ def postPaymentHandling(termId, quantity): codeArray = [] - for i in xrange(quantity): + for i in range(quantity): # create an activation code based on partnerId and period. activationCodeObj = ActivationCode() activationCodeObj.activationCode=str(uuid.uuid4()) @@ -251,7 +251,7 @@ def postPaymentHandling(termId, quantity): codeArray.append(activationCodeObj.activationCode) return codeArray - + @staticmethod def validateCharge(price, termId, quantity): so = SubscriptionTerm.objects.get(subscriptionTermId=termId) @@ -259,4 +259,4 @@ def validateCharge(price, termId, quantity): if so.groupDiscountPercentage > 0 and quantity > 1: calcprice = so.price*quantity*(1-(so.groupDiscountPercentage/100)) calcprice = round(calcprice*100)/100 - return (price == calcprice) + return (price == calcprice) diff --git a/subscription/migrations/0001_initial.py b/subscription/migrations/0001_initial.py index 074fdebf..7aefa844 100644 --- a/subscription/migrations/0001_initial.py +++ b/subscription/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -18,9 +18,9 @@ class Migration(migrations.Migration): ('activationCodeId', models.AutoField(serialize=False, primary_key=True)), ('activationCode', models.CharField(unique=True, max_length=200)), ('period', models.IntegerField()), - ('purchaseDate', models.DateTimeField(default=b'2001-01-01T00:00:00Z')), - ('partnerId', models.ForeignKey(to='partner.Partner', db_column=b'partnerId')), - ('partyId', models.ForeignKey(to='party.Party', null=True)), + ('purchaseDate', models.DateTimeField(default='2001-01-01T00:00:00Z')), + ('partnerId', models.ForeignKey(to='partner.Partner', db_column='partnerId', on_delete=models.PROTECT)), + ('partyId', models.ForeignKey(to='party.Party', null=True, on_delete=models.PROTECT)), ], options={ 'db_table': 'ActivationCode', @@ -30,10 +30,10 @@ class Migration(migrations.Migration): name='Subscription', fields=[ ('subscriptionId', models.AutoField(serialize=False, primary_key=True)), - ('startDate', models.DateTimeField(default=b'2000-01-01T00:00:00Z')), - ('endDate', models.DateTimeField(default=b'2012-12-21T00:00:00Z')), - ('partnerId', models.ForeignKey(db_column=b'partnerId', to='partner.Partner', null=True)), - ('partyId', models.ForeignKey(db_column=b'partyId', to='party.Party', null=True)), + ('startDate', models.DateTimeField(default='2000-01-01T00:00:00Z')), + ('endDate', models.DateTimeField(default='2012-12-21T00:00:00Z')), + ('partnerId', models.ForeignKey(db_column='partnerId', to='partner.Partner', null=True, on_delete=models.PROTECT)), + ('partyId', models.ForeignKey(db_column='partyId', to='party.Party', null=True, on_delete=models.PROTECT)), ], options={ 'db_table': 'Subscription', @@ -43,11 +43,11 @@ class Migration(migrations.Migration): name='SubscriptionTransaction', fields=[ ('subscriptionTransactionId', models.AutoField(serialize=False, primary_key=True)), - ('transactionDate', models.DateTimeField(default=b'2000-01-01T00:00:00Z')), - ('startDate', models.DateTimeField(default=b'2001-01-01T00:00:00Z')), - ('endDate', models.DateTimeField(default=b'2020-01-01T00:00:00Z')), + ('transactionDate', models.DateTimeField(default='2000-01-01T00:00:00Z')), + ('startDate', models.DateTimeField(default='2001-01-01T00:00:00Z')), + ('endDate', models.DateTimeField(default='2020-01-01T00:00:00Z')), ('transactionType', models.CharField(max_length=200)), - ('subscriptionId', models.ForeignKey(to='subscription.Subscription', db_column=b'subscriptionId')), + ('subscriptionId', models.ForeignKey(to='subscription.Subscription', db_column='subscriptionId', on_delete=models.PROTECT)), ], options={ 'db_table': 'SubscriptionTransaction', diff --git a/subscription/migrations/0002_unique_constraint_partyId_partnerId.py b/subscription/migrations/0002_unique_constraint_partyId_partnerId.py index 824e0d4a..83d82f22 100644 --- a/subscription/migrations/0002_unique_constraint_partyId_partnerId.py +++ b/subscription/migrations/0002_unique_constraint_partyId_partnerId.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/subscription/migrations/0003_subscriptionrequestmodel.py b/subscription/migrations/0003_subscriptionrequestmodel.py index a034e974..5b13e814 100644 --- a/subscription/migrations/0003_subscriptionrequestmodel.py +++ b/subscription/migrations/0003_subscriptionrequestmodel.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations import datetime @@ -25,7 +25,7 @@ class Migration(migrations.Migration): ('librarianName', models.CharField(max_length=100)), ('librarianEmail', models.CharField(max_length=128)), ('comments', models.CharField(max_length=5000)), - ('partnerId', models.ForeignKey(db_column=b'partnerId', to='partner.Partner', max_length=200)), + ('partnerId', models.ForeignKey(db_column='partnerId', to='partner.Partner', max_length=200, on_delete=models.PROTECT)), ], options={ 'db_table': 'SubscriptionRequest', diff --git a/subscription/migrations/0004_subscriptionrequest_requesttype.py b/subscription/migrations/0004_subscriptionrequest_requesttype.py index 6254f507..c7f7480a 100644 --- a/subscription/migrations/0004_subscriptionrequest_requesttype.py +++ b/subscription/migrations/0004_subscriptionrequest_requesttype.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/subscription/migrations/0005_added_transactionType_to_activationCode_PW-438.py b/subscription/migrations/0005_added_transactionType_to_activationCode_PW-438.py index 98286cc6..0004745f 100644 --- a/subscription/migrations/0005_added_transactionType_to_activationCode_PW-438.py +++ b/subscription/migrations/0005_added_transactionType_to_activationCode_PW-438.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/subscription/migrations/0006_cons_fields_subscription_PWL-579.py b/subscription/migrations/0006_cons_fields_subscription_PWL-579.py index 64fd70d9..262e1d37 100644 --- a/subscription/migrations/0006_cons_fields_subscription_PWL-579.py +++ b/subscription/migrations/0006_cons_fields_subscription_PWL-579.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations @@ -20,7 +20,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='subscription', name='consortiumId', - field=models.ForeignKey(to='party.Party', null=True), + field=models.ForeignKey(to='party.Party', null=True, on_delete=models.PROTECT), ), migrations.AddField( model_name='subscription', @@ -35,7 +35,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='subscription', name='partyId', - field=models.ForeignKey(related_name='party_id', db_column=b'partyId', to='party.Party', null=True), + field=models.ForeignKey(related_name='party_id', db_column='partyId', to='party.Party', null=True, on_delete=models.PROTECT), ), migrations.AlterField( model_name='subscription', diff --git a/subscription/migrations/0007_added_delete_marker_POL-20.py b/subscription/migrations/0007_added_delete_marker_POL-20.py index b036e2ff..c7d23309 100644 --- a/subscription/migrations/0007_added_delete_marker_POL-20.py +++ b/subscription/migrations/0007_added_delete_marker_POL-20.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals + from django.db import models, migrations diff --git a/subscription/models.py b/subscription/models.py index d1744935..92a4136e 100644 --- a/subscription/models.py +++ b/subscription/models.py @@ -14,13 +14,13 @@ def db_type(self, connection): class Subscription(models.Model): subscriptionId = models.AutoField(primary_key=True) - partyId = models.ForeignKey("party.Party", null=True, db_column="partyId", related_name='party_id') - partnerId = models.ForeignKey("partner.Partner", null=True, db_column="partnerId") + partyId = models.ForeignKey("party.Party", null=True, db_column="partyId", related_name='party_id', on_delete=models.PROTECT) + partnerId = models.ForeignKey("partner.Partner", null=True, db_column="partnerId", on_delete=models.PROTECT) startDate = models.DateTimeField(null=True) endDate = models.DateTimeField(null=True) consortiumStartDate = models.DateTimeField(null=True) consortiumEndDate = models.DateTimeField(null=True) - consortiumId = models.ForeignKey("party.Party", null=True) + consortiumId = models.ForeignKey("party.Party", null=True, on_delete=models.PROTECT) @staticmethod def getByIp(ipAddress): @@ -60,8 +60,8 @@ class Meta: class ActivationCode(models.Model): activationCodeId = models.AutoField(primary_key=True) activationCode = models.CharField(max_length=200, unique=True) - partnerId = models.ForeignKey('partner.Partner', db_column="partnerId") - partyId = models.ForeignKey('party.Party', null=True) + partnerId = models.ForeignKey('partner.Partner', db_column="partnerId", on_delete=models.PROTECT) + partyId = models.ForeignKey('party.Party', null=True, on_delete=models.PROTECT) period = models.IntegerField() purchaseDate = models.DateTimeField(default='2001-01-01T00:00:00Z') transactionType = models.CharField(max_length=200, null=True) @@ -71,7 +71,7 @@ class Meta: class SubscriptionTransaction(models.Model): subscriptionTransactionId = models.AutoField(primary_key=True) - subscriptionId = models.ForeignKey('Subscription', db_column="subscriptionId") + subscriptionId = models.ForeignKey('Subscription', db_column="subscriptionId", on_delete=models.PROTECT) transactionDate = models.DateTimeField(default='2000-01-01T00:00:00Z') startDate = models.DateTimeField(default='2001-01-01T00:00:00Z') endDate = models.DateTimeField(default='2020-01-01T00:00:00Z') @@ -91,7 +91,7 @@ def createFromSubscription(subscription, transactionType, transactionStartDate=N 'endDate':transactionEndDate, 'transactionType':transactionType, } - from serializers import SubscriptionTransactionSerializer + from .serializers import SubscriptionTransactionSerializer transactionSerializer = SubscriptionTransactionSerializer(data=transactionJson) if transactionSerializer.is_valid(): return transactionSerializer.save() @@ -111,7 +111,7 @@ class SubscriptionRequest(models.Model): librarianName = models.CharField(max_length=100) librarianEmail = models.CharField(max_length=128) comments = models.CharField(max_length=5000) - partnerId = models.ForeignKey('partner.Partner', max_length=200, db_column="partnerId") + partnerId = models.ForeignKey('partner.Partner', max_length=200, db_column="partnerId", on_delete=models.PROTECT) requestType = models.CharField(max_length=32) class Meta: diff --git a/subscription/pyTests.py b/subscription/pyTests.py deleted file mode 100644 index 347348f8..00000000 --- a/subscription/pyTests.py +++ /dev/null @@ -1,174 +0,0 @@ -#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. - -import django -import unittest -import sys, getopt -from unittest import TestCase -from subscription.models import Subscription, SubscriptionTransaction, ActivationCode -from partner.models import Partner -import requests -import json -from testSamples import SubscriptionSample, SubscriptionTransactionSample, ActivationCodeSample -from party.testSamples import PartySample, IpRangeSample -from partner.testSamples import PartnerSample, SubscriptionTermSample -import copy -from common.pyTests import PyTestGenerics, GenericCRUDTest, GenericTest -from rest_framework import status -from controls import PaymentControl - -# Create your tests here. -django.setup() -serverUrl = PyTestGenerics.initPyTest() -print "using server url %s" % serverUrl - -# ---------------------- UNIT TEST FOR BASIC CRUD OPERATIONS ------------- - -class ActivationCodeCRUD(GenericCRUDTest, TestCase): - sample = ActivationCodeSample(serverUrl) - partnerSample = PartnerSample(serverUrl) - - def setUp(self): - super(ActivationCodeCRUD, self).setUp() - Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() - self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) - self.sample.data['partnerId'] = self.sample.updateData['partnerId']=self.partnerId - - # delete activationCode entries that will be used for test - ActivationCode.objects.filter(activationCode=self.sample.data['activationCode']).delete() - ActivationCode.objects.filter(activationCode=self.sample.updateData['activationCode']).delete() - - def tearDown(self): - super(ActivationCodeCRUD,self).tearDown() - PyTestGenerics.forceDelete(self.partnerSample.model, self.partnerSample.pkName, self.partnerId) - - -class SubscriptionCRUD(GenericCRUDTest, TestCase): - - sample = SubscriptionSample(serverUrl) - partySample = PartySample(serverUrl) - partnerSample = PartnerSample(serverUrl) - activationCodeSample = ActivationCodeSample(serverUrl) - - def setUp(self): - super(SubscriptionCRUD,self).setUp() - self.partyId = self.partySample.forcePost(self.partySample.data) - Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() - self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) - ActivationCode.objects.filter(activationCode=self.activationCodeSample.data['activationCode']).delete() - self.activationCodeId = self.activationCodeSample.forcePost(self.activationCodeSample.data) - self.sample.data['partyId']=self.sample.updateData['partyId']=self.partyId - self.sample.partnerId=self.sample.data['partnerId']=self.sample.updateData['partnerId']=self.partnerId - - # post request for subscription is not generic. It creates a SubscriptionTransaction - # object in addition to a Subscription object. - def test_for_create(self): - sample = self.sample - url = sample.url - cookies = {'apiKey':self.apiKey} - - # Creation via basic CRUD format - req = requests.post(url, data=sample.data, cookies=cookies) - self.assertEqual(req.status_code, status.HTTP_201_CREATED) - self.assertIsNotNone(PyTestGenerics.forceGet(sample.model,sample.pkName,req.json()[sample.pkName])) - transactionId = req.json()['subscriptionTransactionId'] - self.assertIsNotNone(PyTestGenerics.forceGet(SubscriptionTransaction,'subscriptionTransactionId',transactionId)) - PyTestGenerics.forceDelete(SubscriptionTransaction, 'subscriptionTransactionId', transactionId) - PyTestGenerics.forceDelete(sample.model,sample.pkName,req.json()[sample.pkName]) - - # Creationg via activation code - activationCode = ActivationCode.objects.get(activationCodeId=self.activationCodeId).activationCode - data ={"partyId":self.partyId, "activationCode":activationCode} - req = requests.post(url, data=data, cookies=cookies) - self.assertEqual(req.status_code, status.HTTP_201_CREATED) - self.assertIsNotNone(PyTestGenerics.forceGet(sample.model,sample.pkName,req.json()[sample.pkName])) - transactionId = req.json()['subscriptionTransactionId'] - self.assertIsNotNone(PyTestGenerics.forceGet(SubscriptionTransaction,'subscriptionTransactionId',transactionId)) - PyTestGenerics.forceDelete(SubscriptionTransaction, 'subscriptionTransactionId', transactionId) - PyTestGenerics.forceDelete(sample.model,sample.pkName,req.json()[sample.pkName]) - - def tearDown(self): - super(SubscriptionCRUD,self).tearDown() - PyTestGenerics.forceDelete(self.partySample.model, self.partySample.pkName, self.partyId) - PyTestGenerics.forceDelete(self.partnerSample.model, self.partnerSample.pkName, self.partnerId) - PyTestGenerics.forceDelete(self.activationCodeSample.model, self.activationCodeSample.pkName, self.activationCodeId) - -class SubscriptionTransactionCRUD(GenericCRUDTest, TestCase): - sample = SubscriptionTransactionSample(serverUrl) - - partySample = PartySample(serverUrl) - partnerSample = PartnerSample(serverUrl) - subscriptionSample = SubscriptionSample(serverUrl) - - def setUp(self): - super(SubscriptionTransactionCRUD,self).setUp() - self.partyId = self.partySample.forcePost(self.partySample.data) - Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() - self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) - self.subscriptionSample.data['partyId']=self.partyId - self.subscriptionSample.data['partnerId']=self.partnerId - self.subscriptionId = self.subscriptionSample.forcePost(self.subscriptionSample.data) - self.sample.data['subscriptionId']=self.sample.updateData['subscriptionId']=self.subscriptionId - - def tearDown(self): - super(SubscriptionTransactionCRUD,self).tearDown() - PyTestGenerics.forceDelete(self.partySample.model, self.partySample.pkName, self.partyId) - PyTestGenerics.forceDelete(self.partnerSample.model, self.partnerSample.pkName, self.partnerId) - PyTestGenerics.forceDelete(self.subscriptionSample.model, self.subscriptionSample.pkName, self.subscriptionId) - -# ----------------- END OF BASIC CRUD OPERATIONS ---------------------- - -class SubscriptionRenewalTest(GenericTest, TestCase): - def test_for_update(self): - subscriptionSample = SubscriptionSample(serverUrl) - partnerSample = PartnerSample(serverUrl) - partySample = PartySample(serverUrl) - - Partner.objects.filter(partnerId=partnerSample.data['partnerId']).delete() - partnerId = partnerSample.forcePost(partnerSample.data) - partyId = partySample.forcePost(partySample.data) - - subscriptionSample.data['partnerId']=subscriptionSample.updateData['partnerId']=partnerId - subscriptionSample.data['partyId']=subscriptionSample.updateData['partyId']=partyId - subscriptionId = subscriptionSample.forcePost(subscriptionSample.data) - - url = serverUrl + 'subscriptions/' + str(subscriptionId) + '/renewal?apiKey=%s' % self.apiKey - cookies = {'apiKey':self.apiKey} - req = requests.put(url, data=subscriptionSample.updateData,cookies=cookies) - self.assertEqual(req.status_code, 200) - transactionId = req.json()['subscriptionTransactionId'] - self.assertIsNotNone(PyTestGenerics.forceGet(SubscriptionTransaction,'subscriptionTransactionId',transactionId)) - PyTestGenerics.forceDelete(SubscriptionTransaction, 'subscriptionTransactionId', transactionId) - PyTestGenerics.forceDelete(subscriptionSample.model,subscriptionSample.pkName,subscriptionId) - PyTestGenerics.forceDelete(partnerSample.model,partnerSample.pkName,partnerId) - PyTestGenerics.forceDelete(partySample.model,partySample.pkName,partyId) - -class PostPaymentHandlingTest(GenericTest, TestCase): - - partnerSample = PartnerSample(serverUrl) - subscriptionTermSample = SubscriptionTermSample(serverUrl) - - def setUp(self): - super(PostPaymentHandlingTest, self).setUp() - Partner.objects.filter(partnerId=self.partnerSample.data['partnerId']).delete() - self.partnerId = self.partnerSample.forcePost(self.partnerSample.data) - self.subscriptionTermSample.data['partnerId'] = self.partnerId - self.subscriptionTermId = self.subscriptionTermSample.forcePost(self.subscriptionTermSample.data) - - def test_for_postPaymentSubscription(self): - activationCodeArray = PaymentControl.postPaymentHandling(self.subscriptionTermId, 5) - for activationCode in activationCodeArray: - self.assertIsNotNone(PyTestGenerics.forceGet(ActivationCode, 'activationCode', activationCode)) - PyTestGenerics.forceDelete(ActivationCode, 'activationCode', activationCode) - - def tearDown(self): - super(PostPaymentHandlingTest, self).tearDown() - PyTestGenerics.forceDelete(self.partnerSample.model, self.partnerSample.pkName, self.partnerId) - PyTestGenerics.forceDelete(self.subscriptionTermSample.model, self.subscriptionTermSample.pkName, self.subscriptionTermId) - -print "Running unit tests on subscription web services API........." - -if __name__ == '__main__': - sys.argv[1:] = [] - unittest.main() - ret = not runner.run(suite).wasSuccessful() - sys.exit(ret) diff --git a/subscription/testIndividualEmail.py b/subscription/runTestIndividualEmail.py similarity index 100% rename from subscription/testIndividualEmail.py rename to subscription/runTestIndividualEmail.py diff --git a/subscription/testSamples.py b/subscription/testSamples.py index aa1641f8..16129f25 100644 --- a/subscription/testSamples.py +++ b/subscription/testSamples.py @@ -1,40 +1,66 @@ -import django -import unittest -import sys, getopt -from unittest import TestCase +import copy from subscription.models import Subscription, SubscriptionTransaction, ActivationCode from party.models import Party from partner.models import Partner -import requests -import json +from datetime import timedelta +from django.utils import timezone +from common.tests import TestGenericInterfaces -import copy -from common.pyTests import PyTestGenerics +genericForcePost = TestGenericInterfaces.forcePost -genericForcePost = PyTestGenerics.forcePost +NUM_DAYS_AFTER_PURCHASE = 2 +NUM_SUBSCRIBED_DAYS = 30 +UPDATE_NUM_DAYS_AFTER_PURCHASE = 1 +UPDATE_NUM_SUBSCRIBED_DAYS = 60 + +def getDateTimeString(dateTimeObj): + return dateTimeObj.strftime("%Y-%m-%dT%H:%M:%S.%fZ") class ActivationCodeSample(): path = 'subscriptions/activationCodes/' url = None + TRANSACTION_TYPE = 'create_free' data = { 'activationCode':'testactivationcode', 'partnerId':None, - 'partyId':None, - 'period':180, - 'purchaseDate':'2001-01-01T00:00:00Z', + # partyId is assigned upon activation not on creation + # 'partyId':None, + 'period':NUM_SUBSCRIBED_DAYS, + 'purchaseDate':None, } updateData = { 'activationCode':'testactivationcode2', 'partnerId':None, - 'partyId':None, - 'period':150, - 'purchaseDate':'2005-01-01T00:00:00Z', + # 'partyId':None, + 'period':UPDATE_NUM_SUBSCRIBED_DAYS, + 'purchaseDate':None, } pkName = 'activationCodeId' model = ActivationCode def __init__(self, serverUrl): self.url = serverUrl+self.path + self.data['purchaseDate'] = getDateTimeString(timezone.now() - timedelta(days=NUM_DAYS_AFTER_PURCHASE)) + self.updateData['purchaseDate'] = getDateTimeString(timezone.now() - timedelta(days=UPDATE_NUM_DAYS_AFTER_PURCHASE)) + + def setPartnerId(self, partnerId): + self.data['partnerId'] = partnerId + + # for get endpoint transactionType is not returned, so we only initialize this param when needed + def initTransctionType(self): + self.data['transactionType'] = self.TRANSACTION_TYPE + + def getActivationCode(self): + return self.data['activationCode'] + + def getPeriod(self): + return self.data['period'] + + def getTransactionType(self): + return self.data['transactionType'] + + def getPartnerId(self): + return self.data['partnerId'] def forcePost(self,data): postData = copy.deepcopy(data) @@ -45,22 +71,43 @@ class SubscriptionSample(): path = 'subscriptions/' url = None data = { - 'startDate':'2012-04-12T00:00:00Z', - 'endDate':'2018-04-12T00:00:00Z', - 'partnerId':'tair', - 'partyId':1, + 'startDate':None, + 'endDate':None, + 'partnerId':None, + 'partyId':None, } updateData = { - 'startDate':'2012-04-12T00:00:00Z', - 'endDate':'2018-04-12T00:00:00Z', - 'partnerId':'cdiff', - 'partyId':1, + 'startDate':None, + 'partnerId':None, + 'partyId':None, } pkName = 'subscriptionId' model = Subscription def __init__(self, serverUrl): self.url = serverUrl+self.path + self.data = copy.deepcopy(self.data) + + startDateObj = timezone.now() - timedelta(days=NUM_DAYS_AFTER_PURCHASE) + self.data['startDate'] = getDateTimeString(startDateObj) + self.data['endDate'] = getDateTimeString(startDateObj + timedelta(days=NUM_SUBSCRIBED_DAYS)) + + updateStartDateObj = timezone.now() - timedelta(days=UPDATE_NUM_DAYS_AFTER_PURCHASE) + self.updateData['startDate'] = getDateTimeString(updateStartDateObj) + self.updateData['endDate'] = getDateTimeString(updateStartDateObj + timedelta(days=UPDATE_NUM_SUBSCRIBED_DAYS)) + + def setPartnerId(self, partnerId): + self.data['partnerId'] = partnerId + + def setPartyId(self, partyId): + self.data['partyId'] = partyId + + def getEndDate(self): + return self.data['endDate'] + + def setAsExpired(self): + self.data['startDate'] = getDateTimeString(timezone.now() - timedelta(days=2 * NUM_SUBSCRIBED_DAYS)) + self.data['endDate'] = getDateTimeString(timezone.now() - timedelta(days=NUM_SUBSCRIBED_DAYS)) def forcePost(self,data): postData = copy.deepcopy(data) @@ -68,22 +115,19 @@ def forcePost(self,data): postData['partnerId'] = Partner.objects.get(partnerId=data['partnerId']) return genericForcePost(self.model, self.pkName, postData) - class SubscriptionTransactionSample(): path = 'subscriptions/transactions/' - url = None + url = None data = { - 'subscriptionId':1, - 'transactionDate':'2012-04-12T00:00:00Z', - 'startDate':'2012-04-12T00:00:00Z', - 'endDate':'2018-04-12T00:00:00Z', + 'transactionDate':None, + 'startDate':None, + 'endDate':None, 'transactionType':'create', } updateData = { - 'subscriptionId':1, - 'transactionDate':'2014-02-12T00:00:00Z', - 'startDate':'2012-04-12T00:00:00Z', - 'endDate':'2018-04-12T00:00:00Z', + 'transactionDate':None, + 'startDate':None, + 'endDate':None, 'transactionType':'renew', } pkName = 'subscriptionTransactionId' @@ -92,8 +136,19 @@ class SubscriptionTransactionSample(): def __init__(self, serverUrl): self.url = serverUrl+self.path + transactionDateObj = timezone.now() - timedelta(days=NUM_DAYS_AFTER_PURCHASE) + self.data['transactionDate'] = getDateTimeString(transactionDateObj) + self.data['startDate'] = self.data['transactionDate'] + self.data['endDate'] = getDateTimeString(transactionDateObj + timedelta(days=NUM_SUBSCRIBED_DAYS)) + + updateTransactionDateObj = timezone.now() - timedelta(days=UPDATE_NUM_DAYS_AFTER_PURCHASE) + self.updateData['transactionDate'] = getDateTimeString(updateTransactionDateObj) + self.updateData['startDate'] = self.updateData['transactionDate'] + self.updateData['endDate'] = getDateTimeString(updateTransactionDateObj + timedelta(days=UPDATE_NUM_DAYS_AFTER_PURCHASE)) + + def forcePost(self,data): postData = copy.deepcopy(data) - postData['subscriptionId'] = Subscription.objects.get(subscriptionId=data['subscriptionId']) + if ('subscriptionId' in postData): + postData['subscriptionId'] = Subscription.objects.get(subscriptionId=postData['subscriptionId']) return genericForcePost(self.model, self.pkName, postData) - diff --git a/subscription/tests.py b/subscription/tests.py index 0c4f009a..2053203d 100644 --- a/subscription/tests.py +++ b/subscription/tests.py @@ -1,9 +1,843 @@ -#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. +#Copyright 2015 Phoenix Bioinformatics Corporation. All rights reserved. +import django +import unittest +import sys +import json +import copy +from django.test import TestCase +from subscription.models import Subscription, SubscriptionTransaction, ActivationCode +from .testSamples import SubscriptionSample, SubscriptionTransactionSample, ActivationCodeSample +from party.testSamples import UserPartySample, CountrySample, OrganizationPartySample, IpRangeSample, ConsortiumPartySample, InstitutionPartySample, PartyAffiliationSample, ImageInfoSample +from partner.testSamples import PartnerSample, SubscriptionTermSample +from authentication.testSamples import CredentialSample +from common.tests import TestGenericInterfaces, GenericCRUDTest, GenericTest, LoginRequiredTest, ManualTest, checkMatch, checkMatchDB +from rest_framework import status +from .controls import PaymentControl +from http.cookies import SimpleCookie +# Create your tests here. +django.setup() +serverUrl = TestGenericInterfaces.getHost() +# ---------------------- UNIT TEST FOR BASIC CRUD OPERATIONS ------------- +# test for API end point /subscriptions/ +# except for GET that is explicitly override in end point, all other actions +# (UPDATE and DELETE) queries item by party id instead of subscription id +class SubscriptionCRUDTest(LoginRequiredTest, TestCase): + partyId = None + partnerId = None + sample = SubscriptionSample(serverUrl) -from django.test import TestCase + def setUp(self): + super(SubscriptionCRUDTest,self).setUp() + + partnerSample = PartnerSample(serverUrl) + self.partnerId = partnerSample.forcePost(partnerSample.data) + + partySample = UserPartySample(serverUrl) + self.partyId = partySample.forcePost(partySample.data) + + self.sample.data['partyId']=self.sample.updateData['partyId']=self.partyId + self.sample.data['partnerId']=self.sample.updateData['partnerId']=self.partnerId + + # post request for subscription is not generic. It creates a SubscriptionTransaction + # object in addition to a Subscription object. + # creation via basic CRUD format + # Need valid phoenix credential + # Potential API end point threat: no role based authorization + def test_for_create(self): + sample = self.sample + url = self.getUrl(sample.url) + + res = self.client.post(url, sample.data) + self.assertEqual(res.status_code, status.HTTP_201_CREATED) + resObj = json.loads(res.content) + self.assertEqual(checkMatchDB(sample.data, sample.model,sample.pkName,resObj[sample.pkName]), True) + transactionId = resObj['subscriptionTransactionId'] + self.assertIsNotNone(TestGenericInterfaces.forceGet(SubscriptionTransaction,'subscriptionTransactionId',transactionId)) + + # create subscription by activation code + def test_for_create_by_activation(self): + sample = self.sample + # no need for user credential + url = sample.url + + activationCodeSample = ActivationCodeSample(serverUrl) + activationCodeSample.initTransctionType() + activationCodeSample.setPartnerId(self.partnerId) + activationCodeSample.forcePost(activationCodeSample.data) + + activationCode = activationCodeSample.getActivationCode() + data ={"partyId":self.partyId, "activationCode":activationCode} + res = self.client.post(url, data) + self.assertEqual(res.status_code, status.HTTP_201_CREATED) + resObj = json.loads(res.content) + self.assertIsNotNone(TestGenericInterfaces.forceGet(sample.model,sample.pkName,resObj[sample.pkName])) + transactionId = resObj['subscriptionTransactionId'] + self.assertIsNotNone(TestGenericInterfaces.forceGet(SubscriptionTransaction,'subscriptionTransactionId',transactionId)) + + # get by subscriptionId, partyId or userIdentifier + def test_for_get(self): + sample = self.sample + pk = sample.forcePost(sample.data) + + # get by subscriptionId + url = self.getUrl(sample.url, sample.pkName, pk) + self.runGetTestByIdentifier(url, pk) + + # get by partyId + url = self.getUrl(sample.url, 'partyId', self.partyId) + self.runGetTestByIdentifier(url, pk) + + # get by userIdentifier + credentialSample = CredentialSample(serverUrl) + credentialSample.setPartyId(self.partyId) + credentialSample.setPartnerId(self.partnerId) + credentialSample.forcePost(credentialSample.data) + + url = self.getUrl(sample.url, 'userIdentifier', credentialSample.getUserIdentifier()) + # need to pass ipAddress as arg even when not needed + url = '%s&partnerId=%s&ipAddress=' % (url, self.partnerId) + self.runGetTestByIdentifier(url, pk) + + # get by ipAddress + def test_for_get_by_ip_address(self): + # override party setup + countrySample = CountrySample(serverUrl) + countryId = countrySample.forcePost(countrySample.data) + + partySample = OrganizationPartySample(serverUrl) + partySample.setCountry(countryId) + self.partyId = partySample.forcePost(partySample.data) + + ipRangeSample = IpRangeSample(serverUrl) + ipRangeSample.setPartyId(self.partyId) + ipRangeSample.forcePost(IpRangeSample.data) + + subIp = ipRangeSample.getInRangeIp() + + sample = self.sample + sample.setPartyId(self.partyId) + pk = sample.forcePost(sample.data) + + url = self.getUrl(sample.url, 'ipAddress', subIp) + # need to pass userIdentifier as arg even when not needed + url = '%s&partnerId=%s&userIdentifier=' % (url, self.partnerId) + self.runGetTestByIdentifier(url, pk) + + def runGetTestByIdentifier(self, url, pk): + sample = self.sample + + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + self.assertEqual(checkMatch(sample.data, json.loads(res.content), sample.pkName, pk), True) + + # can only update by party id + def test_for_update(self): + sample = self.sample + pk = sample.forcePost(sample.data) + url = self.getUrl(sample.url, 'partyId', self.partyId) + + # the default content type for put is 'application/octet-stream' + res = self.client.put(url, json.dumps(sample.updateData), content_type='application/json') + + self.assertEqual(res.status_code, 200) + self.assertEqual(checkMatch(sample.updateData, json.loads(res.content), sample.pkName, pk), True) + self.assertEqual(checkMatchDB(sample.updateData, sample.model, sample.pkName, pk), True) + + # can only delete by party id + def test_for_delete(self): + sample = self.sample + pk = sample.forcePost(sample.data) + url = self.getUrl(sample.url, 'partyId', self.partyId) + + res = self.client.delete(url) + + self.assertIsNone(TestGenericInterfaces.forceGet(sample.model,sample.pkName,pk)) + self.assertIsNone(TestGenericInterfaces.forceGet(sample.model,'partyId',self.partyId)) + +# test for API end point /subscriptions/activationCodes/ for create and update +# need to authenticate user +class ActivationCodeCreateAndUpdateTest(LoginRequiredTest, TestCase): + sample = ActivationCodeSample(serverUrl) + + def setUp(self): + super(ActivationCodeCreateAndUpdateTest, self).setUp() + + self.sample.initTransctionType() + + partnerSample = PartnerSample(serverUrl) + partnerId = partnerSample.forcePost(partnerSample.data) + self.sample.data['partnerId'] = self.sample.updateData['partnerId']=partnerId + + # Potential API end point threat: no role based authorization + def test_for_create(self): + sample = self.sample + url = self.getUrl(sample.url) + url = '%s&quantity=%s&period=%s&partnerId=%s&transactionType=%s' % (url, + 1, sample.getPeriod(), sample.getPartnerId(),sample.getTransactionType()) + res = self.client.post(url) + + self.assertEqual(res.status_code, 200) + self.assertIsNotNone(TestGenericInterfaces.forceGet(sample.model,'activationCode',json.loads(res.content)[0])) + + # update activation code as deleted + def test_for_update(self): + sample = self.sample + pk = sample.forcePost(sample.data) + url = self.getUrl(sample.url, sample.pkName, pk) + url += '&deleteMarker=true' + res = self.client.put(url) + + self.assertEqual(res.status_code, 200) + self.assertEqual(TestGenericInterfaces.forceGet(sample.model,sample.pkName,pk).deleteMarker, True) + +# test for API end point /subscriptions/activationCodes/ for get and delete +# returned activation code data does not include transaction type +class ActivationCodeGetAndDeleteTest(GenericCRUDTest, TestCase): + sample = ActivationCodeSample(serverUrl) + + def setUp(self): + super(ActivationCodeGetAndDeleteTest, self).setUp() + + partnerSample = PartnerSample(serverUrl) + partnerId = partnerSample.forcePost(partnerSample.data) + self.sample.data['partnerId'] = self.sample.updateData['partnerId']=partnerId + + + def test_for_get(self): + sample = self.sample + pk = sample.forcePost(sample.data) + url = self.getUrl(sample.url, sample.pkName, pk) + + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + # transaction is not returned in API + data = copy.deepcopy(sample.data) + del data['transactionType'] + self.assertEqual(checkMatch(data, json.loads(res.content), sample.pkName, pk), True) + + def test_for_get_all(self): + sample = self.sample + pk = sample.forcePost(sample.data) + url = self.getUrl(sample.url) + + res = self.client.get(url) + self.assertEqual(res.status_code, 200) + # transaction is not returned in API + data = copy.deepcopy(sample.data) + del data['transactionType'] + self.assertEqual(checkMatch(data, json.loads(res.content), sample.pkName, pk), True) + + # Tested in above class + def test_for_create(self): + pass + + # Tested in above class + def test_for_update(self): + pass + + # delete method can be tested but shall not be used. + # should use update method to update delete marker instead + +# test for API end point /subscriptions/transactions/ +class SubscriptionTransactionCRUDTest(GenericCRUDTest, TestCase): + sample = SubscriptionTransactionSample(serverUrl) + + def setUp(self): + super(SubscriptionTransactionCRUDTest,self).setUp() + + partnerSample = PartnerSample(serverUrl) + partnerId = partnerSample.forcePost(partnerSample.data) + + partySample = UserPartySample(serverUrl) + partyId = partySample.forcePost(partySample.data) + + subscriptionSample = SubscriptionSample(serverUrl) + subscriptionSample.setPartnerId(partnerId) + subscriptionSample.setPartyId(partyId) + subscriptionId = subscriptionSample.forcePost(subscriptionSample.data) + + self.sample.data['subscriptionId']=self.sample.updateData['subscriptionId']=subscriptionId + +# ----------------- END OF BASIC CRUD OPERATIONS ---------------------- + +# Note that for subscription end points, unless specifically designed (/subscriptions/consortiums/ +# and /subscriptions/consactsubscriptions/), otherwise they only return the subscription record of +# the organization itself; they do not check/return the subscription of its consortium + +# No API end point or method update consortiumId, consortiumStartDate and consortiumEndDate; +# they are updated by scripts/updateConsortiumSubscriptionFields.py on a daily basis by cron job +# on API server. + +# The consortiumStartDate and consortiumEndDate values are for reference only and are not used in validating +# a subscription. Instead, when an institution or an individual that belongs to a consortium is checked for +# authorization, the authorization end points calls method that checks not only their own subscription but +# also the subscription of their consortium. But still, startDate and endDate of the record are used + +# test for API end point /subscriptions/{subscriptionId}/renewal/ +# update subscription duration directly +# will create subscription transaction +class SubscriptionRenewalTest(GenericTest, TestCase): + sample = SubscriptionSample(serverUrl) + + def setUp(self): + super(SubscriptionRenewalTest,self).setUp() + + partnerSample = PartnerSample(serverUrl) + partnerId = partnerSample.forcePost(partnerSample.data) + + partySample = UserPartySample(serverUrl) + partyId = partySample.forcePost(partySample.data) + + sample = self.sample + sample.data['partnerId']=sample.updateData['partnerId']=partnerId + sample.data['partyId']=sample.updateData['partyId']=partyId + + def test_for_update(self): + sample = self.sample + subscriptionId = sample.forcePost(sample.data) + + url = serverUrl + 'subscriptions/' + str(subscriptionId) + '/renewal/' + # the default content type for put is 'application/octet-stream' + res = self.client.put(url, json.dumps(sample.updateData), content_type='application/json') + + self.assertEqual(res.status_code, 200) + self.assertEqual(checkMatch(sample.updateData, json.loads(res.content), sample.pkName, subscriptionId), True) + self.assertEqual(checkMatchDB(sample.updateData, sample.model, sample.pkName, subscriptionId), True) + transactionId = json.loads(res.content)['subscriptionTransactionId'] + self.assertIsNotNone(TestGenericInterfaces.forceGet(SubscriptionTransaction,'subscriptionTransactionId',transactionId)) + +# test for API end point /subscriptions/payments/ +# cannot test via POST request to API since not sure how to generate a stripeToken +class PostPaymentHandlingTest(ManualTest, TestCase): + path = "/subscriptions/payments/" + testMethodStr = "by submitting an order on ui.arabidopsis.org" + +# test for API end point /subscriptions/enddate/ +# end point looks for the effective subscription with latest end date that either covers the given IP address +# or belongs to the given party for a given partner +# end point returns the expiration date of the found subscription and 'subscribed' status as True; +# otherwise, the returned expiration date is null and status is False. +# TODO: End point cannot accept ipAddress alone without partyId or userIdentifier, which I do not see a reason to it +class GetSubcriptionEndDateTest(GenericTest, TestCase): + partnerId = None + userPartyId = None + userIdentifier = None + orgPartyId = None + orgInRangeIp = None + USER_SUBSCRIPTION_TYPE = 'individual' + ORG_SUBSCRIPTION_TYPE = 'institutional' + + def setUp(self): + super(GetSubcriptionEndDateTest,self).setUp() + + partnerSample = PartnerSample(serverUrl) + self.partnerId = partnerSample.forcePost(partnerSample.data) + + # create individual user + userPartySample = UserPartySample(serverUrl) + self.userPartyId = userPartySample.forcePost(userPartySample.data) + + credentialSample = CredentialSample(serverUrl) + credentialSample.setPartyId(self.userPartyId) + credentialSample.setPartnerId(self.partnerId) + credentialSample.forcePost(credentialSample.data) + self.userIdentifier = credentialSample.getUserIdentifier() + + # create organization and its subscription + countrySample = CountrySample(serverUrl) + countryId = countrySample.forcePost(countrySample.data) + + orgPartySample = OrganizationPartySample(serverUrl) + orgPartySample.setCountry(countryId) + self.orgPartyId = orgPartySample.forcePost(orgPartySample.data) + + ipRangeSample = IpRangeSample(serverUrl) + ipRangeSample.setPartyId(self.orgPartyId) + ipRangeSample.forcePost(IpRangeSample.data) + + self.orgInRangeIp = ipRangeSample.getInRangeIp() + + def test_for_no_subscription(self): + # test get by userPartyId and ipAddress when neither user nor organization is subscribed + queryParam = 'partyId=%s&ipAddress=%s' % (self.userPartyId, self.orgInRangeIp) + self.runTest(queryParam, False) + + # test get by userIdentifier and ipAddress when neither user nor organization is subscribed + queryParam = 'userIdentifier=%s&ipAddress=%s' % (self.userIdentifier, self.orgInRangeIp) + self.runTest(queryParam, False) + + def test_for_get_individual_subscription(self): + userSubscriptionSample = SubscriptionSample(serverUrl) + userSubscriptionSample.setPartyId(self.userPartyId) + userSubscriptionSample.setPartnerId(self.partnerId) + userSubscriptionSample.forcePost(userSubscriptionSample.data) + + # test get by user partyId when user is subscribed + queryParam = 'partyId=%s' % self.userPartyId + self.runTest(queryParam, True, self.USER_SUBSCRIPTION_TYPE, userSubscriptionSample.getEndDate()) + + # test get by user partyId and ipAddress when only user is subscribed + queryParam = 'partyId=%s&ipAddress=%s' % (self.userPartyId, self.orgInRangeIp) + self.runTest(queryParam, True, self.USER_SUBSCRIPTION_TYPE, userSubscriptionSample.getEndDate()) + + # test get by userIdentifier when user is subscribed + queryParam = 'userIdentifier=%s' % self.userIdentifier + self.runTest(queryParam, True, self.USER_SUBSCRIPTION_TYPE, userSubscriptionSample.getEndDate()) + + # test get by userIdentifier and ipAddress when onlyuser is subscribed + queryParam = 'userIdentifier=%s&ipAddress=%s' % (self.userIdentifier, self.orgInRangeIp) + self.runTest(queryParam, True, self.USER_SUBSCRIPTION_TYPE, userSubscriptionSample.getEndDate()) + + def test_for_get_organization_subscription(self): + # ip address cannot be passed alone. Need to pass individual credential (userIdentifier/partyId) + # as well + orgSubscriptionSample = SubscriptionSample(serverUrl) + orgSubscriptionSample.setPartyId(self.orgPartyId) + orgSubscriptionSample.setPartnerId(self.partnerId) + orgSubscriptionSample.forcePost(orgSubscriptionSample.data) + + # test get when both userIdentifier and ipAddress are provided + queryParam = 'userIdentifier=%s&ipAddress=%s' % (self.userIdentifier, self.orgInRangeIp) + self.runTest(queryParam, True, self.ORG_SUBSCRIPTION_TYPE, orgSubscriptionSample.getEndDate()) + + # test get when both userPartyId and ipAddress are provided + queryParam = 'partyId=%s&ipAddress=%s' % (self.userPartyId, self.orgInRangeIp) + self.runTest(queryParam, True, self.ORG_SUBSCRIPTION_TYPE, orgSubscriptionSample.getEndDate()) + + def test_for_have_both_subscriptions(self): + # create organization subscription before user subscription so user subscription end date is later + orgSubscriptionSample = SubscriptionSample(serverUrl) + orgSubscriptionSample.setPartyId(self.orgPartyId) + orgSubscriptionSample.setPartnerId(self.partnerId) + orgSubscriptionSample.forcePost(orgSubscriptionSample.data) + + userSubscriptionSample = SubscriptionSample(serverUrl) + userSubscriptionSample.setPartyId(self.userPartyId) + userSubscriptionSample.setPartnerId(self.partnerId) + userSubscriptionSample.forcePost(userSubscriptionSample.data) + + # organization (IP based) subscription will shadow individual subscription on subscription type + # but end date will be the latest of both so there could be inconsistency between end date and + # subscription type + + expEndDate = max(orgSubscriptionSample.getEndDate(), userSubscriptionSample.getEndDate()) + + # test get when both userIdentifier and ipAddress are provided + queryParam = 'userIdentifier=%s&ipAddress=%s' % (self.userIdentifier, self.orgInRangeIp) + self.runTest(queryParam, True, self.ORG_SUBSCRIPTION_TYPE, expEndDate) + self.runTest(queryParam, True, self.ORG_SUBSCRIPTION_TYPE, userSubscriptionSample.getEndDate()) + + # test get when both userPartyId and ipAddress are provided + queryParam = 'partyId=%s&ipAddress=%s' % (self.userPartyId, self.orgInRangeIp) + self.runTest(queryParam, True, self.ORG_SUBSCRIPTION_TYPE, expEndDate) + self.runTest(queryParam, True, self.ORG_SUBSCRIPTION_TYPE, userSubscriptionSample.getEndDate()) + + def runTest(self, queryParam, expectedSubscriptionStatus, expectedSubscriptionType = None, expectedExpDate = None): + url = '%ssubscriptions/enddate/?partnerId=%s&%s' % (serverUrl, self.partnerId, queryParam) + + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + resObj = json.loads(res.content) + self.assertEqual(expectedSubscriptionStatus, resObj['subscribed']) + if expectedSubscriptionType and resObj['subscriptionType']: + self.assertEqual(expectedSubscriptionType, resObj['subscriptionType']) + if expectedExpDate and resObj['expDate']: + self.assertEqual(expectedExpDate, resObj['expDate']) + +# test for API end point /subscriptions/membership/ +# end point looks for the effective subscription with latest end date that covers the given IP address for a given partner +# end point returns the expiration date of the found subscription and 'isMember' status as True, and also +# replies the display name or organization and its logo url queried from ImageInfo table; +# otherwise, it returns 'isMember' status as False and all other fields as None +class CheckMembershipTest(GenericTest, TestCase): + partnerId = None + orgPartyId = None + orgInRangeIp = None + imageInfoSample = ImageInfoSample() + + def setUp(self): + super(CheckMembershipTest,self).setUp() + + partnerSample = PartnerSample(serverUrl) + self.partnerId = partnerSample.forcePost(partnerSample.data) + + # create organization and its subscription + countrySample = CountrySample(serverUrl) + countryId = countrySample.forcePost(countrySample.data) + + orgPartySample = OrganizationPartySample(serverUrl) + orgPartySample.setCountry(countryId) + self.orgPartyId = orgPartySample.forcePost(orgPartySample.data) + + imageInfoSample = self.imageInfoSample + imageInfoSample.setPartyId(self.orgPartyId) + imageInfoSample.forcePost(imageInfoSample.data) + + ipRangeSample = IpRangeSample(serverUrl) + ipRangeSample.setPartyId(self.orgPartyId) + ipRangeSample.forcePost(IpRangeSample.data) + + self.orgInRangeIp = ipRangeSample.getInRangeIp() + + def test_for_get(self): + + # test before organization subscribes + url = '%ssubscriptions/membership/?partnerId=%s&ipAddress=%s' % (serverUrl, self.partnerId, self.orgInRangeIp) + + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + resObj = json.loads(res.content) + self.assertEqual(resObj['isMember'], False) + + orgSubscriptionSample = SubscriptionSample(serverUrl) + orgSubscriptionSample.setPartyId(self.orgPartyId) + orgSubscriptionSample.setPartnerId(self.partnerId) + orgSubscriptionSample.forcePost(orgSubscriptionSample.data) + + # test after organization subscribes + imageInfoSample = self.imageInfoSample + res = self.client.get(url) + self.assertEqual(res.status_code, 200) + resObj = json.loads(res.content) + self.assertEqual(resObj['isMember'], True) + self.assertEqual(resObj['name'], imageInfoSample.getName()) + self.assertEqual(resObj['imageUrl'], imageInfoSample.getImageUrl()) + self.assertEqual(resObj['expDate'], orgSubscriptionSample.getEndDate()) + +# test for API end point /subscriptions/subscriptionrequest/ +# this end point seems not working. Will print a warning to ask manual test +class SubscriptionRequestTest(ManualTest, TestCase): + path = "/subscriptions/subscriptionrequest/" + testMethodStr = "clicking Subcription - Download All Requests on ui.arabidopsis.org/adminportal/" + +# test for API end point /subscriptions/active/ +# endpoint returns ALL ACTIVE subscription and party info that covers the given IP address or belongs to the given +# user identifier for a given partner +# ipAddress must be passed in; userIdentifier is optional +class GetActiveSubscriptionTest(GenericTest, TestCase): + partnerId = None + userPartySample = UserPartySample(serverUrl) + userPartyId = None + userIdentifier = None + orgPartySample = OrganizationPartySample(serverUrl) + orgPartyId = None + orgInRangeIp = None + + def setUp(self): + super(GetActiveSubscriptionTest,self).setUp() + + partnerSample = PartnerSample(serverUrl) + self.partnerId = partnerSample.forcePost(partnerSample.data) + + # create individual user + self.userPartyId = self.userPartySample.forcePost(self.userPartySample.data) + + credentialSample = CredentialSample(serverUrl) + credentialSample.setPartyId(self.userPartyId) + credentialSample.setPartnerId(self.partnerId) + credentialSample.forcePost(credentialSample.data) + self.userIdentifier = credentialSample.getUserIdentifier() + + # create organization and its subscription + countrySample = CountrySample(serverUrl) + countryId = countrySample.forcePost(countrySample.data) + + self.orgPartySample.setCountry(countryId) + self.orgPartyId = self.orgPartySample.forcePost(self.orgPartySample.data) + + ipRangeSample = IpRangeSample(serverUrl) + ipRangeSample.setPartyId(self.orgPartyId) + ipRangeSample.forcePost(IpRangeSample.data) + + self.orgInRangeIp = ipRangeSample.getInRangeIp() + + def test_for_no_subscription(self): + self.runTest(partyId = None, expectedPartyData = None, subscriptionId = None, expectSubscriptionData = None) + + def test_for_get_individual_subscription(self): + userSubscriptionSample = SubscriptionSample(serverUrl) + userSubscriptionSample.setPartyId(self.userPartyId) + userSubscriptionSample.setPartnerId(self.partnerId) + subscriptionId = userSubscriptionSample.forcePost(userSubscriptionSample.data) + + self.runTest(self.userPartyId, self.userPartySample.data, subscriptionId, userSubscriptionSample.data) + + def test_for_get_organization_subscription(self): + # ip address cannot be passed alone. Need to pass individual credential (userIdentifier/partyId) + # as well + orgSubscriptionSample = SubscriptionSample(serverUrl) + orgSubscriptionSample.setPartyId(self.orgPartyId) + orgSubscriptionSample.setPartnerId(self.partnerId) + subscriptionId = orgSubscriptionSample.forcePost(orgSubscriptionSample.data) + + self.runTest(self.orgPartyId, self.getOrgDataToCompare(), subscriptionId, orgSubscriptionSample.data) + + def test_for_have_both_subscriptions(self): + orgSubscriptionSample = SubscriptionSample(serverUrl) + orgSubscriptionSample.setPartyId(self.orgPartyId) + orgSubscriptionSample.setPartnerId(self.partnerId) + orgSubId = orgSubscriptionSample.forcePost(orgSubscriptionSample.data) + + userSubscriptionSample = SubscriptionSample(serverUrl) + userSubscriptionSample.setPartyId(self.userPartyId) + userSubscriptionSample.setPartnerId(self.partnerId) + userSubId = userSubscriptionSample.forcePost(userSubscriptionSample.data) + + self.runTest(self.userPartyId, self.userPartySample.data, userSubId, userSubscriptionSample.data) + self.runTest(self.orgPartyId, self.getOrgDataToCompare(), orgSubId, orgSubscriptionSample.data) + + def runTest(self, partyId = None, expectedPartyData = None, subscriptionId = None, expectSubscriptionData = None): + # ipAddress must be passed in; userIdentifier is optional + url = '%ssubscriptions/active/?partnerId=%s&userIdentifier=%s&ipAddress=%s' % (serverUrl, + self.partnerId, self.userIdentifier, self.orgInRangeIp) + + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + resObj = json.loads(res.content) + if not partyId and not subscriptionId: + self.assertFalse(resObj) + return + if partyId: + self.assertEqual(checkMatch(expectedPartyData, resObj, 'partyId', partyId), True) + if subscriptionId: + self.assertEqual(checkMatch(expectSubscriptionData, resObj, 'subscriptionId', subscriptionId), True) + + def getOrgDataToCompare(self): + # remove fields that are not returned by API end point + return { + 'name': self.orgPartySample.getName(), + 'partyType': self.orgPartySample.getPartyType() + } + +# test for API end point /subscriptions/activesubscriptions/{partyId}/ +# endpoint returns ALL ACTIVE subscriptions that belongs to a partyId for ALL partners +# end point returns {'partnerId_1':[{subscription detail}], 'partnerId_2':[{subscription detail}]...} +class GetActiveSubscriptionByPartyIdTest(GenericTest, TestCase): + partnerId = None + subscriptionSample = SubscriptionSample(serverUrl) + + def setUp(self): + super(GetActiveSubscriptionByPartyIdTest,self).setUp() + + partnerSample = PartnerSample(serverUrl) + self.partnerId = partnerSample.forcePost(partnerSample.data) + + self.subscriptionSample.setPartnerId(self.partnerId) + + def test_for_get_individual_subscription(self): + userPartySample = UserPartySample(serverUrl) + partyId = userPartySample.forcePost(userPartySample.data) + + self.runTest(partyId) + + def test_for_get_organization_subscription(self): + countrySample = CountrySample(serverUrl) + countryId = countrySample.forcePost(countrySample.data) + + orgPartySample = OrganizationPartySample(serverUrl) + orgPartySample.setCountry(countryId) + partyId = orgPartySample.forcePost(orgPartySample.data) + + self.runTest(partyId) + + def runTest(self, partyId): + subscriptionSample = self.subscriptionSample + subscriptionSample.setPartyId(partyId) + subscriptionId = subscriptionSample.forcePost(subscriptionSample.data) + + url = '%ssubscriptions/activesubscriptions/%s/' % (serverUrl, partyId) + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + self.assertEqual(checkMatch(self.subscriptionSample.data, json.loads(res.content)[self.partnerId], + 'subscriptionId', subscriptionId), True) + +# test for API end point /subscriptions/allsubscriptions/{partyId}/ +# endpoint returns ALL subscriptions that belongs to a partyId for ALL partners, including active ones +# and expired ones. Note that for each partner a party can only have an active subscription or an expired +# subscription +# end point returns {'partnerId_1':[{subscription detail}], 'partnerId_2':[{subscription detail}]...} +class GetSubscriptionHistoryByPartyIdTest(GenericTest, TestCase): + partnerIdWithActiveSubscription = None + partnerIdWithExpiredSubscription = None + subscriptionSample = SubscriptionSample(serverUrl) + expiredSubscriptionSample = SubscriptionSample(serverUrl) + expiredSubscriptionSample.setAsExpired() + + def setUp(self): + super(GetSubscriptionHistoryByPartyIdTest,self).setUp() + + partnerSample = PartnerSample(serverUrl) + self.partnerIdWithActiveSubscription = partnerSample.forcePost(partnerSample.data) + partnerSample.setDifferentPartnerId() + self.partnerIdWithExpiredSubscription = partnerSample.forcePost(partnerSample.data) + + self.subscriptionSample.setPartnerId(self.partnerIdWithActiveSubscription) + self.expiredSubscriptionSample.setPartnerId(self.partnerIdWithExpiredSubscription) + + def test_for_get_individual_subscription(self): + userPartySample = UserPartySample(serverUrl) + partyId = userPartySample.forcePost(userPartySample.data) + + self.runTest(partyId) + + def test_for_get_organization_subscription(self): + countrySample = CountrySample(serverUrl) + countryId = countrySample.forcePost(countrySample.data) + + orgPartySample = OrganizationPartySample(serverUrl) + orgPartySample.setCountry(countryId) + partyId = orgPartySample.forcePost(orgPartySample.data) + + self.runTest(partyId) + + def runTest(self, partyId): + subscriptionSample = self.subscriptionSample + subscriptionSample.setPartyId(partyId) + subscriptionId = subscriptionSample.forcePost(subscriptionSample.data) + + expiredSubscriptionSample = self.expiredSubscriptionSample + expiredSubscriptionSample.setPartyId(partyId) + expSubscriptionId = expiredSubscriptionSample.forcePost(expiredSubscriptionSample.data) + + url = '%ssubscriptions/allsubscriptions/%s/' % (serverUrl, partyId) + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + resObj = json.loads(res.content) + self.assertEqual(checkMatch(self.subscriptionSample.data, resObj[self.partnerIdWithActiveSubscription], + 'subscriptionId', subscriptionId), True) + self.assertEqual(checkMatch(self.expiredSubscriptionSample.data, resObj[self.partnerIdWithExpiredSubscription], + 'subscriptionId', expSubscriptionId), True) + +# test for API end point /subscriptions/consortiums/ +# check for an institution, for each partner which of its consortium have an active subscription +# passed in partyId is the partyId of the institution +# end point returns {'partnerId_1':[{subscribed consortium data}], 'partnerId_2':[{subscribed consortium data}]...} +# DOES NOT return subscription details +# TODO: to use current API end point we have to pass in param "active" and its value has to be true, which makes +# behavior of this end point to be identical to the end point below: /subscriptions/consactsubscriptions/{partyId} +class GetConsortiumSubcriptionTest(GenericTest, TestCase): + consortiumPartySample = ConsortiumPartySample(serverUrl) + partnerId = None + consortiumPartyId = None + institutionPartyId = None + + def setUp(self): + super(GetConsortiumSubcriptionTest,self).setUp() + + partnerSample = PartnerSample(serverUrl) + self.partnerId = partnerSample.forcePost(partnerSample.data) + + countrySample = CountrySample(serverUrl) + countryId = countrySample.forcePost(countrySample.data) + + parentPartySample = self.consortiumPartySample + parentPartySample.setCountry(countryId) + self.consortiumPartyId = parentPartyId = parentPartySample.forcePost(parentPartySample.data) + + subscriptionSample = SubscriptionSample(serverUrl) + subscriptionSample.setPartnerId(self.partnerId) + subscriptionSample.setPartyId(parentPartyId) + subscriptionSample.forcePost(subscriptionSample.data) + + childPartySample = InstitutionPartySample(serverUrl) + childPartySample.setCountry(countryId) + self.institutionPartyId = childPartyId = childPartySample.forcePost(childPartySample.data) + + affliationSample = PartyAffiliationSample(serverUrl) + affliationSample.setParentId(parentPartyId) + affliationSample.setChildId(childPartyId) + affliationSample.forcePost(affliationSample.data) + + def test_for_get(self): + + url = '%ssubscriptions/consortiums/?active=true&partyId=%s' % (serverUrl, self.institutionPartyId) + + res = self.client.get(url) + + self.assertEqual(res.status_code, 200) + self.assertEqual(checkMatch(self.consortiumPartySample.data, json.loads(res.content)[self.partnerId], 'partyId', self.consortiumPartyId), True) + +# test for API end point /subscriptions/consactsubscriptions/{partyId} for Consortium Active Subscription +# check for an institution, for each partner which of its consortium have an active subscription +# passed in partyId is the partyId of the institution +# end point returns {'partnerId_1':[{subscribed consortium data}], 'partnerId_2':[{subscribed consortium data}]...} +# DOES NOT return subscription details +class GetConsortiumActiveSubscriptionTest(GenericTest, TestCase): + consortiumPartySample = ConsortiumPartySample(serverUrl) + partnerId = None + consortiumPartyId = None + institutionPartyId = None + + def setUp(self): + super(GetConsortiumActiveSubscriptionTest,self).setUp() + + partnerSample = PartnerSample(serverUrl) + self.partnerId = partnerSample.forcePost(partnerSample.data) + + countrySample = CountrySample(serverUrl) + countryId = countrySample.forcePost(countrySample.data) + + parentPartySample = self.consortiumPartySample + parentPartySample.setCountry(countryId) + self.consortiumPartyId = parentPartyId = parentPartySample.forcePost(parentPartySample.data) + + childPartySample = InstitutionPartySample(serverUrl) + childPartySample.setCountry(countryId) + self.institutionPartyId = childPartyId = childPartySample.forcePost(childPartySample.data) + + affliationSample = PartyAffiliationSample(serverUrl) + affliationSample.setParentId(parentPartyId) + affliationSample.setChildId(childPartyId) + affliationSample.forcePost(affliationSample.data) + + def test_for_get(self): + url = '%ssubscriptions/consactsubscriptions/%s/' % (serverUrl, self.institutionPartyId) + + # before consortium has subscription + res = self.client.get(url) + self.assertEqual(res.status_code, 200) + self.assertFalse(json.loads(res.content)) + + subscriptionSample = SubscriptionSample(serverUrl) + subscriptionSample.setPartnerId(self.partnerId) + subscriptionSample.setPartyId(self.consortiumPartyId) + subscriptionSample.forcePost(subscriptionSample.data) + + # after consortium has subcription + res = self.client.get(url) + self.assertEqual(res.status_code, 200) + self.assertEqual(checkMatch(self.consortiumPartySample.data, json.loads(res.content)[self.partnerId], 'partyId', self.consortiumPartyId), True) + +# test for API end point /subscriptions/commercials/ +# Sep/03/2019: this is a dreprecated API, now replaced by SalesForce Campaign + +# test for API end point /subscriptions/institutions/ +# Sep/03/2019: this is a dreprecated API, now replaced by SalesForce Campaign + +# test for API end point /subscriptions/request/ +# Sep/03/2019: this is a dreprecated API, now replaced by SalesForce Campaign + +# test for /subscriptions/templates/block or /subscriptions/templates/warn/ +# no test as no known external resource is using them + +# test for API end point /subscriptions/renew/ +# an end point for sending renewal request email, assume it's deprecated + +print("Running unit tests on subscription web services API.........") -# Create your tests here. +if __name__ == '__main__': + sys.argv[1:] = [] + unittest.main() + ret = not runner.run(suite).wasSuccessful() + sys.exit(ret) diff --git a/subscription/urls.py b/subscription/urls.py index 2aaad55f..da449a6d 100644 --- a/subscription/urls.py +++ b/subscription/urls.py @@ -32,7 +32,7 @@ url(r'^templates/block/$', TemplateView.as_view(template_name="subscription/block.html")), # PW-161 https://demoapi.arabidopsis.org/subscriptions/templates/login.html # url(r'^templates/login/$', TemplateView.as_view(template_name="subscription/login.html")), - + url(r'^templates/warn/$', TemplateView.as_view(template_name="subscription/warn.html")), ] urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/subscription/views.py b/subscription/views.py index de9e9873..6163dd14 100644 --- a/subscription/views.py +++ b/subscription/views.py @@ -59,7 +59,7 @@ def get(self, request): return Response(serializer.data) elif 'partyId' in params: partyId = params['partyId'] - now = datetime.datetime.now() + now = timezone.now() if 'checkConsortium' in params and params['checkConsortium'] == 'true': partnerIdList = Partner.objects.all().values_list('partnerId', flat=True) idSub = [] @@ -151,7 +151,7 @@ def post(self, request): else: # basic subscription creation if not isPhoenix(self.request): - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return Response(status=status.HTTP_400_BAD_REQUEST) serializer = self.serializer_class(data=request.data) if serializer.is_valid(): @@ -160,7 +160,7 @@ def post(self, request): returnData = serializer.data returnData['subscriptionTransactionId']=transaction.subscriptionTransactionId return Response(returnData, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) # /transactions/ class SubscriptionTransactionCRUD(GenericCRUDView): @@ -378,7 +378,7 @@ def get(self, request): class ActiveSubscriptions(generics.GenericAPIView): requireApiKey = False def get(self, request, partyId): - now = datetime.datetime.now() + now = timezone.now() activeSubscriptions = Subscription.objects.all().filter(partyId=partyId).filter(endDate__gt=now).filter(startDate__lt=now) serializer = SubscriptionSerializer(activeSubscriptions, many=True) #return HttpResponse(json.dumps(dict(serializer.data))) @@ -407,7 +407,7 @@ def get(self, request): if not 'partyId' in params: return Response({'error':'partyId is required'}, status=status.HTTP_400_BAD_REQUEST) ret = {} - now = datetime.datetime.now() + now = timezone.now() partyId = params['partyId'] if 'active' in params and params['active'] == 'true': if Party.objects.all().get(partyId=partyId): @@ -429,7 +429,7 @@ class ConsActSubscriptions(generics.GenericAPIView): requireApiKey = False def get(self, request, partyId): ret = {} - now = datetime.datetime.now() + now = timezone.now() if Party.objects.all().get(partyId=partyId): consortiums = Party.objects.all().get(partyId=partyId).consortiums.all() for consortium in consortiums: @@ -511,16 +511,16 @@ def get(self, request): # preprocessing requestDate for request in requestJSONList: request['requestDate'] = datetime.datetime.strptime(request['requestDate'], '%Y-%m-%dT%H:%M:%S.%fZ').strftime('%m/%d/%Y') - rows = [request.values() for request in requestJSONList] + rows = [list(request.values()) for request in requestJSONList] try: - header = requestJSONList[0].keys() + header = list(requestJSONList[0].keys()) except: return Response("requestJSONList[0] index out of range") rows.insert(0, header) pseudo_buffer = Echo() writer = csv.writer(pseudo_buffer) response = StreamingHttpResponse((writer.writerow(row) for row in rows),content_type="text/csv") - now = datetime.datetime.now() + now = timezone.now() response['Content-Disposition'] = 'attachment; filename="requests_report_{:%Y-%m-%d_%H:%M}.csv"'.format(now) response['X-Sendfile'] = smart_str('/Downloads') return response @@ -584,7 +584,7 @@ def post(self,request): activationCodes = [] - for i in xrange(quantity): + for i in range(quantity): # create an activation code based on partnerId and period. activationCodeObj = ActivationCode() activationCodeObj.activationCode=str(uuid.uuid4()) @@ -612,7 +612,7 @@ def put(self, request): activationCodeId = params['activationCodeId'] deleteMarker = True if params['deleteMarker'] == 'true' else False - activationCodeIdList = map(int, activationCodeId.split(',')) + activationCodeIdList = list(map(int, activationCodeId.split(','))) activationCodes = ActivationCode.objects.all().filter(activationCodeId__in=activationCodeIdList)