Source code for myapp.tests.test_views

import json
import math
from unittest import expectedFailure, skip

from django.contrib.auth.models import Permission, User
from rest_framework.reverse import reverse
from rest_framework.test import APITestCase

from myapp.models import Course, CourseTerm

HEADERS = {
    'HTTP_ACCEPT': 'application/vnd.api+json',
    'content_type': 'application/vnd.api+json'
}

COURSE_POST = {
    "data": {
        "type": "courses",
        # "id": "0c1761cf-ee02-4fdb-8cdd-dfa11ec8ac7a",
        "attributes": {
            "school_bulletin_prefix_code": "CEFKGRUXI",
            "suffix_two": "00",
            "subject_area_code": "IF",
            "course_number": "61044",
            "course_identifier": "AHIS2321W",
            "course_name": "ROME BEYOND ROME-DISC 4",
            "course_description": "blah blah",
            "effective_start_date": None,
            "effective_end_date": None,
            "last_mod_user_name": "alan",
            "last_mod_date": "2018-03-10"
        }
    }
}

COURSE_POST_WITH_REL = {
    "data": {
        "type": "courses",
        # "id": "0c1761cf-ee02-4fdb-8cdd-dfa11ec8ac7a",
        "attributes": {
            "school_bulletin_prefix_code": "CEFKGRUXI",
            "suffix_two": "00",
            "subject_area_code": "IF",
            "course_number": "61044",
            "course_identifier": "AHIS2321W",
            "course_name": "ROME BEYOND ROME-DISC 4",
            "course_description": "blah blah",
            "effective_start_date": None,
            "effective_end_date": None,
            "last_mod_user_name": "alan",
            "last_mod_date": "2018-03-10"
        },
        "relationships": {
            "course_terms": {
                "data": [{
                    "type": "course_terms",
                    "id": "e2060fb7-bc0e-4259-8077-2b678fff0c5f"
                }]
            }
        }
    }
}

COURSE_TERM_POST = {
    "data": {
        "type": "course_terms",
        # "id": "e724c43c-f443-4246-8947-a8bb8953699c",
        "attributes": {
            "term_identifier": "20181",
            "audit_permitted_code": 0,
            "exam_credit_flag": False,
            "effective_start_date": None,
            "effective_end_date": None,
            "last_mod_user_name": "alan",
            "last_mod_date": "2018-03-10"
        }
    }
}

COURSE_TERM_REL_PATCH = {
    "data": [{
        "type": "course_terms",
        "id": "e724c43c-f443-4246-8947-a8bb8953699c"
    }]
}


[docs]class DJATestCase(APITestCase): """ test cases using Django REST Framework drf-json-api (DJA) """ # TODO: add tests of query parameters: page, filter, fields, sort, include, and combinations thereof # TODO: add failure test cases (e.g. get of related where the id is invalid returns 500 instead of 404). fixtures = ('testcases',) def setUp(self): # define some users self.superuser = User.objects.create_user('tester', is_superuser=True) # `somebody` can view course but not anything else self.someuser = User.objects.create_user('somebody', is_superuser=False) self.someuser.user_permissions.add(Permission.objects.get(codename='view_course').id) # `nobody` has no permissions self.noneuser = User.objects.create_user('nobody', is_superuser=False) # most tests just use the superuser self.client.force_authenticate(user=self.superuser) self.courses = Course.objects.all() self.courses_url = reverse('course-list') self.course_terms = CourseTerm.objects.all() self.course_terms_url = reverse('courseterm-list') def test_post_course_term(self): response = self.client.post(self.courses_url, data=json.dumps(COURSE_POST), **HEADERS) self.assertEqual(response.status_code, 201, msg=response.content) course_id = json.loads(response.content)['data']['id'] # violate database constraint: get a course_identifier uniqueness error response = self.client.post( self.courses_url, data=json.dumps(COURSE_POST), **HEADERS) self.assertEqual(response.status_code, 400, msg=response.content) # expect this error: {"errors":[{"detail": "course with this course identifier already exists."}]} self.assertEqual('course with this course identifier already exists.', json.loads(response.content)['errors'][0]['detail']) response = self.client.post(self.course_terms_url, data=json.dumps(COURSE_TERM_POST), **HEADERS) self.assertEqual(response.status_code, 201, msg=response.content) term_id = json.loads(response.content)['data']['id'] # now patch in the relationship COURSE_TERM_REL_PATCH['data'][0]['id'] = term_id response = self.client.patch( self.courses_url + course_id + "/relationships/course_terms/", data=json.dumps(COURSE_TERM_REL_PATCH), **HEADERS) self.assertEqual(response.status_code, 200, msg=response.content)
[docs] def test_post_primary_rel(self): """ I should be able to POST the primary data and relationships together. """ response = self.client.post( self.course_terms_url, data=json.dumps(COURSE_TERM_POST), **HEADERS) self.assertEqual(response.status_code, 201, msg=response.content) term_id = json.loads(response.content)['data']['id'] COURSE_POST_WITH_REL['data']['relationships']['course_terms']['data'][ 0]['id'] = term_id # print("posting:") # pprint(COURSE_POST_WITH_REL) response = self.client.post( self.courses_url, data=json.dumps(COURSE_POST_WITH_REL), **HEADERS) self.assertEqual(response.status_code, 201, msg=response.content) # course_id = json.loads(response.content)['data']['id'] # print("course_id: {}".format(course_id)) # print("response:") # pprint(json.loads(response.content)) j = json.loads( response.content)['data']['relationships']['course_terms']['data'] self.assertEqual(len(j), 1, msg="missing relationships") self.assertEqual( j[0]['id'], term_id, msg="missing relationship data for {}".format(term_id))
[docs] @skip("test_patch_primary_rel not yet implemented") def test_patch_primary_rel(self): """ Make sure we can PATCH the attributes *and* relationships """ # TODO: I should be able to PATCH the primary data and updated relationships. pass
[docs] @skip("test_patch_rel not yet implemented") def test_patch_rel(self): """ See https://jsonapi.org/format/#crud-updating-resource-relationships """ # TODO: I should be able to PATCH the relationships. pass
[docs] def test_page_size(self): """ test rest_framework_json_api.pagination.JsonApiPageNumberPagination: page[size] and page[number] """ response = self.client.get(self.courses_url, data={"page[size]": 3, "page[number]": 2}, **HEADERS) j = json.loads(response.content) # pprint(j) self.assertEqual(len(j['data']), 3) # pprint(j['meta']['pagination']) self.assertEqual(j['meta']['pagination']['count'], len(self.courses)) self.assertEqual(j['meta']['pagination']['page'], 2) self.assertEqual(j['meta']['pagination']['pages'], math.ceil(len(self.courses) / 3)) self.assertEqual( j['links']['next'], 'http://testserver/v1/courses/?page%5Bnumber%5D=3&page%5Bsize%5D=3' )
[docs] def test_filter_fields(self): """ test field search (django_filters.rest_framework.DjangoFilterBackend): filter[<field>]=values """ response = self.client.get(self.courses_url, data={"filter[subject_area_code]": "ANTB"}, **HEADERS) j = json.loads(response.content) self.assertEqual( len(j['data']), len([ k for k in self.courses if k.subject_area_code == 'ANTB' ]))
[docs] def test_filter_fields_union_list(self): """ test field for a list of values (ORed): ?filter[field.in]=ANTB,BIOB,XXXX """ response = self.client.get(self.courses_url, data={"filter[subject_area_code.in]": "ANTB,BIOB,XXXX"}, **HEADERS) j = response.json() self.assertEqual( len(j['data']), len([ k for k in self.courses if k.subject_area_code == 'ANTB' ]) + len([ k for k in self.courses if k.subject_area_code == 'BIOB' ]) + len([ k for k in self.courses if k.subject_area_code == 'XXXX' ]), msg="filter field list (union)")
[docs] def test_filter_fields_intersection(self): """ test fields (ANDed): ?filter[subject_area_code]=ANTB&filter[course_number]=1234 """ response = self.client.get(self.courses_url, data={"filter[subject_area_code]": "ANTB", "filter[school_bulletin_prefix_code]": "XCEFK9"}, **HEADERS) j = json.loads(response.content) self.assertEqual( len(j['data']), len([ k for k in self.courses if k.subject_area_code == 'ANTB' and k.school_bulletin_prefix_code == 'XCEFK9' ]))
[docs] def test_sparse_fieldsets(self): """ test sparse fieldsets """ response = self.client.get("{}{}/".format(self.courses_url, self.courses[5].id), data={"fields[courses]": "course_name,course_description"}, **HEADERS) j = json.loads(response.content) self.assertEqual(len(j['data']['attributes']), 2) self.assertIn('course_name', j['data']['attributes']) self.assertIn('course_description', j['data']['attributes'])
[docs] def test_sort(self): """ test sort """ response = self.client.get(self.courses_url, data={"sort": "subject_area_code,-course_number"}, **HEADERS) j = json.loads(response.content) areas = [c['attributes']['subject_area_code'] for c in j['data']] sorted_areas = [ c['attributes']['subject_area_code'] for c in j['data'] ] sorted_areas.sort() self.assertEqual(areas, sorted_areas) prev_area = None prev_code = None for c in j['data']: area = c['attributes']['subject_area_code'] code = c['attributes']['course_number'] if area == prev_area: self.assertLess(code, prev_code) prev_code = code prev_area = area
[docs] def test_sort_badfield(self): """ test sort of nonexistent field """ response = self.client.get(self.courses_url, data={"sort": "nonesuch,-not_a_field,subject_area_code"}, **HEADERS) self.assertEqual(response.status_code, 400, msg=response.content)
[docs] def test_include(self): """ test include """ response = self.client.get(self.courses_url, data={"include": "course_terms"}, **HEADERS) j = response.json() self.assertIn('included', j) self.assertEqual(len(j['included']), sum([len(k.course_terms.all()) for k in self.courses])) kids = [str(k.id) for k in self.courses] for i in j['included']: self.assertIn(i['relationships']['course']['data']['id'], kids)
[docs] @expectedFailure def test_permission_course_course_terms(self): """ See if permissions are correctly implemented. """ # authenticate as user with no permissions: self.client.force_authenticate(user=self.noneuser) course_response = self.client.get("{}{}/".format(self.courses_url, self.courses[5].id), **HEADERS) self.assertEqual(course_response.status_code, 403, msg=course_response.content) self.assertIn("You do not have permission", course_response.json()['errors'][0]['detail']) # authenticate as user with model permission to view course but not course_term self.client.force_authenticate(user=self.someuser) # Look up a random course. In theory the course_terms should be suppressed. In practice, not so. course_response = self.client.get("{}{}/".format(self.courses_url, self.courses[5].id), data={"include": "course_terms"}, **HEADERS) self.assertEqual(course_response.status_code, 200, msg=course_response.content) course = course_response.json() # this should return zero (I think) but instead course_terms inherits permissions from course. self.assertEqual(len(course['data']['relationships']['course_terms']['data']), 0) # put back the default user self.client.force_authenticate(user=self.superuser)
[docs] def test_permission_course_terms(self): """ confirm that `somebody` lacks course_terms view permission """ self.client.force_authenticate(user=self.someuser) # Look up a random course. In theory the course_terms should be suppressed. In practice, not so. term_response = self.client.get("{}{}/".format(self.course_terms_url, self.course_terms[5].id), **HEADERS) self.assertEqual(term_response.status_code, 403, msg=term_response.content) term = term_response.json() self.assertIn("You do not have permission", term['errors'][0]['detail']) # put back the default user self.client.force_authenticate(user=self.superuser)