'DRF/Multi-tenant - How to specify tenant host (domain) in unit tests?

Environment - Django, Rest Framework, Multi-tenant.

In my unit tests, I'm trying to hit an endpoint in a tenant schema (not in Public). It is failing because the host is (for example) example.com instead of demo1.example.com.

I've Googled (of course) but can't find how to specify the domain name part of a request. Other posts I've found say it's best to use "reverse" to get the URL instead of hard-coding it.

Here's my test:

    def test_private_valid_authorization(self):
        self.demo1_tenant.activate()

        user = User.objects.get(username='[email protected]')
        token, created = Token.objects.get_or_create(user=user)
        self.client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)

        response = self.client.get(reverse('clients:client-list'))

        self.assertEqual(response.status_code, 200, "Expected status code: 200; got %s." % response.status_code)

Tenants (and their schemas) are created in the setUpTestData() method, so self.demo1_tenant.activate() simply connects to an already existing schema.

Users are imported via fixtures, hence the "get" instead of "create_user".

I get the following result:

AssertionError: 403 != 200 : Expected status code: 200; got 403.

Adding diagnostic displays to the code, I've narrowed it down to the process_request() method of django_tenants/middleware/main.py - specifically the hostname_from_request() method. If I put 'demo1.example.com' instead of using the return value from hostname_from_request (which is just 'example.com') my test succeeds. I hope someone can point me in the right direction here.



Solution 1:[1]

I found the answer...

To specify the base domain name, you do this:

self.client.defaults['SERVER_NAME'] = 'example.com'

So I can use example.com or demo1.example.com or demo2.example.com as needed.

Updated 16 Apr 2022 to expand on my answer.

In my setUp() method of the TenantTestCase class (which in my case is extended from APITestCase to incorporate rest_framework), I initialize a dict() of client objects, which I can then reference in each test depending on which schema I want to access at that time.

Here's my setUp() method:

class TenantTestCase(APITestCase):
    schemas = ['public', 'demo']
    clients = {}        # One client instance for each schema
    tenants = {}        # One tenant instance for each schema

    # user_tokens holds user tokens for each combination of 
    # schema and user in the form <schema>.<user> - where 
    # <user> is the first part of the username (without
    # "@example.com"). Example: demo.test.owner
    # Note: user_tokens are initialized in the setUpTestData() 
    # method.
    user_tokens = {}

    def setUp(self):
        for schema in self.schemas:
            self.clients[schema] = APIClient()
            subdomain = '' if schema == 'public' else schema + '.'
            self.clients[schema].defaults['SERVER_NAME'] = '%s%s' % (subdomain, settings.API_DOMAIN)

        super().setUp()

... other setup classes ...

Here's an example of how I use the above in a unittest:

class TenantPermissionTests(TenantTestCase):

    def test_domain_view_access(self):
        # Check unauthorized access.
        self.tenants['public'].activate()
        self.clients['public'].credentials(HTTP_AUTHORIZATION='Token invalid-token')
        response = self.clients['public'].get(reverse('tenants:domain-list'))
        self.assertEqual(response.status_code, 403, "Expected status code: 403; got %s." % response.status_code)

        # Check view_domain (view own) access - Domains list.
        self.tenants['demo'].activate()
        self.clients['demo'].credentials(HTTP_AUTHORIZATION='Token ' + self.user_tokens['demo.test.owner'])
        response = self.clients['demo'].get(reverse('tenants:domain-list'))
        self.assertEqual(response.status_code, 200, "Expected status code: 200; got %s." % response.status_code)
        self.assertEqual(len(response.data['results']), 1, "Expected 1 object; got %i." % len(response.data['results']))

I should explain the reason for the last line of the test case... We are not allowing tenants to create their own domains. If a tenant creates an account for "Acme", the system will create a single domain for them of acme.our-saas-site.com. So the test will only ever expect a single domain per tenant.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1