@@ -38,6 +38,62 @@ def test_get_path_in_dict():
3838 assert get_path_in_dict ([["foo" , "loo" ], []], {"foo" : {"loo" : 3 }}) == 3
3939
4040
41+ def test_get_path_in_dict_pathlist_fallback_on_missing_key ():
42+ """When the first path's key is entirely absent, later paths must still be tried."""
43+ assert (
44+ get_path_in_dict ([["missing" , "sub" ], ["foo" , "bar" ]], {"foo" : {"bar" : 3 }}) == 3
45+ )
46+ assert (
47+ get_path_in_dict ([["a" , "b" ], ["c" , "d" ], ["e" , "f" ]], {"e" : {"f" : 42 }}) == 42
48+ )
49+ assert (
50+ get_path_in_dict (
51+ [["missing" , "sub" ], ["also_missing" , "sub" ]], {"foo" : {"bar" : 3 }}
52+ )
53+ is None
54+ )
55+
56+
57+ def test_get_path_in_dict_pathlist_non_dict_intermediate ():
58+ """When an intermediate value is a non-dict (e.g. int), later paths must still be tried."""
59+ assert (
60+ get_path_in_dict (
61+ [["foo" , "bar" ], ["baz" , "qux" ]], {"foo" : 42 , "baz" : {"qux" : 7 }}
62+ )
63+ == 7
64+ )
65+ assert (
66+ get_path_in_dict (
67+ [["a" , "b" , "c" ], ["x" , "y" ]],
68+ {"a" : {"b" : "not_a_dict" }, "x" : {"y" : 99 }},
69+ )
70+ == 99
71+ )
72+
73+
74+ def test_get_path_in_dict_zitadel_admin_path ():
75+ """Real-world scenario: Zitadel project-scoped role claims with PathList fallback."""
76+ token = {
77+ "urn:zitadel:iam:org:project:12345:roles" : {
78+ "MatrixAdmin" : {"org_id" : "famedly.localhost" }
79+ },
80+ }
81+ assert get_path_in_dict (
82+ [
83+ ["roles" , "Admin" ],
84+ ["urn:zitadel:iam:org:project:12345:roles" , "MatrixAdmin" ],
85+ ],
86+ token ,
87+ ) == {"org_id" : "famedly.localhost" }
88+ assert get_path_in_dict (
89+ [
90+ ["urn:zitadel:iam:org:project:12345:roles" , "MatrixAdmin" ],
91+ ["roles" , "Admin" ],
92+ ],
93+ token ,
94+ ) == {"org_id" : "famedly.localhost" }
95+
96+
4197def test_validate_scopes ():
4298 assert validate_scopes ("foo boo" , "boo foo" )
4399 assert validate_scopes (["foo" , "boo" ], "boo foo" )
0 commit comments