# -*- mode: python; coding: utf-8 -*-
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2018 Maël Kervella
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""Defines the test suite for the API
"""

import json
import datetime
from rest_framework.test import APITestCase
from requests import codes

import cotisations.models as cotisations
import machines.models as machines
import preferences.models as preferences
import topologie.models as topologie
import users.models as users


class APIEndpointsTestCase(APITestCase):    
    """Test case to test that all endpoints are reachable with respects to
    authentication and permission checks.

    Attributes:
        no_auth_endpoints: A list of endpoints that should be reachable
            without authentication.
        auth_no_perm_endpoints: A list of endpoints that should be reachable
            when being authenticated but without permissions.
        auth_perm_endpoints: A list of endpoints that should be reachable
            when being authenticated and having the correct permissions.
        stduser: A standard user with no permission used for the tests and
            initialized at the beggining of this test case.
        superuser: A superuser (with all permissions) used for the tests and
            initialized at the beggining of this test case.
    """
    no_auth_endpoints = [
        '/api/'
    ]
    auth_no_perm_endpoints = []
    auth_perm_endpoints = [
        '/api/cotisations/article/',
        '/api/cotisations/article/1/',
        '/api/cotisations/banque/',
        '/api/cotisations/banque/1/',
        '/api/cotisations/cotisation/',
        '/api/cotisations/cotisation/1/',
        '/api/cotisations/facture/',
        '/api/cotisations/facture/1/',
        '/api/cotisations/paiement/',
        '/api/cotisations/paiement/1/',
        '/api/cotisations/vente/',
        '/api/cotisations/vente/1/',
        '/api/machines/domain/',
        '/api/machines/domain/1/',
        '/api/machines/extension/',
        '/api/machines/extension/1/',
        '/api/machines/interface/',
        '/api/machines/interface/1/',
        '/api/machines/iplist/',
        '/api/machines/iplist/1/',
        '/api/machines/iptype/',
        '/api/machines/iptype/1/',
        '/api/machines/ipv6list/',
        '/api/machines/ipv6list/1/',
        '/api/machines/machine/',
        '/api/machines/machine/1/',
        '/api/machines/machinetype/',
        '/api/machines/machinetype/1/',
        '/api/machines/mx/',
        '/api/machines/mx/1/',
        '/api/machines/nas/',
        '/api/machines/nas/1/',
        '/api/machines/ns/',
        '/api/machines/ns/1/',
        '/api/machines/ouvertureportlist/',
        '/api/machines/ouvertureportlist/1/',
        '/api/machines/ouvertureport/',
        '/api/machines/ouvertureport/1/',
        '/api/machines/servicelink/',
        '/api/machines/servicelink/1/',
        '/api/machines/service/',
        '/api/machines/service/1/',
        '/api/machines/soa/',
        '/api/machines/soa/1/',
        '/api/machines/srv/',
        '/api/machines/srv/1/',
        '/api/machines/txt/',
        '/api/machines/txt/1/',
        '/api/machines/vlan/',
        '/api/machines/vlan/1/',
        '/api/preferences/optionaluser/',
        '/api/preferences/optionalmachine/',
        '/api/preferences/optionaltopologie/',
        '/api/preferences/generaloption/',
        '/api/preferences/service/',
        '/api/preferences/service/1/',
        '/api/preferences/assooption/',
        '/api/preferences/homeoption/',
        '/api/preferences/mailmessageoption/',
        '/api/topologie/acesspoint/',
        # 2nd machine to be create (machines_machine_1, topologie_accesspoint_1)
        '/api/topologie/acesspoint/2/',
        '/api/topologie/building/',
        '/api/topologie/building/1/',
        '/api/topologie/constructorswitch/',
        '/api/topologie/constructorswitch/1/',
        '/api/topologie/modelswitch/',
        '/api/topologie/modelswitch/1/',
        '/api/topologie/room/',
        '/api/topologie/room/1/',
        '/api/topologie/server/',
        # 3rd machine to be create (machines_machine_1, topologie_accesspoint_1,
        # topologie_server_1)
        '/api/topologie/server/3/',
        '/api/topologie/stack/',
        '/api/topologie/stack/1/',
        '/api/topologie/switch/',
        # 4th machine to be create (machines_machine_1, topologie_accesspoint_1,
        # topologie_server_1, topologie_switch_1)
        '/api/topologie/switch/4/',
        '/api/topologie/switchbay/',
        '/api/topologie/switchbay/1/',
        '/api/topologie/switchport/',
        '/api/topologie/switchport/1/',
        '/api/topologie/switchport/2/',
        '/api/topologie/switchport/3/',
        '/api/users/adherent/',
        # 3rd user to be create (stduser, superuser, users_adherent_1)
        '/api/users/adherent/3/',
        '/api/users/ban/',
        '/api/users/ban/1/',
        '/api/users/club/',
        # 4th user to be create (stduser, superuser, users_adherent_1,
        # users_club_1)
        '/api/users/club/4/', 
        '/api/users/listright/',
# TODO: Merge !145
#        '/api/users/listright/1/',
        '/api/users/school/',
        '/api/users/school/1/',
        '/api/users/serviceuser/',
        '/api/users/serviceuser/1/',
        '/api/users/shell/',
        '/api/users/shell/1/',
        '/api/users/user/',
        '/api/users/user/1/',
        '/api/users/whitelist/',
        '/api/users/whitelist/1/',
        '/api/dns/zones/',
        '/api/dhcp/hostmacip/',
        '/api/mailing/standard',
        '/api/mailing/club',
        '/api/services/regen/',
    ]
    not_found_endpoints = [
        '/api/cotisations/article/4242/',
        '/api/cotisations/banque/4242/',
        '/api/cotisations/cotisation/4242/',
        '/api/cotisations/facture/4242/',
        '/api/cotisations/paiement/4242/',
        '/api/cotisations/vente/4242/',
        '/api/machines/domain/4242/',
        '/api/machines/extension/4242/',
        '/api/machines/interface/4242/',
        '/api/machines/iplist/4242/',
        '/api/machines/iptype/4242/',
        '/api/machines/ipv6list/4242/',
        '/api/machines/machine/4242/',
        '/api/machines/machinetype/4242/',
        '/api/machines/mx/4242/',
        '/api/machines/nas/4242/',
        '/api/machines/ns/4242/',
        '/api/machines/ouvertureportlist/4242/',
        '/api/machines/ouvertureport/4242/',
        '/api/machines/servicelink/4242/',
        '/api/machines/service/4242/',
        '/api/machines/soa/4242/',
        '/api/machines/srv/4242/',
        '/api/machines/txt/4242/',
        '/api/machines/vlan/4242/',
        '/api/preferences/service/4242/',
        '/api/topologie/acesspoint/4242/',
        '/api/topologie/building/4242/',
        '/api/topologie/constructorswitch/4242/',
        '/api/topologie/modelswitch/4242/',
        '/api/topologie/room/4242/',
        '/api/topologie/server/4242/',
        '/api/topologie/stack/4242/',
        '/api/topologie/switch/4242/',
        '/api/topologie/switchbay/4242/',
        '/api/topologie/switchport/4242/',
        '/api/users/adherent/4242/',
        '/api/users/ban/4242/',
        '/api/users/club/4242/',
        '/api/users/listright/4242/',
        '/api/users/school/4242/',
        '/api/users/serviceuser/4242/',
        '/api/users/shell/4242/',
        '/api/users/user/4242/',
        '/api/users/whitelist/4242/',
    ]
    
    stduser = None
    superuser = None

    @classmethod
    def setUpTestData(cls):
        # Be aware that every object created here is never actually committed
        # to the database. TestCase uses rollbacks after each test to cancel all
        # modifications and recreates the data defined here before each test.
        # For more details, see
        # https://docs.djangoproject.com/en/1.10/topics/testing/tools/#testcase

        super(APIEndpointsTestCase, cls).setUpClass()

        # A user with no rights
        cls.stduser = users.User.objects.create_user(
            "apistduser",
            "apistduser",
            "apistduser@example.net",
            "apistduser"
        )
        # A user with all the rights
        cls.superuser = users.User.objects.create_superuser(
            "apisuperuser",
            "apisuperuser",
            "apisuperuser@example.net",
            "apisuperuser"
        )

        # Creates 1 instance for each object so the "details" endpoints
        # can be tested too. Objects need to be created in the right order.
        # Dependencies (relatedFields, ...) are highlighted by a comment at
        # the end of the concerned line (# Dep <model>).
        cls.users_school_1 = users.School.objects.create(
            name="users_school_1"
        )
        cls.users_school_1.save()
        cls.users_listshell_1 = users.ListShell.objects.create(
            shell="users_listshell_1"
        )
        cls.users_adherent_1 = users.Adherent.objects.create(
            password="password",
            last_login=datetime.datetime.now(datetime.timezone.utc),
            surname="users_adherent_1",
            pseudo="usersadherent1",
            email="users_adherent_1@example.net",
            school=cls.users_school_1,  # Dep users.School
            shell=cls.users_listshell_1,  # Dep users.ListShell
            comment="users Adherent 1 comment",
            pwd_ntlm="",
            state=users.User.STATES[0][0],
            registered=datetime.datetime.now(datetime.timezone.utc),
            telephone="0123456789",
            uid_number=21102,
            rezo_rez_uid=21102
        )
        cls.users_user_1 = cls.users_adherent_1
        cls.cotisations_article_1 = cotisations.Article.objects.create(
            name="cotisations_article_1",
            prix=10,
            duration=1,
            type_user=cotisations.Article.USER_TYPES[0][0],
            type_cotisation=cotisations.Article.COTISATION_TYPE[0][0]
        )
        cls.cotisations_banque_1 = cotisations.Banque.objects.create(
            name="cotisations_banque_1"
        )
        cls.cotisations_paiement_1 = cotisations.Paiement.objects.create(
            moyen="cotisations_paiement_1",
            type_paiement=cotisations.Paiement.PAYMENT_TYPES[0][0]
        )
        cls.cotisations_facture_1 = cotisations.Facture.objects.create(
            user=cls.users_user_1,  # Dep users.User
            paiement=cls.cotisations_paiement_1,  # Dep cotisations.Paiement
            banque=cls.cotisations_banque_1,  # Dep cotisations.Banque
            cheque="1234567890",
            date=datetime.datetime.now(datetime.timezone.utc),
            valid=True,
            control=False
        )
        cls.cotisations_vente_1 = cotisations.Vente.objects.create(
            facture=cls.cotisations_facture_1,  # Dep cotisations.Facture
            number=2,
            name="cotisations_vente_1",
            prix=10,
            duration=1,
            type_cotisation=cotisations.Vente.COTISATION_TYPE[0][0]
        )
        # A cotisation is automatically created by the Vente object and
        # trying to create another cotisation associated with this vente
        # will fail so we simply retrieve it so it can be used in the tests
        cls.cotisations_cotisation_1 = cotisations.Cotisation.objects.get(
            vente=cls.cotisations_vente_1,  # Dep cotisations.Vente
        )
        cls.machines_machine_1 = machines.Machine.objects.create(
            user=cls.users_user_1,  # Dep users.User
            name="machines_machine_1",
            active=True
        )
        cls.machines_ouvertureportlist_1 = machines.OuverturePortList.objects.create(
            name="machines_ouvertureportlist_1"
        )
        cls.machines_soa_1 = machines.SOA.objects.create(
            name="machines_soa_1",
            mail="postmaster@example.net",
            refresh=86400,
            retry=7200,
            expire=3600000,
            ttl=172800
        )
        cls.machines_extension_1 = machines.Extension.objects.create(
            name="machines_extension_1",
            need_infra=False,
            # Do not set origin because of circular dependency
            origin_v6="2001:db8:1234::",
            soa=cls.machines_soa_1  # Dep machines.SOA
        )
        cls.machines_vlan_1 = machines.Vlan.objects.create(
            vlan_id=0,
            name="machines_vlan_1",
            comment="machines Vlan 1"
        )
        cls.machines_iptype_1 = machines.IpType.objects.create(
            type="machines_iptype_1",
            extension=cls.machines_extension_1,  # Dep machines.Extension
            need_infra=False,
            domaine_ip_start="10.0.0.1",
            domaine_ip_stop="10.0.0.255",
            prefix_v6="2001:db8:1234::",
            vlan=cls.machines_vlan_1,  # Dep machines.Vlan
            ouverture_ports=cls.machines_ouvertureportlist_1  # Dep machines.OuverturePortList
        )
        # All IPs in the IpType range are autocreated so we can't create
        # new ones and thus we only retrieve it if needed in the tests
        cls.machines_iplist_1 = machines.IpList.objects.get(
            ipv4="10.0.0.1",
            ip_type=cls.machines_iptype_1,  # Dep machines.IpType
        )
        cls.machines_machinetype_1 = machines.MachineType.objects.create(
            type="machines_machinetype_1",
            ip_type=cls.machines_iptype_1,  # Dep machines.IpType
        )
        cls.machines_interface_1 = machines.Interface.objects.create(
            ipv4=cls.machines_iplist_1,  # Dep machines.IpList
            mac_address="00:00:00:00:00:00",
            machine=cls.machines_machine_1,  # Dep machines.Machine
            type=cls.machines_machinetype_1,  # Dep machines.MachineType
            details="machines Interface 1",
            #port_lists=[cls.machines_ouvertureportlist_1]  # Dep machines.OuverturePortList
        )
        cls.machines_domain_1 = machines.Domain.objects.create(
            interface_parent=cls.machines_interface_1,  # Dep machines.Interface
            name="machinesdomain",
            extension=cls.machines_extension_1  # Dep machines.Extension
            # Do no define cname for circular dependency
        )
        cls.machines_mx_1 = machines.Mx.objects.create(
            zone=cls.machines_extension_1,  # Dep machines.Extension
            priority=10,
            name=cls.machines_domain_1  # Dep machines.Domain
        )
        cls.machines_ns_1 = machines.Ns.objects.create(
            zone=cls.machines_extension_1,  # Dep machines.Extension
            ns=cls.machines_domain_1  # Dep machines.Domain
        )
        cls.machines_txt_1 = machines.Txt.objects.create(
            zone=cls.machines_extension_1,  # Dep machines.Extension
            field1="machines_txt_1",
            field2="machies Txt 1"
        )
        cls.machines_srv_1 = machines.Srv.objects.create(
            service="machines_srv_1",
            protocole=machines.Srv.TCP,
            extension=cls.machines_extension_1,  # Dep machines.Extension
            ttl=172800,
            priority=0,
            port=1,
            target=cls.machines_domain_1,  # Dep machines.Domain
        )
        cls.machines_ipv6list_1 = machines.Ipv6List.objects.create(
            ipv6="2001:db8:1234::",
            interface=cls.machines_interface_1,  # Dep machines.Interface
            slaac_ip=False
        )
        cls.machines_service_1 = machines.Service.objects.create(
            service_type="machines_service_1",
            min_time_regen=datetime.timedelta(minutes=1),
            regular_time_regen=datetime.timedelta(hours=1)
            # Do not define service_link because circular dependency
        )
        cls.machines_servicelink_1 = machines.Service_link.objects.create(
            service=cls.machines_service_1,  # Dep machines.Service
            server=cls.machines_interface_1,  # Dep machines.Interface
            last_regen=datetime.datetime.now(datetime.timezone.utc),
            asked_regen=False
        )
        cls.machines_ouvertureport_1 = machines.OuverturePort.objects.create(
            begin=1,
            end=2,
            port_list=cls.machines_ouvertureportlist_1,  # Dep machines.OuverturePortList
            protocole=machines.OuverturePort.TCP,
            io=machines.OuverturePort.OUT
        )
        cls.machines_nas_1 = machines.Nas.objects.create(
            name="machines_nas_1",
            nas_type=cls.machines_machinetype_1,  # Dep machines.MachineType
            machine_type=cls.machines_machinetype_1,  # Dep machines.MachineType
            port_access_mode=machines.Nas.AUTH[0][0],
            autocapture_mac=False
        )
        cls.preferences_service_1 = preferences.Service.objects.create(
            name="preferences_service_1",
            url="https://example.net",
            description="preferences Service 1",
            image="/media/logo/none.png"
        )
        cls.topologie_stack_1 = topologie.Stack.objects.create(
            name="topologie_stack_1",
            stack_id="1",
            details="topologie Stack 1",
            member_id_min=1,
            member_id_max=10
        )
        cls.topologie_accespoint_1 = topologie.AccessPoint.objects.create(
            user=cls.users_user_1,  # Dep users.User
            name="machines_machine_1",
            active=True,
            location="topologie AccessPoint 1"
        )
        cls.topologie_server_1 = topologie.Server.objects.create(
            user=cls.users_user_1,  # Dep users.User
            name="machines_machine_1",
            active=True
        )
        cls.topologie_building_1 = topologie.Building.objects.create(
            name="topologie_building_1"
        )
        cls.topologie_switchbay_1 = topologie.SwitchBay.objects.create(
            name="topologie_switchbay_1",
            building=cls.topologie_building_1,  # Dep topologie.Building
            info="topologie SwitchBay 1"
        )
        cls.topologie_constructorswitch_1 = topologie.ConstructorSwitch.objects.create(
            name="topologie_constructorswitch_1"
        )
        cls.topologie_modelswitch_1 = topologie.ModelSwitch.objects.create(
            reference="topologie_modelswitch_1",
            constructor=cls.topologie_constructorswitch_1  # Dep topologie.ConstructorSwitch
        )
        cls.topologie_switch_1 = topologie.Switch.objects.create(
            user=cls.users_user_1,  # Dep users.User
            name="machines_machine_1",
            active=True,
            number=10,
            stack=cls.topologie_stack_1,  # Dep topologie.Stack
            stack_member_id=1,
            model=cls.topologie_modelswitch_1,  # Dep topologie.ModelSwitch
            switchbay=cls.topologie_switchbay_1  # Dep topologie.SwitchBay
        )
        cls.topologie_room_1 = topologie.Room.objects.create(
            name="topologie_romm_1",
            details="topologie Room 1"
        )
        cls.topologie_port_1 = topologie.Port.objects.create(
            switch=cls.topologie_switch_1,  # Dep topologie.Switch
            port=1,
            room=cls.topologie_room_1,  # Dep topologie.Room
            radius=topologie.Port.STATES[0][0],
            vlan_force=cls.machines_vlan_1,  # Dep machines.Vlan
            details="topologie_switch_1"
        )
        cls.topologie_port_2 = topologie.Port.objects.create(
            switch=cls.topologie_switch_1,  # Dep topologie.Switch
            port=2,
            machine_interface=cls.machines_interface_1,  # Dep machines.Interface
            radius=topologie.Port.STATES[0][0],
            vlan_force=cls.machines_vlan_1,  # Dep machines.Vlan
            details="topologie_switch_1"
        )
        cls.topologie_port_3 = topologie.Port.objects.create(
            switch=cls.topologie_switch_1,  # Dep topologie.Switch
            port=3,
            room=cls.topologie_room_1,  # Dep topologie.Room
            radius=topologie.Port.STATES[0][0],
            # Do not defines related because circular dependency  # Dep machines.Vlan
            details="topologie_switch_1"
        )
        cls.users_ban_1 = users.Ban.objects.create(
            user=cls.users_user_1,  # Dep users.User
            raison="users Ban 1",
            date_start=datetime.datetime.now(datetime.timezone.utc),
            date_end=datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1),
            state=users.Ban.STATES[0][0]
        )
        cls.users_club_1 = users.Club.objects.create(
            password="password",
            last_login=datetime.datetime.now(datetime.timezone.utc),
            surname="users_club_1",
            pseudo="usersclub1",
            email="users_club_1@example.net",
            school=cls.users_school_1,  # Dep users.School
            shell=cls.users_listshell_1,  # Dep users.ListShell
            comment="users Club 1 comment",
            pwd_ntlm="",
            state=users.User.STATES[0][0],
            registered=datetime.datetime.now(datetime.timezone.utc),
            telephone="0123456789",
            uid_number=21103,
            rezo_rez_uid=21103
        )
# Need merge of MR145 to work
# TODO: Merge !145
#        cls.users_listright_1 = users.ListRight.objects.create(
#            unix_name="userslistright",
#            gid=601,
#            critical=False,
#            details="userslistright"
#        )
        cls.users_serviceuser_1 = users.ServiceUser.objects.create(
            password="password",
            last_login=datetime.datetime.now(datetime.timezone.utc),
            pseudo="usersserviceuser1",
            access_group=users.ServiceUser.ACCESS[0][0],
            comment="users ServiceUser 1"
        )
        cls.users_whitelist_1 = users.Whitelist.objects.create(
            user=cls.users_user_1,
            raison="users Whitelist 1",
            date_start=datetime.datetime.now(datetime.timezone.utc),
            date_end=datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1)
        )

    def check_responses_code(self, urls, expected_code, formats=None,
                             assert_more=None):
        """Utility function to test if a list of urls answer an expected code.

        Args:
            urls: The list of urls to test
            expected_code: The HTTP return code expected
            formats: The list of formats to use for the request. Default is to
                only test `None` format.
            assert_more: An optional function to assert more specific data in
                the same test. The response object, the url and the format
                used are passed as arguments.

        Raises:
            AssertionError: The response got did not have the expected status
                code.
            Any exception raised in the evalutation of `assert_more`.
        """
        if formats is None:
            formats = [None]
        for url in urls:
            for format in formats:
                with self.subTest(url=url, format=format):
                    response = self.client.get(url, format=format)
                    assert response.status_code == expected_code
                    if assert_more is not None:
                        assert_more(response, url, format)

    def test_no_auth_endpoints_with_no_auth(self):
        """Tests that every endpoint that does not require to be
        authenticated, returns a Ok (200) response when not authenticated.

        Raises:
            AssertionError: An endpoint did not have a 200 status code.
        """
        urls = self.no_auth_endpoints
        self.check_responses_code(urls, codes.ok)

    def test_auth_endpoints_with_no_auth(self):
        """Tests that every endpoint that does require to be authenticated,
        returns a Unauthorized (401) response when not authenticated.

        Raises:
            AssertionError: An endpoint did not have a 401 status code.
        """
        urls = self.auth_no_perm_endpoints + self.auth_perm_endpoints
        self.check_responses_code(urls, codes.unauthorized)

    def test_no_auth_endpoints_with_auth(self):
        """Tests that every endpoint that does not require to be
        authenticated, returns a Ok (200) response when authenticated.

        Raises:
            AssertionError: An endpoint did not have a 200 status code.
        """
        self.client.force_authenticate(user=self.stduser)
        urls = self.no_auth_endpoints
        self.check_responses_code(urls, codes.ok)

    def test_auth_no_perm_endpoints_with_auth_and_no_perm(self):
        """Tests that every endpoint that does require to be authenticated and
        no special permissions, returns a Ok (200) response when authenticated
        but without permissions.

        Raises:
            AssertionError: An endpoint did not have a 200 status code.
        """
        self.client.force_authenticate(user=self.stduser)
        urls = self.auth_no_perm_endpoints
        self.check_responses_code(urls, codes.ok)

    def test_auth_perm_endpoints_with_auth_and_no_perm(self):
        """Tests that every endpoint that does require to be authenticated and
        special permissions, returns a Forbidden (403) response when
        authenticated but without permissions.

        Raises:
            AssertionError: An endpoint did not have a 403 status code.
        """
        self.client.force_authenticate(user=self.stduser)
        urls = self.auth_perm_endpoints
        self.check_responses_code(urls, codes.forbidden)

    def test_auth_endpoints_with_auth_and_perm(self):
        """Tests that every endpoint that does require to be authenticated,
        returns a Ok (200) response when authenticated with all permissions.

        Raises:
            AssertionError: An endpoint did not have a 200 status code.
        """
        self.client.force_authenticate(user=self.superuser)
        urls = self.auth_no_perm_endpoints + self.auth_perm_endpoints
        self.check_responses_code(urls, codes.ok)

    def test_endpoints_not_found(self):
        """Tests that every endpoint that uses a primary key parameter,
        returns a Not Found (404) response when queried with non-existing
        primary key.

        Raises:
            AssertionError: An endpoint did not have a 404 status code.
        """
        self.client.force_authenticate(user=self.superuser)
        # Select only the URLs with '<pk>' and replace it with '42'
        urls = self.not_found_endpoints
        self.check_responses_code(urls, codes.not_found)

    def test_formats(self):
        """Tests that every endpoint returns a Ok (200) response when using
        different formats. Also checks that 'json' format returns a valid
        JSON object.

        Raises:
            AssertionError: An endpoint did not have a 200 status code.
        """
        self.client.force_authenticate(user=self.superuser)
        
        urls = self.no_auth_endpoints + self.auth_no_perm_endpoints + \
               self.auth_perm_endpoints

        def assert_more(response, url, format):
            """Assert the response is valid json when format is json"""
            if format is 'json':
                json.loads(response.content.decode())

        self.check_responses_code(urls, codes.ok,
                                  formats=[None, 'json', 'api'],
                                  assert_more=assert_more)

class APIPaginationTestCase(APITestCase):
    """Test case to check that the pagination is used on all endpoints that
    should use it.

    Attributes:
        endpoints: A list of endpoints that should use the pagination.
        superuser: A superuser used in the tests to access the endpoints.
    """

    endpoints = [
        '/api/cotisations/article/',
        '/api/cotisations/banque/',
        '/api/cotisations/cotisation/',
        '/api/cotisations/facture/',
        '/api/cotisations/paiement/',
        '/api/cotisations/vente/',
        '/api/machines/domain/',
        '/api/machines/extension/',
        '/api/machines/interface/',
        '/api/machines/iplist/',
        '/api/machines/iptype/',
        '/api/machines/ipv6list/',
        '/api/machines/machine/',
        '/api/machines/machinetype/',
        '/api/machines/mx/',
        '/api/machines/nas/',
        '/api/machines/ns/',
        '/api/machines/ouvertureportlist/',
        '/api/machines/ouvertureport/',
        '/api/machines/servicelink/',
        '/api/machines/service/',
        '/api/machines/soa/',
        '/api/machines/srv/',
        '/api/machines/txt/',
        '/api/machines/vlan/',
        '/api/preferences/service/',
        '/api/topologie/acesspoint/',
        '/api/topologie/building/',
        '/api/topologie/constructorswitch/',
        '/api/topologie/modelswitch/',
        '/api/topologie/room/',
        '/api/topologie/server/',
        '/api/topologie/stack/',
        '/api/topologie/switch/',
        '/api/topologie/switchbay/',
        '/api/topologie/switchport/',
        '/api/users/adherent/',
        '/api/users/ban/',
        '/api/users/club/',
        '/api/users/listright/',
        '/api/users/school/',
        '/api/users/serviceuser/',
        '/api/users/shell/',
        '/api/users/user/',
        '/api/users/whitelist/',
        '/api/dns/zones/',
        '/api/dhcp/hostmacip/',
        '/api/mailing/standard',
        '/api/mailing/club',
        '/api/services/regen/',
    ]
    superuser = None

    @classmethod
    def setUpTestData(cls):
        # A user with all the rights
        # We need to use a different username than for the first
        # test case because TestCase is using rollbacks which don't 
        # trigger the ldap_sync() thus the LDAP still have data about
        # the old users.
        cls.superuser = users.User.objects.create_superuser(
            "apisuperuser2",
            "apisuperuser2",
            "apisuperuser2@example.net",
            "apisuperuser2"
        )

    @classmethod
    def tearDownClass(cls):
        cls.superuser.delete()
        super().tearDownClass()

    def test_pagination(self):
        """Tests that every endpoint is using the pagination correctly.

        Raises:
            AssertionError: An endpoint did not have one the following keyword
                in the JSOn response: 'count', 'next', 'previous', 'results'
                or more that 100 results were returned.
        """
        self.client.force_authenticate(self.superuser)
        for url in self.endpoints:
            with self.subTest(url=url):
                response = self.client.get(url, format='json')
                res_json = json.loads(response.content.decode())
                assert 'count' in res_json.keys()
                assert 'next' in res_json.keys()
                assert 'previous' in res_json.keys()
                assert 'results' in res_json.keys()
                assert not len('results') > 100