Commit 4ca2eb11 authored by Daniel Schreiber's avatar Daniel Schreiber

Merge branch 'master' into rabbitmq

parents b905897c 8f3fc588
......@@ -10,18 +10,17 @@ variables:
stages:
- test
variables:
LD_LIBRARY_PATH: /opt/sqlite-compat-django/lib/
before_script:
- apt-get update -qqy
- apt-get install -qqy python-dev
- pip install tox
#api-test-py3:
# stage: test
# script: tox -e py36
# coverage: '/^TOTAL.*\s+(\d+\.\d\%)\s*$/'
# allow_failure: true
api-test-py2:
api-test-py3:
stage: test
script: tox -e py27
script: tox -e py36
coverage: '/^TOTAL.*\s+(\d+\.\d\%)\s*$/'
# allow_failure: true
......@@ -26,7 +26,7 @@ class Command(BaseCommand):
continue
try:
url = user.remote_reverse("health")
url = user.remote_reverse("api:health")
r = requests.get(url, headers=headers, timeout=5)
if r.status_code == 200:
......
......@@ -44,7 +44,7 @@ class Command(BaseCommand):
token = user.token()
headers = {'Authorization': 'token ' + str(token)}
try:
url = user.remote_reverse("health")
url = user.remote_reverse("api:health")
r = requests.get(url, headers=headers, timeout=5)
if r.status_code == 200:
logger.info("{} OK".format(user.username))
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from datetime import date, timedelta, datetime
from django.conf import settings
from django.core.management import BaseCommand
from django.db import transaction
from django.utils import timezone
from api.models import Resource, UserType
from api.utils.queue_utils import dispatch_deletion_sync
from api.utils.csv_handling import sync_from_csv_data
class Command(BaseCommand):
......@@ -22,37 +17,5 @@ class Command(BaseCommand):
def handle(self, *args, **options):
path = options["csv"]
date_format = options["date"]
eppn_dict = {}
with open(path, "r") as csv:
for line in csv.readlines():
line.strip()
eppn, d = line.strip().split(",")
exp_date = timezone.make_aware(datetime.strptime(d, date_format))
eppn_dict[eppn] = exp_date
# iterate over all local resources
for res in Resource.objects.filter(realm_idp=settings.SERVER_REALM)\
.select_related("sp", "sp__localresourcepolicy"):
try:
exp_date = eppn_dict[res.eppn]
except KeyError:
# eppn does no longer exist, we need to make sure that
# the resource will be locked immediately
# deletion and purging happens as usual
exp_date = min(res.expiry_date, timezone.now() - timedelta(days=1))
delete_date = exp_date + timedelta(days=res.sp.delete_after_days)
purge_date = delete_date + timedelta(days=res.sp.purge_api_data_after_days)
if purge_date < timezone.now():
uuid = res.uuid
is_local_sp = res.sp.is_local
res.delete()
# if this resource belongs to a remote sp, we need to notify
# the sp that we deleted the resource record
if not is_local_sp:
dispatch_deletion_sync(res, uuid)
else:
if res.expiry_date != exp_date or res.deletion_date != delete_date:
res.update_attribute([
{"name": "expiry_date", "value": exp_date.isoformat()},
{"name": "deletion_date", "value": delete_date.isoformat()},
], user_type=UserType.TYPE_IDP)
with open(path, "rb") as csv:
sync_from_csv_data(csv, date_format)
......@@ -100,17 +100,17 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='resource',
name='sp',
field=models.ForeignKey(verbose_name=b'SP', to='api.ServiceProvider'),
field=models.ForeignKey(verbose_name=b'SP', to='api.ServiceProvider', on_delete=models.PROTECT),
),
migrations.AddField(
model_name='attribute',
name='resource',
field=models.ForeignKey(related_name='attributes', to='api.Resource'),
field=models.ForeignKey(related_name='attributes', to='api.Resource', on_delete=models.CASCADE),
),
migrations.AddField(
model_name='tokenauthuser',
name='sp',
field=models.ForeignKey(blank=True, to='api.ServiceProvider', help_text=b'Must be set for type SP!', null=True, verbose_name=b'SP'),
field=models.ForeignKey(blank=True, to='api.ServiceProvider', help_text=b'Must be set for type SP!', null=True, verbose_name=b'SP', on_delete=models.PROTECT),
),
migrations.AddField(
model_name='tokenauthuser',
......
......@@ -47,6 +47,6 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='localresourcepolicy',
name='sp',
field=models.OneToOneField(verbose_name=b'SP', to='api.ServiceProvider'),
field=models.OneToOneField(verbose_name=b'SP', to='api.ServiceProvider', on_delete=models.CASCADE),
),
]
......@@ -29,7 +29,7 @@ from django.core.exceptions import ValidationError
from django.db import transaction, models
from django.contrib.auth.models import AbstractUser
from django.conf import settings
from django.core.urlresolvers import get_script_prefix
from django.urls import get_script_prefix
from django.db.models.signals import post_save
from django.dispatch import receiver
from django_choice_object import Choice
......@@ -138,7 +138,7 @@ class ServiceProvider(models.Model):
class LocalResourcePolicy(models.Model):
sp = models.OneToOneField(ServiceProvider, verbose_name="SP")
sp = models.OneToOneField(ServiceProvider, verbose_name="SP", on_delete=models.CASCADE)
delete_after_days = models.IntegerField(
default=90,
verbose_name="Default delete policy for resources",
......@@ -169,7 +169,7 @@ class Resource(models.Model):
updated = models.DateTimeField(auto_now=True)
expiry_date = models.DateTimeField(db_index=True)
deletion_date = models.DateTimeField(db_index=True)
sp = models.ForeignKey(ServiceProvider, verbose_name="SP")
sp = models.ForeignKey(ServiceProvider, verbose_name="SP", on_delete=models.PROTECT)
sp_primary_key = models.CharField(max_length=255,
blank=True,
verbose_name="Primary Key of resource in SP software")
......@@ -246,7 +246,9 @@ class Resource(models.Model):
if self.reject_request(is_sp_local, local_attrib, rmt_attrib, user_type):
attrib_dict.update({
"status": "rejected"
"status": "rejected",
"vec_sp": local_attrib.vec_sp,
"vec_idp": local_attrib.vec_idp,
})
res.append({attrib_name: attrib_dict})
else:
......@@ -275,10 +277,10 @@ class Resource(models.Model):
return res
def reject_request(self, is_sp_local, local_attrib, rmt_attrib, user_type):
local_vec = VersionVector(local_attrib.vec_sp, local_attrib.vec_idp)
remote_vec = VersionVector(rmt_attrib.get("vec_sp"), rmt_attrib.get("vec_idp"))
reject_request = False
if user_type == UserType.TYPE_SYNC:
local_vec = VersionVector(local_attrib.vec_sp, local_attrib.vec_idp)
remote_vec = VersionVector(rmt_attrib.get("vec_sp"), rmt_attrib.get("vec_idp"))
if local_vec.concurrent_to(remote_vec) or remote_vec.happened_before(local_vec):
# As this is represents a conflict situation - depending on prioritization -
# we might have to reject this request in order to keep ours dominant
......@@ -289,7 +291,29 @@ class Resource(models.Model):
if local_attrib.prio == PriorityRole.ROLE_IDP and not is_sp_local:
# We are IdP and this attribute prioritizes IdP
reject_request = True
# else: Local changes only require incrementing our local component
# jetzt müssen wir noch testen, dass die andere Seite nicht fälschlicherweise unseren Teil der Vektoruhr updatet
if is_sp_local:
if remote_vec.sp and remote_vec.sp != local_vec.sp:
reject_request = True
else:
if remote_vec.idp and remote_vec.idp != local_vec.idp:
reject_request = True
else: # Local changes only require incrementing our local component
if remote_vec.idp and remote_vec.sp:
if local_vec.concurrent_to(remote_vec) or remote_vec.happened_before(local_vec):
reject_request = True
if remote_vec.idp and remote_vec.idp < local_vec.idp:
reject_request = True
if remote_vec.sp and remote_vec.sp < local_vec.sp:
reject_request = True
if remote_vec.sp and user_type == UserType.TYPE_IDP and remote_vec.sp != local_vec.sp:
# IDP Darf SP Wert nicht verändern
reject_request = True
if remote_vec.idp and user_type == UserType.TYPE_SP and remote_vec.idp != local_vec.idp:
# SP Darf IDP Wert nicht verändern
reject_request = True
return reject_request
......@@ -332,7 +356,7 @@ class Attribute(models.Model):
name = models.CharField(max_length=100, db_index=True)
default_value = models.CharField(max_length=100, blank=True)
value = models.CharField(max_length=100)
resource = models.ForeignKey(Resource, related_name="attributes")
resource = models.ForeignKey(Resource, related_name="attributes", on_delete=models.CASCADE)
vec_sp = models.IntegerField(verbose_name="VV (SP)", default=1)
vec_idp = models.IntegerField(verbose_name="VV (IdP)", default=0)
prio = models.CharField(verbose_name="Priority role",
......@@ -358,7 +382,9 @@ class TokenAuthUser(AbstractUser):
verbose_name="SP",
blank=True,
null=True,
help_text="Must be set for type SP!")
help_text="Must be set for type SP!",
on_delete=models.PROTECT,
)
realm = models.CharField(verbose_name="Realm",
max_length=100,
blank=True,
......
......@@ -51,7 +51,7 @@ class AttributeUpdateTest(APITestCase):
)
def test_attribute_update(self):
url = reverse('resource_attributes', args=[self.res.uuid])
url = reverse('api:resource_attributes', args=[self.res.uuid])
user = TokenAuthUser.objects.get(username='a_super')
self.client.force_authenticate(user=user, token=user.token())
......@@ -93,7 +93,7 @@ class AttributeUpdateTest(APITestCase):
self.assertEqual(2, QueueJob.objects.count())
def test_attribute_update_as_sp(self):
url = reverse('resource_attributes', args=[self.res.uuid])
url = reverse('api:resource_attributes', args=[self.res.uuid])
user = TokenAuthUser.objects.get(username='testservice')
self.client.force_authenticate(user=user, token=user.token())
......@@ -134,7 +134,7 @@ class AttributeUpdateTest(APITestCase):
self.assertEqual(2, QueueJob.objects.count())
def test_attribute_update_rejected(self):
url = reverse('resource_attributes', args=[self.res.uuid])
url = reverse('api:resource_attributes', args=[self.res.uuid])
user = TokenAuthUser.objects.get(username='b_sync')
self.client.force_authenticate(user=user, token=user.token())
......
......@@ -25,7 +25,7 @@ class AuthTestCommandTest(TestCase):
def test_success(self):
out = StringIO()
with requests_mock.Mocker() as m:
m.register_uri("GET", self.user.remote_reverse("health"), status_code=200)
m.register_uri("GET", self.user.remote_reverse("api:health"), status_code=200)
call_command("auth_test", stdout=out)
self.assertTrue(m.called)
self.assertEqual(out.getvalue(), "1 passed, 0 failed.\n")
......@@ -33,7 +33,7 @@ class AuthTestCommandTest(TestCase):
def test_failed(self):
out = StringIO()
with requests_mock.Mocker() as m:
m.register_uri("GET", self.user.remote_reverse("health"), status_code=500)
m.register_uri("GET", self.user.remote_reverse("api:health"), status_code=500)
call_command("auth_test", stdout=out)
self.assertTrue(m.called)
self.assertEqual(out.getvalue(), "0 passed, 1 failed.\n\nFailed:\nb_sync\n")
......@@ -41,7 +41,7 @@ class AuthTestCommandTest(TestCase):
def test_connection_failed(self):
out = StringIO()
with requests_mock.Mocker() as m:
m.register_uri("GET", self.user.remote_reverse("health"), exc=requests.exceptions.ConnectTimeout)
m.register_uri("GET", self.user.remote_reverse("api:health"), exc=requests.exceptions.ConnectTimeout)
call_command("auth_test", stdout=out)
self.assertTrue(m.called)
self.assertEqual(out.getvalue(), "0 passed, 1 failed.\n\nFailed:\nb_sync\n")
......@@ -51,7 +51,7 @@ class AuthTestCommandTest(TestCase):
self.user.save()
out = StringIO()
with requests_mock.Mocker() as m:
m.register_uri("GET", self.user.remote_reverse("health"))
m.register_uri("GET", self.user.remote_reverse("api:health"))
call_command("auth_test", stdout=out)
self.assertFalse(m.called)
self.assertEqual(out.getvalue(), "0 passed, 1 failed.\n\nFailed:\nb_sync\n")
......
......@@ -32,7 +32,7 @@ class AttributeUpdateTest(APITestCase):
realm="b.edu")
def test_health(self):
url = reverse('health')
url = reverse('api:health')
user = TokenAuthUser.objects.get(username='a_super')
token = Token.objects.get(user_id=user)
......@@ -83,7 +83,7 @@ class TestHealthCommand(TestCase):
remote_user.save()
with requests_mock.Mocker() as m:
m.register_uri("GET", remote_user.remote_reverse("health"), status_code=500)
m.register_uri("GET", remote_user.remote_reverse("api:health"), status_code=500)
with self.assertRaises(SystemExit) as ex:
call_command("check_health")
self.assertEqual(ex.exception.code, 1)
......@@ -97,7 +97,7 @@ class TestHealthCommand(TestCase):
remote_user.save()
with requests_mock.Mocker() as m:
m.register_uri("GET", remote_user.remote_reverse("health"), exc=requests.exceptions.ConnectTimeout)
m.register_uri("GET", remote_user.remote_reverse("api:health"), exc=requests.exceptions.ConnectTimeout)
with self.assertRaises(SystemExit) as ex:
call_command("check_health")
self.assertEqual(ex.exception.code, 1)
......@@ -18,6 +18,6 @@ class TokenAuthUserTest(APITestCase):
realm="a.edu")
def test_remote_reverse(self):
self.assertEqual(
self.a_super.remote_reverse("health"),
self.a_super.remote_reverse("api:health"),
"http://api.a.edu:7000/foobar/health/"
)
......@@ -63,7 +63,7 @@ class SyncResourceTest(TestCase):
"""
transfer_data = '{"foo": "bar"}'
with requests_mock.Mocker() as m:
m.register_uri('POST', self.remote_user.remote_reverse("resource_list"), status_code=201)
m.register_uri('POST', self.remote_user.remote_reverse("api:resource_list"), status_code=201)
queue_utils.sync_resource(self.remote_user, transfer_data)
self.assertTrue(m.called)
# content matches
......@@ -77,7 +77,7 @@ class SyncResourceTest(TestCase):
"""
transfer_data = '{"foo": "bar"}'
with requests_mock.Mocker() as m:
m.register_uri('POST', self.remote_user.remote_reverse("resource_list"), status_code=500)
m.register_uri('POST', self.remote_user.remote_reverse("api:resource_list"), status_code=500)
with self.assertRaises(queue_utils.SyncException):
queue_utils.sync_resource(self.remote_user, transfer_data)
self.assertTrue(m.called)
......@@ -86,8 +86,8 @@ class SyncResourceTest(TestCase):
transfer_data = '{"foo": "bar", "uuid": "12345"}'
uuid = '12345'
with requests_mock.Mocker() as m:
m.register_uri('POST', self.remote_user.remote_reverse("resource_list"), status_code=400)
m.register_uri('GET', self.remote_user.remote_reverse("resource_detail", args=(uuid, )), status_code=200)
m.register_uri('POST', self.remote_user.remote_reverse("api:resource_list"), status_code=400)
m.register_uri('GET', self.remote_user.remote_reverse("api:resource_detail", args=(uuid, )), status_code=200)
queue_utils.sync_resource(self.remote_user, transfer_data)
self.assertTrue(m.called)
......@@ -95,8 +95,8 @@ class SyncResourceTest(TestCase):
transfer_data = '{"foo": "bar", "uuid": "12345"}'
uuid = '12345'
with requests_mock.Mocker() as m:
m.register_uri('POST', self.remote_user.remote_reverse("resource_list"), status_code=400)
m.register_uri('GET', self.remote_user.remote_reverse("resource_detail", args=(uuid, )), status_code=404)
m.register_uri('POST', self.remote_user.remote_reverse("api:resource_list"), status_code=400)
m.register_uri('GET', self.remote_user.remote_reverse("api:resource_detail", args=(uuid, )), status_code=404)
with self.assertRaises(queue_utils.SyncException):
queue_utils.sync_resource(self.remote_user, transfer_data)
self.assertTrue(m.called)
......@@ -131,7 +131,7 @@ class SyncAttributesTest(TestCase):
}
])
with requests_mock.Mocker() as m:
m.register_uri('PUT', self.remote_user.remote_reverse("resource_attributes", args=(self.r.pk, )), status_code=200, text=answer)
m.register_uri('PUT', self.remote_user.remote_reverse("api:resource_attributes", args=(self.r.pk, )), status_code=200, text=answer)
queue_utils.sync_attributes(self.remote_user, transfer_data, str(self.r.pk))
foo = Attribute.objects.get(pk=self.foo.pk)
self.assertEquals(foo.vec_idp, 2)
......@@ -145,7 +145,7 @@ class SyncAttributesTest(TestCase):
}
])
with requests_mock.Mocker() as m:
m.register_uri('PUT', self.remote_user.remote_reverse("resource_attributes", args=(self.r.pk, )), status_code=404, text=answer)
m.register_uri('PUT', self.remote_user.remote_reverse("api:resource_attributes", args=(self.r.pk, )), status_code=404, text=answer)
with self.assertRaises(queue_utils.SyncException):
queue_utils.sync_attributes(self.remote_user, transfer_data, str(self.r.pk))
......@@ -162,18 +162,18 @@ class DeleteResourceTest(TestCase):
def test_success(self):
uuid = "12345"
with requests_mock.Mocker() as m:
m.register_uri('DELETE', self.remote_user.remote_reverse("resource_detail", args=(uuid, )), status_code=200)
m.register_uri('DELETE', self.remote_user.remote_reverse("api:resource_detail", args=(uuid, )), status_code=200)
queue_utils.delete_resource(self.remote_user, uuid)
def test_not_found(self):
uuid = "12345"
with requests_mock.Mocker() as m:
m.register_uri('DELETE', self.remote_user.remote_reverse("resource_detail", args=(uuid, )), status_code=404)
m.register_uri('DELETE', self.remote_user.remote_reverse("api:resource_detail", args=(uuid, )), status_code=404)
queue_utils.delete_resource(self.remote_user, uuid)
def test_failure(self):
uuid = "12345"
with requests_mock.Mocker() as m:
m.register_uri('DELETE', self.remote_user.remote_reverse("resource_detail", args=(uuid, )), status_code=500)
m.register_uri('DELETE', self.remote_user.remote_reverse("api:resource_detail", args=(uuid, )), status_code=500)
with self.assertRaises(queue_utils.SyncException):
queue_utils.delete_resource(self.remote_user, uuid)
......@@ -60,7 +60,7 @@ class ResourceUpdatedTest(APITestCase):
self.resource_update_signals.append((sender, resource, user_type))
def test_attribute_update(self):
url = reverse('resource_attributes', args=[self.res.uuid])
url = reverse('api:resource_attributes', args=[self.res.uuid])
user = TokenAuthUser.objects.get(username='a_super')
self.client.force_authenticate(user=user, token=user.token())
......@@ -151,7 +151,7 @@ class ResourceCreatedTest(APITestCase):
self.created_signal_calls.append((sender, resource, user_type))
def test_create_resource(self):
url = reverse('resource_list')
url = reverse('api:resource_list')
user = TokenAuthUser.objects.get(username='a_super')
self.client.force_authenticate(user=user, token=user.token())
......
......@@ -43,7 +43,7 @@ class SyncNewResourceTest(APITestCase):
vec_idp=0)
def test_sync_new_resource(self):
url = reverse('resource_list')
url = reverse('api:resource_list')
user = TokenAuthUser.objects.get(username='b_sync')
self.client.force_authenticate(user=user, token=user.token())
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function
import json
from datetime import datetime
from datetime import timedelta
import uuid
from unittest import skip
from django.core.files.uploadedfile import SimpleUploadedFile
from django.conf import settings
from django.test import override_settings
from django.utils import timezone
from django.test.client import encode_multipart
from rest_framework.renderers import JSONRenderer
from rest_framework.reverse import reverse
from rest_framework.test import APITestCase
from api.models import QueueJob, Resource, ServiceProvider, TokenAuthUser, UserType
from api.models import Attribute, QueueJob, Resource, ServiceProvider, TokenAuthUser, UserType
from api.serializers import ResourceSerializer
......@@ -79,7 +83,7 @@ class ResourceDeleteTest(APITestCase):
def _test_delete(self, res, user, queue_delta):
qlen = QueueJob.objects.filter(res_uuid=res.uuid).count()
self.client.force_authenticate(user=user, token=user.token())
self.client.delete(reverse('resource_detail', kwargs={'uuid': res.uuid}))
self.client.delete(reverse('api:resource_detail', kwargs={'uuid': res.uuid}))
# resource has been deleted
self.assertEqual(Resource.objects.filter(uuid=res.uuid).count(), 0)
# no queueJob has been created
......@@ -147,7 +151,7 @@ class ResourceCreateTest(APITestCase):
sp=self.local_sp,
sp_primary_key="usera@a.edu",
)
self.url = reverse('resource_list')
self.url = reverse('api:resource_list')
self.res = Resource()
self.queue_data = JSONRenderer().render(ResourceSerializer(self.localResource).data)
......@@ -200,14 +204,14 @@ class ResourceGetTest(APITestCase):
def test_unauthenticated(self):
invalid_uuid = str(uuid.uuid4())
url = reverse("resource_detail", args=(invalid_uuid,))
url = reverse("api:resource_detail", args=(invalid_uuid,))
r = self.client.get(url)
self.assertEqual(r.status_code, 401)
def test_not_exist(self):
invalid_uuid = str(uuid.uuid4())
self.client.force_authenticate(self.sp_user)
url = reverse("resource_detail", args=(invalid_uuid,))
url = reverse("api:resource_detail", args=(invalid_uuid,))
r = self.client.get(url)
self.assertEqual(r.status_code, 404)
......@@ -216,11 +220,236 @@ class ResourceGetTest(APITestCase):
r.full_clean()
r.save()
self.client.force_authenticate(self.sp_user)
url = reverse("resource_detail", args=(r.pk,))
url = reverse("api:resource_detail", args=(r.pk,))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
class ResourceUpdateTest(APITestCase):
def setUp(self):
self.sp = ServiceProvider.objects.create(
name="TestService",
expiry_date=timezone.now(),
realm="a.edu"
)
self.sp_user = TokenAuthUser.objects.create_superuser(
username='sp',
email='foo@bar.com',
password='sp',
user_type=UserType.TYPE_SP,
api_url="http://api.a.edu:7000/",
realm="a.edu",
sp=self.sp,
)
self.idp_user = TokenAuthUser.objects.create_superuser(
username='idp',
email='foo@bar.com',
password='idp',
user_type=UserType.TYPE_IDP,
api_url="http://api.b.edu:7000/",
realm="b.edu",
)
self.sync_user = TokenAuthUser.objects.create_superuser(
username='sync',
email='foo@bar.com',
password='idp',
user_type=UserType.TYPE_SYNC,
api_url="http://api.b.edu:7000/",
realm="b.edu",
)
self.r = Resource(sp=self.sp, expiry_date=timezone.now(), deletion_date=timezone.now(), setup_token="1234", eppn="foo@b.edu")
self.r.full_clean()
self.r.save()
self.attr1 = Attribute(name='test', value='foo', vec_sp=3, vec_idp=1, resource=self.r)
self.attr1.full_clean()
self.attr1.save()
self.attr2 = Attribute(name='other', value='bar', vec_sp=5, vec_idp=2, resource=self.r)
self.attr2.full_clean()
self.attr2.save()
def test_get(self):
self.client.force_authenticate(self.sp_user)
r = self.client.get(reverse('api:resource_list'))
self.maxDiff=None
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json(),[{
'uuid': str(self.r.uuid),
'added': self.r.added.astimezone(timezone.get_default_timezone()).isoformat(),
'updated': self.r.updated.astimezone(timezone.get_default_timezone()).isoformat(),
'deletion_date': self.r.deletion_date.astimezone(timezone.get_default_timezone()).isoformat(),
'expiry_date': self.r.expiry_date.astimezone(timezone.get_default_timezone()).isoformat(),
'eppn': 'foo@b.edu',
'realm_idp': 'b.edu',
'setup_token': '1234',
'sp': str(self.sp.uuid),
'sp_primary_key': '',
'attributes': [
{
'name': 'test',
'value': 'foo',
'default_value': '',
'vec_sp': 3,
'vec_idp': 1
},
{
'name': 'other',
'value': 'bar',
'default_value': '',
'vec_sp': 5,
'vec_idp': 2
},
]
}])
def _run_test_update(self, vec_sp, vec_idp):
self._run_test_update_with_result(vec_sp, vec_idp, 200, vec_sp, vec_idp, 'updated')
def _run_test_update_with_result(self, vec_sp, vec_idp, expected_status_code, expected_vec_sp, expected_vec_idp, expected_status):
data = [
{
'name': 'other',
'value': 'bar2',
'default_value': '',
'vec_sp': vec_sp,
'vec_idp': vec_idp
},
]
url = reverse("api:resource_attributes", args=(self.r.pk,))
res = self.client.put(url, data, format='json')
self.assertEqual(res.status_code, expected_status_code)
result_data = res.json()
result_dict = result_data[0]['other']
self.assertEqual(result_dict, {
'status': expected_status,
'vec_sp': expected_vec_sp,
'vec_idp': expected_vec_idp,
})
attr = Attribute.objects.get(pk=self.attr2.pk)
self.assertEqual({