authentik.lib.tests.test_utils_inheritance

Tests for inheritance helpers.

  1"""Tests for inheritance helpers."""
  2
  3from contextlib import contextmanager
  4
  5from django.db import connection, models
  6from django.test import TransactionTestCase
  7from django.test.utils import isolate_apps
  8
  9from authentik.lib.utils.inheritance import get_deepest_child
 10
 11
 12@contextmanager
 13def temporary_inheritance_models():
 14    """Create a temporary multi-table inheritance graph for testing."""
 15    with isolate_apps("authentik.lib.tests"):
 16
 17        class GrandParent(models.Model):
 18            class Meta:
 19                app_label = "tests"
 20
 21            def __str__(self) -> str:
 22                return f"GrandParent({self.pk})"
 23
 24        class Parent(GrandParent):
 25            class Meta:
 26                app_label = "tests"
 27
 28            def __str__(self) -> str:
 29                return f"Parent({self.pk})"
 30
 31        class Child(Parent):
 32            class Meta:
 33                app_label = "tests"
 34
 35            def __str__(self) -> str:
 36                return f"Child({self.pk})"
 37
 38        class GrandChild(Child):
 39            class Meta:
 40                app_label = "tests"
 41
 42            def __str__(self) -> str:
 43                return f"GrandChild({self.pk})"
 44
 45        with connection.schema_editor() as schema_editor:
 46            schema_editor.create_model(GrandParent)
 47            schema_editor.create_model(Parent)
 48            schema_editor.create_model(Child)
 49            schema_editor.create_model(GrandChild)
 50
 51        try:
 52            yield GrandParent, Parent, Child, GrandChild
 53        finally:
 54            with connection.schema_editor() as schema_editor:
 55                schema_editor.delete_model(GrandChild)
 56                schema_editor.delete_model(Child)
 57                schema_editor.delete_model(Parent)
 58                schema_editor.delete_model(GrandParent)
 59
 60
 61class TestInheritanceUtils(TransactionTestCase):
 62    """Tests for helper functions in authentik.lib.utils.inheritance."""
 63
 64    def test_get_deepest_child_grandparent_to_parent(self):
 65        """GrandParent -> Parent."""
 66        with temporary_inheritance_models() as (GrandParent, Parent, _Child, _GrandChild):
 67            parent = Parent.objects.create()
 68            grandparent = GrandParent.objects.get(pk=parent.pk)
 69
 70            resolved = get_deepest_child(grandparent)
 71
 72            self.assertIsInstance(resolved, Parent)
 73            self.assertEqual(resolved.pk, parent.pk)
 74
 75    def test_get_deepest_child_grandparent_to_child(self):
 76        """GrandParent -> Child."""
 77        with temporary_inheritance_models() as (GrandParent, _Parent, Child, _GrandChild):
 78            child = Child.objects.create()
 79            grandparent = GrandParent.objects.get(pk=child.pk)
 80
 81            resolved = get_deepest_child(grandparent)
 82
 83            self.assertIsInstance(resolved, Child)
 84            self.assertEqual(resolved.pk, child.pk)
 85
 86    def test_get_deepest_child_grandparent_to_grandchild(self):
 87        """GrandParent -> GrandChild."""
 88        with temporary_inheritance_models() as (GrandParent, _Parent, _Child, GrandChild):
 89            grandchild = GrandChild.objects.create()
 90            grandparent = GrandParent.objects.get(pk=grandchild.pk)
 91
 92            resolved = get_deepest_child(grandparent)
 93
 94            self.assertIsInstance(resolved, GrandChild)
 95            self.assertEqual(resolved.pk, grandchild.pk)
 96
 97    def test_get_deepest_child_parent_to_child(self):
 98        """Parent -> Child (start from non-root)."""
 99        with temporary_inheritance_models() as (_GrandParent, Parent, Child, _GrandChild):
100            child = Child.objects.create()
101            parent = Parent.objects.get(pk=child.pk)
102
103            resolved = get_deepest_child(parent)
104
105            self.assertIsInstance(resolved, Child)
106            self.assertEqual(resolved.pk, child.pk)
107
108    def test_get_deepest_child_no_queries_with_preloaded_relations(self):
109        """No extra queries when the inheritance chain is fully select_related."""
110        with temporary_inheritance_models() as (GrandParent, _Parent, _Child, GrandChild):
111            grandchild = GrandChild.objects.create()
112            grandparent = GrandParent.objects.select_related("parent__child__grandchild").get(
113                pk=grandchild.pk
114            )
115
116            with self.assertNumQueries(0):
117                resolved = get_deepest_child(grandparent)
118
119            self.assertIsInstance(resolved, GrandChild)
@contextmanager
def temporary_inheritance_models():
13@contextmanager
14def temporary_inheritance_models():
15    """Create a temporary multi-table inheritance graph for testing."""
16    with isolate_apps("authentik.lib.tests"):
17
18        class GrandParent(models.Model):
19            class Meta:
20                app_label = "tests"
21
22            def __str__(self) -> str:
23                return f"GrandParent({self.pk})"
24
25        class Parent(GrandParent):
26            class Meta:
27                app_label = "tests"
28
29            def __str__(self) -> str:
30                return f"Parent({self.pk})"
31
32        class Child(Parent):
33            class Meta:
34                app_label = "tests"
35
36            def __str__(self) -> str:
37                return f"Child({self.pk})"
38
39        class GrandChild(Child):
40            class Meta:
41                app_label = "tests"
42
43            def __str__(self) -> str:
44                return f"GrandChild({self.pk})"
45
46        with connection.schema_editor() as schema_editor:
47            schema_editor.create_model(GrandParent)
48            schema_editor.create_model(Parent)
49            schema_editor.create_model(Child)
50            schema_editor.create_model(GrandChild)
51
52        try:
53            yield GrandParent, Parent, Child, GrandChild
54        finally:
55            with connection.schema_editor() as schema_editor:
56                schema_editor.delete_model(GrandChild)
57                schema_editor.delete_model(Child)
58                schema_editor.delete_model(Parent)
59                schema_editor.delete_model(GrandParent)

Create a temporary multi-table inheritance graph for testing.

class TestInheritanceUtils(django.test.testcases.TransactionTestCase):
 62class TestInheritanceUtils(TransactionTestCase):
 63    """Tests for helper functions in authentik.lib.utils.inheritance."""
 64
 65    def test_get_deepest_child_grandparent_to_parent(self):
 66        """GrandParent -> Parent."""
 67        with temporary_inheritance_models() as (GrandParent, Parent, _Child, _GrandChild):
 68            parent = Parent.objects.create()
 69            grandparent = GrandParent.objects.get(pk=parent.pk)
 70
 71            resolved = get_deepest_child(grandparent)
 72
 73            self.assertIsInstance(resolved, Parent)
 74            self.assertEqual(resolved.pk, parent.pk)
 75
 76    def test_get_deepest_child_grandparent_to_child(self):
 77        """GrandParent -> Child."""
 78        with temporary_inheritance_models() as (GrandParent, _Parent, Child, _GrandChild):
 79            child = Child.objects.create()
 80            grandparent = GrandParent.objects.get(pk=child.pk)
 81
 82            resolved = get_deepest_child(grandparent)
 83
 84            self.assertIsInstance(resolved, Child)
 85            self.assertEqual(resolved.pk, child.pk)
 86
 87    def test_get_deepest_child_grandparent_to_grandchild(self):
 88        """GrandParent -> GrandChild."""
 89        with temporary_inheritance_models() as (GrandParent, _Parent, _Child, GrandChild):
 90            grandchild = GrandChild.objects.create()
 91            grandparent = GrandParent.objects.get(pk=grandchild.pk)
 92
 93            resolved = get_deepest_child(grandparent)
 94
 95            self.assertIsInstance(resolved, GrandChild)
 96            self.assertEqual(resolved.pk, grandchild.pk)
 97
 98    def test_get_deepest_child_parent_to_child(self):
 99        """Parent -> Child (start from non-root)."""
100        with temporary_inheritance_models() as (_GrandParent, Parent, Child, _GrandChild):
101            child = Child.objects.create()
102            parent = Parent.objects.get(pk=child.pk)
103
104            resolved = get_deepest_child(parent)
105
106            self.assertIsInstance(resolved, Child)
107            self.assertEqual(resolved.pk, child.pk)
108
109    def test_get_deepest_child_no_queries_with_preloaded_relations(self):
110        """No extra queries when the inheritance chain is fully select_related."""
111        with temporary_inheritance_models() as (GrandParent, _Parent, _Child, GrandChild):
112            grandchild = GrandChild.objects.create()
113            grandparent = GrandParent.objects.select_related("parent__child__grandchild").get(
114                pk=grandchild.pk
115            )
116
117            with self.assertNumQueries(0):
118                resolved = get_deepest_child(grandparent)
119
120            self.assertIsInstance(resolved, GrandChild)

Tests for helper functions in authentik.lib.utils.inheritance.

def test_get_deepest_child_grandparent_to_parent(self):
65    def test_get_deepest_child_grandparent_to_parent(self):
66        """GrandParent -> Parent."""
67        with temporary_inheritance_models() as (GrandParent, Parent, _Child, _GrandChild):
68            parent = Parent.objects.create()
69            grandparent = GrandParent.objects.get(pk=parent.pk)
70
71            resolved = get_deepest_child(grandparent)
72
73            self.assertIsInstance(resolved, Parent)
74            self.assertEqual(resolved.pk, parent.pk)

GrandParent -> Parent.

def test_get_deepest_child_grandparent_to_child(self):
76    def test_get_deepest_child_grandparent_to_child(self):
77        """GrandParent -> Child."""
78        with temporary_inheritance_models() as (GrandParent, _Parent, Child, _GrandChild):
79            child = Child.objects.create()
80            grandparent = GrandParent.objects.get(pk=child.pk)
81
82            resolved = get_deepest_child(grandparent)
83
84            self.assertIsInstance(resolved, Child)
85            self.assertEqual(resolved.pk, child.pk)

GrandParent -> Child.

def test_get_deepest_child_grandparent_to_grandchild(self):
87    def test_get_deepest_child_grandparent_to_grandchild(self):
88        """GrandParent -> GrandChild."""
89        with temporary_inheritance_models() as (GrandParent, _Parent, _Child, GrandChild):
90            grandchild = GrandChild.objects.create()
91            grandparent = GrandParent.objects.get(pk=grandchild.pk)
92
93            resolved = get_deepest_child(grandparent)
94
95            self.assertIsInstance(resolved, GrandChild)
96            self.assertEqual(resolved.pk, grandchild.pk)

GrandParent -> GrandChild.

def test_get_deepest_child_parent_to_child(self):
 98    def test_get_deepest_child_parent_to_child(self):
 99        """Parent -> Child (start from non-root)."""
100        with temporary_inheritance_models() as (_GrandParent, Parent, Child, _GrandChild):
101            child = Child.objects.create()
102            parent = Parent.objects.get(pk=child.pk)
103
104            resolved = get_deepest_child(parent)
105
106            self.assertIsInstance(resolved, Child)
107            self.assertEqual(resolved.pk, child.pk)

Parent -> Child (start from non-root).

def test_get_deepest_child_no_queries_with_preloaded_relations(self):
109    def test_get_deepest_child_no_queries_with_preloaded_relations(self):
110        """No extra queries when the inheritance chain is fully select_related."""
111        with temporary_inheritance_models() as (GrandParent, _Parent, _Child, GrandChild):
112            grandchild = GrandChild.objects.create()
113            grandparent = GrandParent.objects.select_related("parent__child__grandchild").get(
114                pk=grandchild.pk
115            )
116
117            with self.assertNumQueries(0):
118                resolved = get_deepest_child(grandparent)
119
120            self.assertIsInstance(resolved, GrandChild)

No extra queries when the inheritance chain is fully select_related.