authentik.flows.tests.test_stage_views

stage view tests

  1"""stage view tests"""
  2
  3from collections.abc import Callable
  4from unittest.mock import patch
  5
  6from django.test import RequestFactory, TestCase
  7from django.urls import reverse
  8
  9from authentik.core.tests.utils import RequestFactory as AuthentikRequestFactory
 10from authentik.core.tests.utils import create_test_flow
 11from authentik.flows.models import Flow, FlowStageBinding
 12from authentik.flows.stage import StageView
 13from authentik.flows.views.executor import FlowExecutorView
 14from authentik.lib.utils.reflection import all_subclasses
 15from authentik.stages.dummy.models import DummyStage
 16from authentik.stages.dummy.stage import DummyStageView
 17
 18
 19class TestViews(TestCase):
 20    """Generic model properties tests"""
 21
 22    def setUp(self) -> None:
 23        self.factory = RequestFactory()
 24        self.exec = FlowExecutorView(request=self.factory.get("/"))
 25        self.authentik_factory = AuthentikRequestFactory()
 26
 27    def test_challenge_stage_flow_info_uses_relative_background(self):
 28        """Test challenge flow info keeps background URLs app-relative."""
 29        flow = create_test_flow()
 30        stage = DummyStage.objects.create(name="dummy")
 31        FlowStageBinding.objects.create(target=flow, stage=stage, order=0)
 32        request = self.authentik_factory.get("/")
 33
 34        executor = FlowExecutorView(flow=flow, request=request)
 35        executor.current_stage = stage
 36
 37        view = DummyStageView(executor)
 38        view.request = request
 39
 40        challenge = view._get_challenge()
 41
 42        self.assertEqual(
 43            challenge.initial_data["flow_info"]["background"],
 44            "/static/dist/assets/images/flow_background.jpg",
 45        )
 46
 47    def test_flow_interface_css_background_preserves_presigned_url_query(self):
 48        """Test flow CSS keeps signed URL query separators intact."""
 49        flow = create_test_flow()
 50        background_url = (
 51            "https://s3.ca-central-1.amazonaws.com/example/media/public/background.png"
 52            "?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=credential"
 53            "&X-Amz-Signature=signature"
 54        )
 55
 56        with patch.object(Flow, "background_url", return_value=background_url):
 57            response = self.client.get(
 58                reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
 59            )
 60
 61        self.assertContains(
 62            response,
 63            f'--ak-global--background-image: url("{background_url}");',
 64            html=False,
 65        )
 66
 67    def test_flow_sfe_css_background_preserves_presigned_url_query(self):
 68        """Test SFE flow CSS keeps signed URL query separators intact."""
 69        flow = create_test_flow()
 70        background_url = (
 71            "https://s3.ca-central-1.amazonaws.com/example/media/public/background.png"
 72            "?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=credential"
 73            "&X-Amz-Signature=signature"
 74        )
 75
 76        with patch.object(Flow, "background_url", return_value=background_url):
 77            response = self.client.get(
 78                reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}) + "?sfe"
 79            )
 80
 81        self.assertContains(
 82            response,
 83            f'background-image: url("{background_url}");',
 84            html=False,
 85        )
 86
 87
 88def view_tester_factory(view_class: type[StageView]) -> Callable:
 89    """Test a form"""
 90
 91    def tester(self: TestViews):
 92        model_class = view_class(self.exec)
 93        if not hasattr(model_class, "dispatch"):
 94            self.assertIsNotNone(model_class.post)
 95            self.assertIsNotNone(model_class.get)
 96
 97    return tester
 98
 99
100for view in all_subclasses(StageView):
101    setattr(TestViews, f"test_view_{view.__name__}", view_tester_factory(view))
class TestViews(django.test.testcases.TestCase):
20class TestViews(TestCase):
21    """Generic model properties tests"""
22
23    def setUp(self) -> None:
24        self.factory = RequestFactory()
25        self.exec = FlowExecutorView(request=self.factory.get("/"))
26        self.authentik_factory = AuthentikRequestFactory()
27
28    def test_challenge_stage_flow_info_uses_relative_background(self):
29        """Test challenge flow info keeps background URLs app-relative."""
30        flow = create_test_flow()
31        stage = DummyStage.objects.create(name="dummy")
32        FlowStageBinding.objects.create(target=flow, stage=stage, order=0)
33        request = self.authentik_factory.get("/")
34
35        executor = FlowExecutorView(flow=flow, request=request)
36        executor.current_stage = stage
37
38        view = DummyStageView(executor)
39        view.request = request
40
41        challenge = view._get_challenge()
42
43        self.assertEqual(
44            challenge.initial_data["flow_info"]["background"],
45            "/static/dist/assets/images/flow_background.jpg",
46        )
47
48    def test_flow_interface_css_background_preserves_presigned_url_query(self):
49        """Test flow CSS keeps signed URL query separators intact."""
50        flow = create_test_flow()
51        background_url = (
52            "https://s3.ca-central-1.amazonaws.com/example/media/public/background.png"
53            "?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=credential"
54            "&X-Amz-Signature=signature"
55        )
56
57        with patch.object(Flow, "background_url", return_value=background_url):
58            response = self.client.get(
59                reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
60            )
61
62        self.assertContains(
63            response,
64            f'--ak-global--background-image: url("{background_url}");',
65            html=False,
66        )
67
68    def test_flow_sfe_css_background_preserves_presigned_url_query(self):
69        """Test SFE flow CSS keeps signed URL query separators intact."""
70        flow = create_test_flow()
71        background_url = (
72            "https://s3.ca-central-1.amazonaws.com/example/media/public/background.png"
73            "?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=credential"
74            "&X-Amz-Signature=signature"
75        )
76
77        with patch.object(Flow, "background_url", return_value=background_url):
78            response = self.client.get(
79                reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}) + "?sfe"
80            )
81
82        self.assertContains(
83            response,
84            f'background-image: url("{background_url}");',
85            html=False,
86        )

Generic model properties tests

def setUp(self) -> None:
23    def setUp(self) -> None:
24        self.factory = RequestFactory()
25        self.exec = FlowExecutorView(request=self.factory.get("/"))
26        self.authentik_factory = AuthentikRequestFactory()

Hook method for setting up the test fixture before exercising it.

def test_challenge_stage_flow_info_uses_relative_background(self):
28    def test_challenge_stage_flow_info_uses_relative_background(self):
29        """Test challenge flow info keeps background URLs app-relative."""
30        flow = create_test_flow()
31        stage = DummyStage.objects.create(name="dummy")
32        FlowStageBinding.objects.create(target=flow, stage=stage, order=0)
33        request = self.authentik_factory.get("/")
34
35        executor = FlowExecutorView(flow=flow, request=request)
36        executor.current_stage = stage
37
38        view = DummyStageView(executor)
39        view.request = request
40
41        challenge = view._get_challenge()
42
43        self.assertEqual(
44            challenge.initial_data["flow_info"]["background"],
45            "/static/dist/assets/images/flow_background.jpg",
46        )

Test challenge flow info keeps background URLs app-relative.

def test_flow_interface_css_background_preserves_presigned_url_query(self):
48    def test_flow_interface_css_background_preserves_presigned_url_query(self):
49        """Test flow CSS keeps signed URL query separators intact."""
50        flow = create_test_flow()
51        background_url = (
52            "https://s3.ca-central-1.amazonaws.com/example/media/public/background.png"
53            "?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=credential"
54            "&X-Amz-Signature=signature"
55        )
56
57        with patch.object(Flow, "background_url", return_value=background_url):
58            response = self.client.get(
59                reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug})
60            )
61
62        self.assertContains(
63            response,
64            f'--ak-global--background-image: url("{background_url}");',
65            html=False,
66        )

Test flow CSS keeps signed URL query separators intact.

def test_flow_sfe_css_background_preserves_presigned_url_query(self):
68    def test_flow_sfe_css_background_preserves_presigned_url_query(self):
69        """Test SFE flow CSS keeps signed URL query separators intact."""
70        flow = create_test_flow()
71        background_url = (
72            "https://s3.ca-central-1.amazonaws.com/example/media/public/background.png"
73            "?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=credential"
74            "&X-Amz-Signature=signature"
75        )
76
77        with patch.object(Flow, "background_url", return_value=background_url):
78            response = self.client.get(
79                reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}) + "?sfe"
80            )
81
82        self.assertContains(
83            response,
84            f'background-image: url("{background_url}");',
85            html=False,
86        )

Test SFE flow CSS keeps signed URL query separators intact.

def test_view_AccessDeniedStage(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_AccountLockdownStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_AgentAuthFulfillmentStage(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_AuthenticatorDuoStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_AuthenticatorEmailStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_AuthenticatorEndpointStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_AuthenticatorSMSStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_AuthenticatorStaticStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_AuthenticatorTOTPStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_AuthenticatorValidateStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_AuthenticatorWebAuthnStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_AutosubmitStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_CaptchaStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_ChallengeStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_ConsentStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_DenyStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_DummyStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_EmailStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_EmailTokenRevocationConsentStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_EndpointStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_FleetStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_GoogleChromeStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_GroupUpdateStage(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_IdentificationStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_IframeLogoutStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_InvitationStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_MTLSStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_MessageStage(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_NativeLogoutStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_NativeLogoutStageViewBase(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_OAuthDeviceCodeFinishStage(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_OAuthDeviceCodeStage(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_OAuthFulfillmentStage(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_PasswordStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_PostSetupStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_PostSourceStage(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_PromptStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_RACFinalStage(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_RedirectStage(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_RedirectStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_RedirectToAppStage(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_SAMLFlowFinalView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_SessionEndStage(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_SourceStageFinal(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_SourceStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_TelegramLoginView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_UserDeleteStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_UserLoginStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_UserLogoutStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_UserWriteStageView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def test_view_WSFedFlowFinalView(self: TestViews):
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)

The type of the None singleton.

def view_tester_factory(view_class: type[authentik.flows.stage.StageView]) -> Callable:
89def view_tester_factory(view_class: type[StageView]) -> Callable:
90    """Test a form"""
91
92    def tester(self: TestViews):
93        model_class = view_class(self.exec)
94        if not hasattr(model_class, "dispatch"):
95            self.assertIsNotNone(model_class.post)
96            self.assertIsNotNone(model_class.get)
97
98    return tester

Test a form