Skip to content

Commit 129def1

Browse files
committed
test: add unit tests for handling unknown and invalid scope keys
1 parent e18ae56 commit 129def1

1 file changed

Lines changed: 75 additions & 0 deletions

File tree

openedx_authz/tests/api/test_data.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,43 @@ def test_scope_validate_external_key(self, external_key, expected_valid, expecte
325325

326326
self.assertEqual(result, expected_valid)
327327

328+
def test_get_subclass_by_external_key_unknown_scope_raises_value_error(self):
329+
"""Unknown namespace should raise ValueError in get_subclass_by_external_key."""
330+
with self.assertRaises(ValueError):
331+
ScopeMeta.get_subclass_by_external_key("unknown:DemoX")
332+
333+
def test_get_subclass_by_external_key_invalid_format_raises_value_error(self):
334+
"""Invalid format (fails subclass.validate_external_key) should raise ValueError."""
335+
with self.assertRaises(ValueError):
336+
ScopeMeta.get_subclass_by_external_key("lib:invalid_library_key")
337+
338+
def test_scope_meta_initializes_registries_when_missing(self):
339+
"""ScopeMeta should create registries if they don't exist on initialization.
340+
341+
This validates the defensive branch in ScopeMeta.__init__ that initializes
342+
scope_registry and glob_registry when they are not present on the class.
343+
"""
344+
original_scope_registry = ScopeMeta.scope_registry
345+
original_glob_registry = ScopeMeta.glob_registry
346+
347+
try:
348+
# Simulate an environment where the registries are not yet defined
349+
del ScopeMeta.scope_registry
350+
del ScopeMeta.glob_registry
351+
352+
class TempScope(ScopeData):
353+
NAMESPACE = "temp"
354+
355+
# Metaclass should have recreated the registries on the class
356+
self.assertTrue(hasattr(TempScope, "scope_registry"))
357+
self.assertTrue(hasattr(TempScope, "glob_registry"))
358+
# And the new scope should be registered under its namespace
359+
self.assertIs(TempScope.scope_registry.get("temp"), TempScope)
360+
finally:
361+
# Restore original registries to avoid side effects on other tests
362+
ScopeMeta.scope_registry = original_scope_registry
363+
ScopeMeta.glob_registry = original_glob_registry
364+
328365
def test_direct_subclass_instantiation_bypasses_metaclass(self):
329366
"""Test that direct subclass instantiation doesn't trigger metaclass logic.
330367
@@ -680,6 +717,17 @@ class TestOrgLibraryGlobData(TestCase):
680717
("lib:DemoX:*", True),
681718
("lib:Org-123:*", True),
682719
("lib:Org.with.dots:*", True),
720+
("lib:Org With Space:*", False),
721+
("lib:Org/With/Slash:*", False),
722+
("lib:Org\\With\\Backslash:*", False),
723+
("lib:Org,With,Comma:*", False),
724+
("lib:Org;With;Semicolon:*", False),
725+
("lib:Org@WithAt:*", False),
726+
("lib:Org#WithHash:*", False),
727+
("lib:Org$WithDollar:*", False),
728+
("lib:Org&WithAmp:*", False),
729+
("lib:Org+WithPlus:*", False),
730+
("lib:(Org):*", False),
683731
("lib:Org", False),
684732
("other:DemoX:*", False),
685733
("lib:DemoX:*:*", False),
@@ -695,6 +743,8 @@ def test_validate_external_key(self, external_key, expected_valid):
695743
("lib:Org.with.dots:*", "Org.with.dots"),
696744
("lib:Org:With:Colon:*", None),
697745
("lib:*", None),
746+
("lib:DemoX", None),
747+
("lib:DemoX:*:*", None),
698748
)
699749
@unpack
700750
def test_get_org(self, external_key, expected_org):
@@ -728,6 +778,13 @@ def test_exists_false_when_org_exists_but_no_libraries_in_db(self):
728778

729779
self.assertFalse(result)
730780

781+
def test_exists_false_when_org_cannot_be_parsed(self):
782+
"""exists() returns False when org property is None (invalid pattern)."""
783+
scope = OrgLibraryGlobData(external_key="lib:Org:With:Colon:*")
784+
785+
self.assertIsNone(scope.org)
786+
self.assertFalse(scope.exists())
787+
731788

732789
@ddt
733790
@override_settings(OPENEDX_AUTHZ_COURSE_OVERVIEW_MODEL="course_overviews.CourseOverview")
@@ -738,6 +795,17 @@ class TestOrgCourseGlobData(TestCase):
738795
("course-v1:OpenedX+*", True),
739796
("course-v1:My-Org_1+*", True),
740797
("course-v1:Org.with.dots+*", True),
798+
("course-v1:Org With Space+*", False),
799+
("course-v1:Org/With/Slash+*", False),
800+
("course-v1:Org\\With\\Backslash+*", False),
801+
("course-v1:Org,With,Comma+*", False),
802+
("course-v1:Org;With;Semicolon+*", False),
803+
("course-v1:Org@WithAt+*", False),
804+
("course-v1:Org#WithHash+*", False),
805+
("course-v1:Org$WithDollar+*", False),
806+
("course-v1:Org&WithAmp+*", False),
807+
("course-v1:Org+WithPlus+*", False),
808+
("course-v1:(Org)+*", False),
741809
("course-v1:Org:With:Plus+*", False),
742810
("course-v1:OpenedX", False),
743811
("other:OpenedX+*", False),
@@ -786,3 +854,10 @@ def test_exists_false_when_org_exists_but_no_courses(self):
786854
result = OrgCourseGlobData(external_key=f"course-v1:{org_name}+*").exists()
787855

788856
self.assertFalse(result)
857+
858+
def test_exists_false_when_org_cannot_be_parsed(self):
859+
"""exists() returns False when org property is None (invalid pattern)."""
860+
scope = OrgCourseGlobData(external_key="course-v1:Org:With:Colon+*")
861+
862+
self.assertIsNone(scope.org)
863+
self.assertFalse(scope.exists())

0 commit comments

Comments
 (0)