示例#1
0
async def test_delete(kelvin_session_kwargs, new_school_user):
    user = await new_school_user()
    async with Session(**kelvin_session_kwargs) as session:
        obj = await UserResource(session=session).get(school=user.school, name=user.name)
        assert obj
        res = await obj.delete()
        assert res is None

    async with Session(**kelvin_session_kwargs) as session:
        with pytest.raises(NoObject):
            await UserResource(session=session).get(school=user.school, name=user.name)
示例#2
0
async def test_delete(kelvin_session_kwargs, new_school_class):
    sc1_dn, sc1_attr = await new_school_class()
    school = sc1_attr["school"]
    name = sc1_attr["name"]
    async with Session(**kelvin_session_kwargs) as session:
        obj = await SchoolClassResource(session=session).get(school=school,
                                                             name=name)
        assert obj
        res = await obj.delete()
        assert res is None

    async with Session(**kelvin_session_kwargs) as session:
        with pytest.raises(NoObject):
            await SchoolClassResource(session=session).get(school=school,
                                                           name=name)
示例#3
0
async def test_search_no_name_arg(
    compare_kelvin_obj_with_test_data,
    new_school,
    kelvin_session_kwargs,
    new_school_class,
):
    school = new_school(1)[0]
    school_name = school.name
    sc1_dn, sc1_attr = await new_school_class(school=school_name)
    sc2_dn, sc2_attr = await new_school_class(school=school_name)

    async with Session(**kelvin_session_kwargs) as session:
        objs = [
            obj async for obj in SchoolClassResource(session=session).search(
                school=school_name)
        ]

    assert objs, f"No SchoolClass in school {school!r} found."
    assert len(objs) >= 2
    assert sc1_dn in [sc.dn for sc in objs]
    assert sc2_dn in [sc.dn for sc in objs]
    for obj in objs:
        if obj.dn == sc1_dn:
            compare_kelvin_obj_with_test_data(obj, **sc1_attr)
        if obj.dn == sc2_dn:
            compare_kelvin_obj_with_test_data(obj, **sc2_attr)
async def test_search_partial_name_arg(
    compare_kelvin_obj_with_test_data,
    kelvin_session_kwargs,
    new_school,
):
    school = new_school(1)[0]
    name_len = len(school.name)
    name_begin = school.name[:int(name_len / 2)]
    name_end = school.name[len(name_begin):]

    async with Session(**kelvin_session_kwargs) as session:
        objs1 = [
            obj async for obj in SchoolResource(session=session).search(
                name=f"{name_begin}*")
        ]
        objs2 = [
            obj async for obj in SchoolResource(session=session).search(
                name=f"*{name_end}")
        ]
    assert objs1, f"No School for name='{name_begin}*' found."
    assert len(objs1) >= 1
    assert school.dn in [o.dn for o in objs1]
    assert objs2, f"No School for name='*{name_end}' found."
    assert len(objs2) >= 1
    assert school.dn in [o.dn for o in objs2]
示例#5
0
async def test_search_partial_name_arg(
    compare_kelvin_obj_with_test_data,
    new_school,
    kelvin_session_kwargs,
    new_school_class,
):
    school = new_school(1)[0]
    school_name = school.name
    # don't use usual name starting with 'test.', as leftovers of previous
    # tests will also match 'test.*'
    name = f"{fake.first_name()}{fake.first_name()}"
    sc1_dn, sc1_attr = await new_school_class(school=school_name, name=name)
    name_len = len(name)
    name_begin = name[:int(name_len / 2)]
    name_end = name[len(name_begin):]

    async with Session(**kelvin_session_kwargs) as session:
        objs1 = [
            obj async for obj in SchoolClassResource(session=session).search(
                school=school_name, name=f"{name_begin}*")
        ]
        objs2 = [
            obj async for obj in SchoolClassResource(session=session).search(
                school=school_name, name=f"*{name_end}")
        ]
    assert objs1, f"No SchoolClass for school={school_name!r} and name='{name_begin}*' found."
    assert len(objs1) == 1
    assert sc1_dn == objs1[0].dn
    assert objs2, f"No SchoolClass for school={school_name!r} and name='*{name_end}' found."
    assert len(objs2) == 1
    assert sc1_dn == objs2[0].dn
示例#6
0
def kelvin_client_session(school_authority: SchoolAuthorityConfiguration,
                          plugin_name: str) -> Session:
    m: Match = kelvin_url_regex().match(school_authority.url)
    if not m:
        raise ValueError(
            f"Bad Kelvin URL in school authority {school_authority!r}: {school_authority.url!r}."
        )
    host = m.groupdict()["host"]
    try:
        username = school_authority.plugin_configs[plugin_name]["username"]
        password = school_authority.plugin_configs[plugin_name][
            "password"].get_secret_value()
    except KeyError as exc:
        raise ValueError(
            f"Missing {exc!s} in Kelvin plugin configuration of school authority "
            f"{school_authority.dict()!r}.")
    timeout = httpx.Timeout(timeout=HTTP_REQUEST_TIMEOUT)
    certificate_path = fetch_ucs_certificate(host)
    ssl_context: ssl.SSLContext = httpx.create_ssl_context(
        verify=str(certificate_path))
    for k, v in school_authority.plugin_configs[plugin_name].get(
            "ssl_context", {}).items():
        logger.info("Applying to SSL context: %r=%r", k, v)
        setattr(ssl_context, k, v)
    return Session(
        username=username,
        password=password,
        host=host,
        verify=ssl_context,
        timeout=timeout,
    )
示例#7
0
 def _func(host: str, **kwargs) -> Session:
     if host not in sessions:
         session_kwargs = kelvin_session_kwargs(host)
         session_kwargs.update(kwargs)
         sessions[host] = Session(**session_kwargs)
         sessions[host].open()
     return sessions[host]
async def test_create_all_attrs(
    base_dn,
    compare_kelvin_obj_with_test_data,
    kelvin_session_kwargs,
    ldap_access,
    new_school_test_obj,
    schedule_delete_ou_using_ssh,
):
    school_data = new_school_test_obj()
    schedule_delete_ou_using_ssh(school_data.name)
    async with Session(**kelvin_session_kwargs) as session:
        school_kwargs = asdict(school_data)
        print(f"Creating school with kwargs: {school_kwargs!r}")
        school_obj = School(session=session, **school_kwargs)
        await school_obj.save()
        print("Created new School: {!r}".format(school_obj.as_dict()))

    ldap_filter = f"(&(ou={school_obj.name})(objectClass=ucsschoolOrganizationalUnit))"
    ldap_objs = await ldap_access.search(filter_s=ldap_filter)
    assert len(ldap_objs) == 1
    ldap_obj = ldap_objs[0]
    assert ldap_obj.entry_dn == school_obj.dn
    assert ldap_obj["ou"].value == school_obj.name
    assert ldap_obj[
        "ucsschoolRole"].value == f"school:school:{school_obj.name}"
    assert ldap_obj["displayName"].value == school_obj.display_name
    dc_base_dn = f"cn=dc,cn=server,cn=computers,ou={school_obj.name},{base_dn}"
    class_share_file_server_dn_dn = f"cn={school_obj.class_share_file_server},{dc_base_dn}"
    home_share_file_server_dn = f"cn={school_obj.home_share_file_server},{dc_base_dn}"
    assert ldap_obj[
        "ucsschoolClassShareFileServer"].value == class_share_file_server_dn_dn
    assert ldap_obj[
        "ucsschoolHomeShareFileServer"].value == home_share_file_server_dn
示例#9
0
async def test_create(
    compare_kelvin_obj_with_test_data,
    kelvin_session_kwargs,
    ldap_access,
    new_school_class_test_obj,
    schedule_delete_obj,
    new_school_user,
):
    sc_data = new_school_class_test_obj()
    user1 = await new_school_user(school=sc_data.school)
    user2 = await new_school_user(school=sc_data.school)
    sc_data.users = [user1.name, user2.name]
    async with Session(**kelvin_session_kwargs) as session:
        sc_kwargs = asdict(sc_data)
        sc_obj = SchoolClass(session=session, **sc_kwargs)
        schedule_delete_obj(object_type="class",
                            school=sc_data.school,
                            name=sc_data.name)
        await sc_obj.save()
        print("Created new SchoolClass: {!r}".format(sc_obj.as_dict()))

    ldap_filter = f"(&(cn={sc_data.school}-{sc_data.name})(objectClass=ucsschoolGroup))"
    ldap_objs = await ldap_access.search(filter_s=ldap_filter)
    assert len(ldap_objs) == 1
    ldap_obj = ldap_objs[0]
    assert ldap_obj.entry_dn == sc_obj.dn
    assert ldap_obj["cn"].value == f"{sc_data.school}-{sc_data.name}"
    assert ldap_obj[
        "ucsschoolRole"].value == f"school_class:school:{sc_data.school}"
    assert ldap_obj["description"].value == sc_obj.description
    assert set(ldap_obj["uniqueMember"].value) == {user1.dn, user2.dn}
示例#10
0
async def test_search_partial_name_arg(
    compare_kelvin_obj_with_test_data,
    new_school,
    kelvin_session_kwargs,
    new_school_user,
):
    school = new_school(1)[0]
    user = await new_school_user(school=school.name)
    name = user.name
    name_len = len(name)
    name_begin = name[: int(name_len / 2)]
    name_end = name[len(name_begin) :]

    async with Session(**kelvin_session_kwargs) as session:
        objs1 = [
            obj
            async for obj in UserResource(session=session).search(
                school=school.name, name=f"{name_begin}*"
            )
        ]
        objs2 = [
            obj
            async for obj in UserResource(session=session).search(
                school=school.name, name=f"*{name_end}"
            )
        ]
    assert objs1, f"No User for school={school.name!r} and name='{name_begin}*' found."
    assert len(objs1) == 1
    assert user.dn == objs1[0].dn
    assert objs2, f"No User for school={school.name!r} and name='*{name_end}' found."
    assert len(objs2) == 1
    assert user.dn == objs2[0].dn
示例#11
0
async def test_move_change_name(
    compare_kelvin_obj_with_test_data,
    kelvin_session_kwargs,
    new_school_user,
    schedule_delete_obj,
):
    user = await new_school_user()
    old_name = user.name
    new_name = fake.first_name()
    assert old_name != new_name
    async with Session(**kelvin_session_kwargs) as session:
        user_resource = UserResource(session=session)
        obj: User = await user_resource.get(school=user.school, name=old_name)
        assert obj.name == old_name
        compare_kelvin_obj_with_test_data(obj, **asdict(user))
        obj.name = new_name
        old_url = obj.url
        schedule_delete_obj(object_type="user", name=new_name)

        new_obj: User = await obj.save()
        assert new_obj is obj
        assert new_obj.name == new_name
        assert f"uid={new_name}" in new_obj.dn
        assert new_obj.url != old_url
        # load fresh object
        fresh_obj: User = await user_resource.get(school=user.school, name=new_name)
        assert fresh_obj.name == new_name
        assert fresh_obj.url != old_url
        compare_kelvin_obj_with_test_data(fresh_obj, **new_obj.as_dict())
示例#12
0
async def test_modify(
    compare_kelvin_obj_with_test_data,
    kelvin_session_kwargs,
    new_school_class,
    new_school_class_test_obj,
):
    sc1_dn, sc1_attr = await new_school_class()
    new_data = asdict(new_school_class_test_obj())
    school = sc1_attr["school"]
    name = sc1_attr["name"]
    async with Session(**kelvin_session_kwargs) as session:
        sc_resource = SchoolClassResource(session=session)
        obj: SchoolClass = await sc_resource.get(school=school, name=name)
        compare_kelvin_obj_with_test_data(obj, dn=sc1_dn, **sc1_attr)
        for k, v in new_data.items():
            if k not in ("school", "name", "dn", "url", "ucsschool_roles"):
                setattr(obj, k, v)
        new_obj: SchoolClass = await obj.save()
        assert new_obj is obj
        assert new_obj.as_dict() == obj.as_dict()
        # load fresh object
        fresh_obj: SchoolClass = await sc_resource.get(school=school,
                                                       name=name)
        assert fresh_obj.as_dict() == new_obj.as_dict()
        compare_kelvin_obj_with_test_data(fresh_obj, **obj.as_dict())
示例#13
0
async def test_move_change_name(
    compare_kelvin_obj_with_test_data,
    kelvin_session_kwargs,
    new_school_class,
    schedule_delete_obj,
):
    sc1_dn, sc1_attr = await new_school_class()
    school = sc1_attr["school"]
    old_name = sc1_attr["name"]
    new_name = fake.first_name()
    assert old_name != new_name
    async with Session(**kelvin_session_kwargs) as session:
        sc_resource = SchoolClassResource(session=session)
        obj: SchoolClass = await sc_resource.get(school=school, name=old_name)
        assert obj.name == old_name
        compare_kelvin_obj_with_test_data(obj, dn=sc1_dn, **sc1_attr)
        obj.name = new_name
        old_url = obj.url
        schedule_delete_obj(object_type="class", school=school, name=new_name)

        new_obj: SchoolClass = await obj.save()
        assert new_obj is obj
        assert new_obj.name == new_name
        assert new_obj.url != old_url
        # load fresh object
        fresh_obj: SchoolClass = await sc_resource.get(school=school,
                                                       name=new_name)
        assert fresh_obj.name == new_name
        assert fresh_obj.url != old_url
        compare_kelvin_obj_with_test_data(fresh_obj, **new_obj.as_dict())
示例#14
0
async def test_modify(
    check_password,
    compare_kelvin_obj_with_test_data,
    kelvin_session_kwargs,
    new_school_user,
    new_user_test_obj,
):
    user = await new_school_user()
    new_data = asdict(
        new_user_test_obj(
            name=user.name,
            roles=user.roles,
            school=user.school,
            schools=user.schools,
            ucsschool_roles=user.ucsschool_roles,
        )
    )
    async with Session(**kelvin_session_kwargs) as session:
        user_resource = UserResource(session=session)
        obj: User = await user_resource.get(school=user.school, name=user.name)
        compare_kelvin_obj_with_test_data(obj, **asdict(user))
        await check_password(obj.dn, user.password)
        for k, v in new_data.items():
            if k not in ("dn", "url"):
                setattr(obj, k, v)
        new_obj: User = await obj.save()
        assert new_obj is obj
        compare_kelvin_obj_with_test_data(new_obj, **obj.as_dict())
        # load fresh object
        fresh_obj: User = await user_resource.get(school=user.school, name=user.name)
        compare_kelvin_obj_with_test_data(fresh_obj, **new_obj.as_dict())
        compare_kelvin_obj_with_test_data(fresh_obj, **obj.as_dict())
        await check_password(fresh_obj.dn, new_data["password"])
示例#15
0
async def test_move_change_school(
    compare_kelvin_obj_with_test_data,
    new_school,
    kelvin_session_kwargs,
    new_school_class,
    schedule_delete_obj,
):
    sc1_dn, sc1_attr = await new_school_class()
    old_school = sc1_attr["school"]
    school1, school2 = new_school(2)
    ou1, ou2 = school1.name, school2.name
    name = sc1_attr["name"]
    new_school_ = ou1 if old_school == ou2 else ou2
    assert old_school != new_school_
    async with Session(**kelvin_session_kwargs) as session:
        sc_resource = SchoolClassResource(session=session)
        obj: SchoolClass = await sc_resource.get(school=old_school, name=name)
        assert obj.school == old_school
        compare_kelvin_obj_with_test_data(obj, dn=sc1_dn, **sc1_attr)
        obj.school = new_school_
        schedule_delete_obj(object_type="class", school=new_school_, name=name)

        with pytest.raises(InvalidRequest) as exc_info:
            await obj.save()
        assert "Moving of class to other school is not allowed" in exc_info.value.args[
            0]
async def test_role_attrs(compare_kelvin_obj_with_test_data,
                          kelvin_session_kwargs):
    role = random.choice(("staff", "student", "teacher"))
    async with Session(**kelvin_session_kwargs) as session:
        obj = await RoleResource(session=session).get(name=role)
    assert obj.name == role
    assert set(obj._kelvin_attrs) == {"name", "display_name"}
    assert set(obj.as_dict().keys()) == {"name", "display_name", "url"}
async def test_get_from_url(compare_kelvin_obj_with_test_data,
                            kelvin_session_kwargs, new_school):
    school = new_school(1)[0]
    url = URL_SCHOOL_OBJECT.format(host=kelvin_session_kwargs["host"],
                                   name=school.name)
    async with Session(**kelvin_session_kwargs) as session:
        obj = await SchoolResource(session=session).get_from_url(url)
    compare_kelvin_obj_with_test_data(obj, **asdict(school))
示例#18
0
async def test_search_inexact_school(new_school, kelvin_session_kwargs):
    school = new_school(1)[0]
    school_name = school.name
    school_name_len = len(school_name)
    school_name_begin = school_name[: int(school_name_len / 2)]

    async with Session(**kelvin_session_kwargs) as session:
        with pytest.raises(InvalidRequest):
            assert [
                obj async for obj in UserResource(session=session).search(school=f"{school_name_begin}*")
            ]
async def test_search_no_name_arg(compare_kelvin_obj_with_test_data,
                                  kelvin_session_kwargs):
    async with Session(**kelvin_session_kwargs) as session:
        objs = [obj async for obj in RoleResource(session=session).search()]

    assert objs, "No roles found."
    assert len(objs) == 3
    for obj in objs:
        assert not hasattr(obj, "dn")
        assert not hasattr(obj, "ucsschool_roles")
    assert {"staff", "student", "teacher"} == {obj.name for obj in objs}
示例#20
0
async def test_get_from_url(compare_kelvin_obj_with_test_data,
                            kelvin_session_kwargs, new_school_class):
    sc1_dn, sc1_attr = await new_school_class()
    school = sc1_attr["school"]
    name = sc1_attr["name"]
    url = URL_CLASS_OBJECT.format(host=kelvin_session_kwargs["host"],
                                  school=school,
                                  name=name)
    async with Session(**kelvin_session_kwargs) as session:
        obj = await SchoolClassResource(session=session).get_from_url(url)
    compare_kelvin_obj_with_test_data(obj, dn=sc1_dn, **sc1_attr)
示例#21
0
async def test_get_no_users(
    compare_kelvin_obj_with_test_data,
    kelvin_session_kwargs,
    new_school,
    new_school_class,
):
    sc_dn, sc_attr = await new_school_class()
    async with Session(**kelvin_session_kwargs) as session:
        obj = await SchoolClassResource(session=session
                                        ).get(school=sc_attr["school"],
                                              name=sc_attr["name"])
    compare_kelvin_obj_with_test_data(obj, dn=sc_dn, **sc_attr)
async def test_search_no_name_arg(compare_kelvin_obj_with_test_data,
                                  new_school, kelvin_session_kwargs):
    school1, school2 = new_school(2)

    async with Session(**kelvin_session_kwargs) as session:
        objs = [obj async for obj in SchoolResource(session=session).search()]

    assert objs, "No Schools found."
    assert len(objs) >= 2
    assert school1.dn in [school.dn for school in objs]
    assert school2.dn in [school.dn for school in objs]
    for obj in objs:
        if obj.dn == school1.dn:
            compare_kelvin_obj_with_test_data(obj, **asdict(school1))
        if obj.dn == school2.dn:
            compare_kelvin_obj_with_test_data(obj, **asdict(school2))
示例#23
0
async def test_get_with_users(
    compare_kelvin_obj_with_test_data,
    kelvin_session_kwargs,
    new_school,
    new_school_class,
    new_school_user,
):
    user1 = await new_school_user()
    user2 = await new_school_user(school=user1.school)
    sc_dn, sc_attr = await new_school_class(school=user1.school,
                                            users=[user1.name, user2.name])
    async with Session(**kelvin_session_kwargs) as session:
        obj = await SchoolClassResource(session=session
                                        ).get(school=sc_attr["school"],
                                              name=sc_attr["name"])
    assert set(obj.users) == {user1.name, user2.name}
    compare_kelvin_obj_with_test_data(obj, dn=sc_dn, **sc_attr)
示例#24
0
async def test_modify_password_hashes(
    check_password,
    kelvin_session_kwargs,
    password_hash,
    new_school_user,
):
    user = await new_school_user()
    password, password_hashes = await password_hash()
    assert user.password != password
    async with Session(**kelvin_session_kwargs) as session:
        user_resource = UserResource(session=session)
        obj: User = await user_resource.get(school=user.school, name=user.name)
        await check_password(obj.dn, user.password)
        obj.password = None
        obj.kelvin_password_hashes = PasswordsHashes(**asdict(password_hashes))
        await obj.save()
        fresh_obj: User = await user_resource.get(school=user.school, name=user.name)
        await check_password(fresh_obj.dn, password)
示例#25
0
async def test_reload(
    compare_kelvin_obj_with_test_data,
    kelvin_session_kwargs,
    ldap_access,
    new_school_user,
):
    user = await new_school_user()
    first_name_old = user.firstname
    first_name_new = fake.first_name()

    async with Session(**kelvin_session_kwargs) as session:
        obj: User = await UserResource(session=session).get(school=user.school, name=user.name)
        assert obj.firstname == first_name_old
        await obj.reload()
        assert obj.firstname == first_name_old
        await ldap_access.modify(obj.dn, {"givenName": [(ldap3.MODIFY_REPLACE, [first_name_new])]})
        await obj.reload()
        assert obj.firstname == first_name_new
示例#26
0
async def test_create_with_password_hashes(
    check_password,
    kelvin_session_kwargs,
    password_hash,
    new_user_test_obj,
    schedule_delete_obj,
):
    user_data = new_user_test_obj()
    password, password_hashes = await password_hash()
    assert user_data.password != password
    user_data.password = None
    user_data.kelvin_password_hashes = PasswordsHashes(**asdict(password_hashes))

    async with Session(**kelvin_session_kwargs) as session:
        user_obj = User(session=session, **asdict(user_data))
        schedule_delete_obj(object_type="user", name=user_data.name)
        await user_obj.save()
        print("Created new User: {!r}".format(user_obj.as_dict()))
    await check_password(user_obj.dn, password)
async def test_reload(compare_kelvin_obj_with_test_data, kelvin_session_kwargs,
                      ldap_access, new_school):
    school = new_school(1)[0]
    display_name_old = school.display_name
    display_name_new = fake.text(max_nb_chars=50)

    async with Session(**kelvin_session_kwargs) as session:
        obj: School = await SchoolResource(session=session
                                           ).get(name=school.name)
        assert obj.display_name == display_name_old
        await obj.reload()
        assert obj.display_name == display_name_old
        await ldap_access.modify(
            obj.dn,
            {"displayName": [(ldap3.MODIFY_REPLACE, [display_name_new])]})
        await obj.reload()
        assert obj.display_name == display_name_new
        await ldap_access.modify(
            obj.dn,
            {"displayName": [(ldap3.MODIFY_REPLACE, [display_name_old])]})
示例#28
0
async def test_search_exact(
    compare_kelvin_obj_with_test_data,
    new_school,
    kelvin_session_kwargs,
    new_school_user,
    attr,
):
    user = await new_school_user()
    value = getattr(user, attr)

    async with Session(**kelvin_session_kwargs) as session:
        objs = [
            obj
            async for obj in UserResource(session=session).search(school=user.school, **{attr: value})
        ]
    assert objs, f"No User for school={user.school!r} and {attr!r}={value!r} found."
    if attr in ("roles", "disabled"):
        assert len(objs) >= 1
        assert user.dn in [o.dn for o in objs]
    else:
        assert len(objs) == 1
        assert user.dn == objs[0].dn
示例#29
0
async def test_search_no_name_arg(
    compare_kelvin_obj_with_test_data,
    new_school,
    kelvin_session_kwargs,
    new_school_user,
):
    school = new_school(1)[0]
    user1 = await new_school_user(school=school.name)
    user2 = await new_school_user(school=school.name)

    async with Session(**kelvin_session_kwargs) as session:
        objs = [obj async for obj in UserResource(session=session).search(school=school.name)]

    assert objs, f"No Users in school {school.name!r} found."
    assert len(objs) >= 2
    assert user1.dn in [obj.dn for obj in objs]
    assert user2.dn in [obj.dn for obj in objs]
    for obj in objs:
        if obj.dn == user1.dn:
            compare_kelvin_obj_with_test_data(obj, **asdict(user1))
        elif obj.dn == user2.dn:
            compare_kelvin_obj_with_test_data(obj, **asdict(user2))
示例#30
0
async def test_search_inexact(
    compare_kelvin_obj_with_test_data,
    new_school,
    kelvin_session_kwargs,
    new_school_user,
    attr,
):
    user = await new_school_user()
    value = getattr(user, attr)
    value_len = len(value)
    value_begin = value[: int(value_len / 2)]
    value_end = value[len(value_begin) :]

    async with Session(**kelvin_session_kwargs) as session:
        objs1 = [
            obj
            async for obj in UserResource(session=session).search(
                school=user.school, **{attr: f"{value_begin}*"}
            )
        ]
        objs2 = [
            obj
            async for obj in UserResource(session=session).search(
                school=user.school, **{attr: f"*{value_end}"}
            )
        ]
    assert objs1, f"No User for school={user.school!r} and {attr!r}='{value_begin}*' found."
    assert objs2, f"No User for school={user.school!r} and {attr!r}='*{value_end}' found."
    if attr == "source_uid":
        assert len(objs1) >= 1
        assert user.dn in [o.dn for o in objs1]
        assert len(objs2) >= 1
        assert user.dn in [o.dn for o in objs2]
    else:
        assert len(objs1) == 1
        assert user.dn == objs1[0].dn
        assert len(objs2) == 1
        assert user.dn == objs2[0].dn