async def test_login_back_to_account_list( aqtbot, gui_factory, autoclose_dialog, client_config, alice, bob ): # Create an existing device before starting the gui password = "******" save_device_with_password(client_config.config_dir, alice, password) save_device_with_password(client_config.config_dir, bob, password) gui = await gui_factory() lw = gui.test_get_login_widget() accounts_w = lw.widget.layout().itemAt(0).widget() assert accounts_w async with aqtbot.wait_signal(accounts_w.account_clicked): await aqtbot.mouse_click( accounts_w.accounts_widget.layout().itemAt(0).widget(), QtCore.Qt.LeftButton ) def _password_widget_shown(): assert isinstance(lw.widget.layout().itemAt(0).widget(), LoginPasswordInputWidget) await aqtbot.wait_until(_password_widget_shown) password_w = lw.widget.layout().itemAt(0).widget() async with aqtbot.wait_signal(password_w.back_clicked): await aqtbot.mouse_click(password_w.button_back, QtCore.Qt.LeftButton) def _account_widget_shown(): assert isinstance(lw.widget.layout().itemAt(0).widget(), LoginAccountsWidget)
async def test_tab_login_logout(aqtbot, running_backend, gui_factory, autoclose_dialog, client_config, alice): password = "******" save_device_with_password(client_config.config_dir, alice, password) gui = await gui_factory() assert gui.tab_center.count() == 1 assert gui.tab_center.tabText(0) == translate( "TEXT_TAB_TITLE_LOG_IN_SCREEN") assert not gui.add_tab_button.isEnabled() first_created_tab = gui.test_get_tab() await gui.test_switch_to_logged_in(alice) assert gui.tab_center.count() == 1 assert gui.tab_center.tabText( 0) == "CoolOrg - Alicey McAliceFace - My dev1 machine" assert gui.add_tab_button.isEnabled() assert gui.test_get_tab() == first_created_tab await gui.test_logout() assert gui.tab_center.count() == 1 assert gui.tab_center.tabText(0) == translate( "TEXT_TAB_TITLE_LOG_IN_SCREEN") assert not gui.add_tab_button.isEnabled() assert gui.test_get_tab() != first_created_tab
async def test_login(aqtbot, gui_factory, autoclose_dialog, client_config, alice): # Create an existing device before starting the gui password = "******" save_device_with_password(client_config.config_dir, alice, password) gui = await gui_factory() lw = gui.test_get_login_widget() tabw = gui.test_get_tab() accounts_w = lw.widget.layout().itemAt(0).widget() assert accounts_w def _password_widget_shown(): assert isinstance(lw.widget.layout().itemAt(0).widget(), LoginPasswordInputWidget) await aqtbot.wait_until(_password_widget_shown) password_w = lw.widget.layout().itemAt(0).widget() await aqtbot.key_clicks(password_w.line_edit_password, "P2ssxdor!s3") async with aqtbot.wait_signals([lw.login_with_password_clicked, tabw.logged_in]): await aqtbot.mouse_click(password_w.button_login, QtCore.Qt.LeftButton) central_widget = gui.test_get_central_widget() assert central_widget with aqtbot.qtbot.waitExposed(central_widget): assert ( central_widget.button_user.text() == f"{alice.organization_id}\n{alice.short_user_display}" ) assert ( gui.tab_center.tabText(0) == f"{alice.organization_id} - {alice.short_user_display} - {alice.device_display}" )
def test_password_save_and_load(path_exists, config_dir, alice): config_dir = config_dir if path_exists else fix_dir(config_dir / "dummy") save_device_with_password(config_dir, alice, "S3Cr37") key_file = get_key_file(config_dir, alice) alice_reloaded = load_device_with_password(key_file, "S3Cr37") assert alice == alice_reloaded
async def test_login_device_list(aqtbot, gui_factory, autoclose_dialog, client_config, alice, bob): password = "******" save_device_with_password(client_config.config_dir, alice, password) save_device_with_password(client_config.config_dir, bob, password) gui = await gui_factory() lw = gui.test_get_login_widget() accounts_w = lw.widget.layout().itemAt(0).widget() assert accounts_w assert accounts_w.accounts_widget.layout().count() == 3 dev1_w = accounts_w.accounts_widget.layout().itemAt(0).widget() dev2_w = accounts_w.accounts_widget.layout().itemAt(1).widget() case1 = ( dev1_w.label_name.text() == "Alicey McAliceFace" and dev2_w.label_name.text() == "Boby McBobFace" ) case2 = ( dev2_w.label_name.text() == "Alicey McAliceFace" and dev1_w.label_name.text() == "Boby McBobFace" ) assert case1 ^ case2 assert dev1_w.label_device.text() == "My dev1 machine" assert dev1_w.label_organization.text() == "CoolOrg" assert dev2_w.label_device.text() == "My dev1 machine" assert dev2_w.label_organization.text() == "CoolOrg"
async def _claim_invitation(config, addr, password): async with backend_invited_cmds_factory( addr=addr, keepalive=config.backend_connection_keepalive) as cmds: try: async with spinner("Retrieving invitation info"): initial_ctx = await claimer_retrieve_info(cmds) except BackendConnectionRefused: raise RuntimeError("Invitation not found") if initial_ctx.greeter_human_handle: display_greeter = click.style(str( initial_ctx.greeter_human_handle), fg="yellow") else: display_greeter = click.style(initial_ctx.greeter_user_id, fg="yellow") click.echo(f"Invitation greeter: {display_greeter}") while True: try: if isinstance(initial_ctx, DeviceClaimInitialCtx): new_device = await _do_claim_device(initial_ctx) else: assert isinstance(initial_ctx, UserClaimInitialCtx) new_device = await _do_claim_user(initial_ctx) if new_device: break except InviteError as exc: click.secho(str(exc), fg="red") click.secho("Restarting the invitation process", fg="red") device_display = click.style(new_device.slughash, fg="yellow") with operation(f"Saving device {device_display}"): save_device_with_password(config.config_dir, new_device, password)
def test_share_workspace(tmpdir, alice, bob): # As usual Windows path require a big hack... config_dir = tmpdir.strpath.replace("\\", "\\\\") # Mocking factory_mock = MagicMock() share_mock = MagicMock() @asynccontextmanager async def logged_client_factory(*args, **kwargs): yield factory_mock(*args, **kwargs) async def share(*args, **kwargs): return share_mock(*args, **kwargs) factory_mock.return_value.user_fs.workspace_share = share password = "******" save_device_with_password(Path(config_dir), bob, password) with patch("guardata.client.cli.share_workspace.logged_client_factory", logged_client_factory): runner = CliRunner() args = (f"client share_workspace --password {password} " f"--device={bob.slughash} --config-dir={config_dir} " f"ws1 {alice.user_id}") result = runner.invoke(cli, args) print(result.output) assert result.exit_code == 0 factory_mock.assert_called_once_with(ANY, bob) share_mock.assert_called_once_with("/ws1", alice.user_id)
async def _do_bootstrap_organization( config_dir, password: str, password_check: str, human_handle: HumanHandle, device_label: str, bootstrap_addr: BackendOrganizationBootstrapAddr, ): if password != password_check: raise JobResultError("password-mismatch") if len(password) < 8: raise JobResultError("password-size") try: async with apiv1_backend_anonymous_cmds_factory( addr=bootstrap_addr) as cmds: new_device = await bootstrap_organization( cmds=cmds, human_handle=human_handle, device_label=device_label) save_device_with_password(config_dir, new_device, password) return new_device, password except InviteNotFoundError as exc: raise JobResultError("not-found", info=str(exc)) from exc except InviteAlreadyUsedError as exc: raise JobResultError("already-bootstrapped", info=str(exc)) from exc except BackendConnectionRefused as exc: raise JobResultError("invalid-url", info=str(exc)) from exc except BackendNotAvailable as exc: raise JobResultError("backend-offline", info=str(exc)) from exc except BackendConnectionError as exc: raise JobResultError("refused-by-backend", info=str(exc)) from exc
async def _add_random_device(cmds, password, config_dir, device, force, additional_devices_number): for _ in range(additional_devices_number): requested_device_label = "device_" + str(uuid4())[:8] new_device = await _register_new_device( cmds=cmds, author=device, device_label=requested_device_label) save_device_with_password(config_dir, new_device, password, force=force)
def test_available_devices_slughash_uniqueness(organization_factory, local_device_factory, config_dir): def _to_available(device): return AvailableDevice( key_file_path=get_default_key_file(config_dir, device), organization_id=device.organization_id, device_id=device.device_id, human_handle=device.human_handle, device_label=device.device_label, root_verify_key_hash=device.root_verify_key_hash, ) def _assert_different_as_available(d1, d2): available_device_d1 = _to_available(d1) available_device_d2 = _to_available(d2) assert available_device_d1.slughash != available_device_d2.slughash # Make sure slughash is consistent between LocalDevice and AvailableDevice assert available_device_d1.slughash == d1.slughash assert available_device_d2.slughash == d2.slughash o1 = organization_factory("org1") o2 = organization_factory("org2") # Different user id o1u1d1 = local_device_factory("u1@d1", o1) o1u2d1 = local_device_factory("u2@d1", o1) _assert_different_as_available(o1u1d1, o1u2d1) # Different device name o1u1d2 = local_device_factory("u1@d2", o1) _assert_different_as_available(o1u1d1, o1u1d2) # Different organization id o2u1d1 = local_device_factory("u1@d1", o2) _assert_different_as_available(o1u1d1, o2u1d1) # Same organization_id, but different root verify key ! dummy_key = SigningKey.generate().verify_key o1u1d1_bad_rvk = o1u1d1.evolve( organization_addr=o1u1d1.organization_addr.build( backend_addr=o1u1d1.organization_addr, organization_id=o1u1d1.organization_addr.organization_id, root_verify_key=dummy_key, )) _assert_different_as_available(o1u1d1, o1u1d1_bad_rvk) # Finally make sure slughash is stable through save/load save_device_with_password(config_dir, o1u1d1, "S3Cr37") key_file = get_key_file(config_dir, o1u1d1) o1u1d1_reloaded = load_device_with_password(key_file, "S3Cr37") available_device = _to_available(o1u1d1) available_device_reloaded = _to_available(o1u1d1_reloaded) assert available_device.slughash == available_device_reloaded.slughash
def test_same_device_id_different_orginazations(config_dir, alice, otheralice): devices = (alice, otheralice) for device in devices: save_device_with_password(config_dir, device, f"S3Cr37-{device.organization_id}") for device in devices: key_file = get_key_file(config_dir, device) device_reloaded = load_device_with_password( key_file, f"S3Cr37-{device.organization_id}") assert device == device_reloaded
def test_change_password(config_dir, alice): old_password = "******" new_password = "******" save_device_with_password(config_dir, alice, old_password) key_file = get_key_file(config_dir, alice) change_device_password(key_file, old_password, new_password) alice_reloaded = load_device_with_password(key_file, new_password) assert alice == alice_reloaded with pytest.raises(LocalDeviceCryptoError): load_device_with_password(key_file, old_password)
async def test_tab_login_logout_two_tabs_logged_in(aqtbot, running_backend, gui_factory, autoclose_dialog, client_config, alice, bob): password = "******" save_device_with_password(client_config.config_dir, alice, password) gui = await gui_factory() assert gui.tab_center.count() == 1 assert gui.tab_center.tabText(0) == translate( "TEXT_TAB_TITLE_LOG_IN_SCREEN") await gui.test_switch_to_logged_in(alice) assert gui.tab_center.count() == 1 assert gui.tab_center.tabText( 0) == "CoolOrg - Alicey McAliceFace - My dev1 machine" alice_logged_tab = gui.test_get_tab() await aqtbot.mouse_click(gui.add_tab_button, QtCore.Qt.LeftButton) assert gui.tab_center.count() == 2 assert gui.tab_center.tabText( 0) == "CoolOrg - Alicey McAliceFace - My dev1 machine" assert gui.tab_center.tabText(1) == translate( "TEXT_TAB_TITLE_LOG_IN_SCREEN") save_device_with_password(client_config.config_dir, bob, password) await gui.test_switch_to_logged_in(bob) assert gui.tab_center.count() == 2 assert gui.tab_center.tabText( 0) == "CoolOrg - Alicey McAliceFace - My dev1 machine" assert gui.tab_center.tabText( 1) == "CoolOrg - Boby McBobFace - My dev1 machine" bob_logged_tab = gui.test_get_tab() assert bob_logged_tab != alice_logged_tab gui.switch_to_tab(0) def _logged_tab_displayed(): assert alice_logged_tab == gui.test_get_tab() await aqtbot.wait(200) await aqtbot.wait_until(_logged_tab_displayed) await gui.test_logout() assert gui.tab_center.count() == 2 assert gui.tab_center.tabText( 0) == "CoolOrg - Boby McBobFace - My dev1 machine" assert gui.tab_center.tabText(1) == translate( "TEXT_TAB_TITLE_LOG_IN_SCREEN")
async def test_switch_to_logged_in(self, device): try: save_device_with_password(self.config.config_dir, device, "P2ssxdor!s3") except LocalDeviceAlreadyExistsError: pass lw = self.test_get_login_widget() # Reload to take into account the new saved device await aqtbot.run(lw.reload_devices) tabw = self.test_get_tab() accounts_w = lw.widget.layout().itemAt(0).widget() assert accounts_w if isinstance(accounts_w, LoginAccountsWidget): async with aqtbot.wait_signal(accounts_w.account_clicked): await aqtbot.mouse_click( accounts_w.accounts_widget.layout().itemAt(0).widget(), QtCore.Qt.LeftButton) await aqtbot.wait(200) def _password_widget_shown(): assert isinstance(lw.widget.layout().itemAt(0).widget(), LoginPasswordInputWidget) await aqtbot.wait_until(_password_widget_shown) password_w = lw.widget.layout().itemAt(0).widget() await aqtbot.key_clicks(password_w.line_edit_password, "P2ssxdor!s3") async with aqtbot.wait_signals( [lw.login_with_password_clicked, tabw.logged_in], timeout=2500): await aqtbot.mouse_click(password_w.button_login, QtCore.Qt.LeftButton) central_widget = self.test_get_central_widget() assert central_widget is not None return central_widget
def test_list_devices(organization_factory, local_device_factory, config_dir): org1 = organization_factory("org1") org2 = organization_factory("org2") o1d11 = local_device_factory("d1@1", org1) o1d12 = local_device_factory("d1@2", org1) o1d21 = local_device_factory("d2@1", org1) o2d11 = local_device_factory("d1@1", org2, has_human_handle=False) o2d12 = local_device_factory("d1@2", org2, has_device_label=False) o2d21 = local_device_factory("d2@1", org2, has_human_handle=False, has_device_label=False) for device in [o1d11, o1d12, o1d21]: save_device_with_password(config_dir, device, "S3Cr37") for device in [o2d11, o2d12, o2d21]: save_device_with_password(config_dir, device, "secret") # Also add dummy stuff that should be ignored device_dir = config_dir / "devices" (device_dir / "bad1").touch() (device_dir / "373955f566#corp#bob@laptop").mkdir() dummy_slug = "a54ed6df3a#corp#alice@laptop" (device_dir / dummy_slug).mkdir() (device_dir / dummy_slug / f"{dummy_slug}.keys").write_bytes(b"dummy") devices = list_available_devices(config_dir) expected_devices = { AvailableDevice( key_file_path=get_key_file(config_dir, d), organization_id=d.organization_id, device_id=d.device_id, human_handle=d.human_handle, device_label=d.device_label, root_verify_key_hash=d.root_verify_key_hash, ) for d in [o1d11, o1d12, o1d21, o2d11, o2d12, o2d21] } assert set(devices) == expected_devices
def _on_finished(self, device, password): save_device_with_password(self.config.config_dir, device, password) show_info(self, _("TEXT_CLAIM_DEVICE_SUCCESSFUL")) self.status = (device, password) self.accepted.emit()
async def initialize_test_organization( config_dir: Path, backend_address: BackendAddr, password: str, administration_token: str, force: bool, additional_users_number: int, additional_devices_number: int, ) -> Tuple[LocalDevice, LocalDevice, LocalDevice]: configure_logging("WARNING") organization_id = OrganizationID("Org") # Create organization async with apiv1_backend_administration_cmds_factory( backend_address, administration_token) as administration_cmds: rep = await administration_cmds.organization_create(organization_id) assert rep["status"] == "ok" bootstrap_token = rep["bootstrap_token"] organization_bootstrap_addr = BackendOrganizationBootstrapAddr.build( backend_address, organization_id, bootstrap_token) # Bootstrap organization and Alice user and create device "laptop" for Alice async with apiv1_backend_anonymous_cmds_factory( organization_bootstrap_addr) as anonymous_cmds: alice_device = await bootstrap_organization( cmds=anonymous_cmds, human_handle=HumanHandle(label="Alice", email="*****@*****.**"), device_label="laptop", ) save_device_with_password(config_dir, alice_device, password, force=force) config = load_config(config_dir, debug="DEBUG" in os.environ) # Create context manager, alice_client will be needed for the rest of the script async with logged_client_factory(config, alice_device) as alice_client: async with backend_authenticated_cmds_factory( addr=alice_device.organization_addr, device_id=alice_device.device_id, signing_key=alice_device.signing_key, ) as alice_cmds: # Create new device "pc" for Alice other_alice_device = await _register_new_device( cmds=alice_cmds, author=alice_device, device_label="pc") save_device_with_password(config_dir, other_alice_device, password, force=force) # Add additional random device for alice if additional_devices_number > 0: print( f"Adding {additional_devices_number} devices in the test group" ) print(" ... please wait ...") await _add_random_device( cmds=alice_cmds, config_dir=config_dir, force=force, password=password, device=alice_device, additional_devices_number=additional_devices_number, ) print(" Done ") # Invite Bob in organization bob_device = await _register_new_user( cmds=alice_cmds, author=alice_device, device_label="laptop", human_handle=HumanHandle(email="*****@*****.**", label="Bob"), profile=UserProfile.STANDARD, ) save_device_with_password(config_dir, bob_device, password, force=force) # Create Alice workspace alice_ws_id = await alice_client.user_fs.workspace_create( "alice_workspace") # Create context manager async with logged_client_factory(config, bob_device) as bob_client: # Create Bob workspace bob_ws_id = await bob_client.user_fs.workspace_create( "bob_workspace") # Bob share workspace with Alice await bob_client.user_fs.workspace_share( bob_ws_id, alice_device.user_id, WorkspaceRole.MANAGER) # Alice share workspace with Bob await alice_client.user_fs.workspace_share( alice_ws_id, bob_device.user_id, WorkspaceRole.MANAGER) # Add additional random users if additional_users_number > 0: print( f"Adding {additional_users_number} users in the test group" ) print(" ... please wait ...") await _add_random_users( cmds=alice_cmds, author=alice_device, alice_client=alice_client, bob_client=bob_client, alice_ws_id=alice_ws_id, bob_ws_id=bob_ws_id, additional_users_number=additional_users_number, ) print(" Done ") # Synchronize every device for device in (alice_device, other_alice_device, bob_device): async with logged_client_factory(config, device) as client: await client.user_fs.process_last_messages() await client.user_fs.sync() return (alice_device, other_alice_device, bob_device)
def test_password_save_already_existing(config_dir, alice): save_device_with_password(config_dir, alice, "S3Cr37") with pytest.raises(LocalDeviceAlreadyExistsError): save_device_with_password(config_dir, alice, "S3Cr37")
def test_load_bad_password(config_dir, alice): save_device_with_password(config_dir, alice, "S3Cr37") with pytest.raises(LocalDeviceCryptoError): key_file = get_key_file(config_dir, alice) load_device_with_password(key_file, "dummy")
async def test_login_logout_account_list_refresh( aqtbot, gui_factory, autoclose_dialog, client_config, alice, bob ): # Create two devices before starting the gui password = "******" save_device_with_password(client_config.config_dir, alice, password) save_device_with_password(client_config.config_dir, bob, password) gui = await gui_factory() lw = gui.test_get_login_widget() tabw = gui.test_get_tab() acc_w = lw.widget.layout().itemAt(0).widget() assert acc_w # 3 because we have a spacer assert acc_w.accounts_widget.layout().count() == 3 async with aqtbot.wait_signal(acc_w.account_clicked): await aqtbot.mouse_click( acc_w.accounts_widget.layout().itemAt(0).widget(), QtCore.Qt.LeftButton ) def _password_widget_shown(): assert isinstance(lw.widget.layout().itemAt(0).widget(), LoginPasswordInputWidget) await aqtbot.wait_until(_password_widget_shown) password_w = lw.widget.layout().itemAt(0).widget() await aqtbot.key_clicks(password_w.line_edit_password, password) async with aqtbot.wait_signals([lw.login_with_password_clicked, tabw.logged_in]): await aqtbot.mouse_click(password_w.button_login, QtCore.Qt.LeftButton) central_widget = gui.test_get_central_widget() assert central_widget is not None corner_widget = gui.tab_center.cornerWidget(QtCore.Qt.TopLeftCorner) assert isinstance(corner_widget, QtWidgets.QPushButton) assert corner_widget.isVisible() # Now add a new tab await aqtbot.mouse_click(corner_widget, QtCore.Qt.LeftButton) def _switch_to_login_tab(): assert gui.tab_center.count() == 2 gui.tab_center.setCurrentIndex(1) assert isinstance(gui.test_get_login_widget(), LoginWidget) await aqtbot.wait_until(_switch_to_login_tab) acc_w = gui.test_get_login_widget().widget.layout().itemAt(0).widget() assert isinstance(acc_w, LoginPasswordInputWidget) assert not acc_w.button_back.isVisible() def _switch_to_main_tab(): gui.tab_center.setCurrentIndex(0) assert isinstance(gui.test_get_central_widget(), CentralWidget) await aqtbot.wait(200) await aqtbot.wait_until(_switch_to_main_tab) await gui.test_logout() assert gui.tab_center.count() == 1 def _wait_devices_refreshed(): acc_w = gui.test_get_login_widget().widget.layout().itemAt(0).widget() assert acc_w.accounts_widget.layout().count() == 3 await aqtbot.wait_until(_wait_devices_refreshed)
async def test_share_workspace( aqtbot, running_backend, gui_workspace_sharing, autoclose_dialog, client_config, alice, adam, catch_share_workspace_widget, qt_thread_gateway, ): password = "******" save_device_with_password(client_config.config_dir, alice, password) save_device_with_password(client_config.config_dir, adam, password) logged_gui, w_w, share_w_w = gui_workspace_sharing def _users_listed(): assert share_w_w.scroll_content.layout().count() == 4 await aqtbot.wait_until(_users_listed) user_w = share_w_w.scroll_content.layout().itemAt(1).widget() assert user_w.combo_role.currentIndex() == 0 user_name = user_w.user_info.short_user_display def _set_manager(): user_w.combo_role.setCurrentIndex(3) async with aqtbot.wait_signal(share_w_w.share_success, timeout=2500): await qt_thread_gateway.send_action(_set_manager) async with aqtbot.wait_signals( [share_w_w.parent().parent().closing, w_w.list_success], timeout=2500): def _close_dialog(): share_w_w.parent().parent().reject() await qt_thread_gateway.send_action(_close_dialog) autoclose_dialog.reset() def _workspace_listed(): assert w_w.layout_workspaces.count() == 1 wk_button = w_w.layout_workspaces.itemAt(0).widget() assert isinstance(wk_button, WorkspaceButton) assert wk_button.name == "Workspace" assert wk_button.label_title.toolTip( ) == "Workspace (shared with Adamy McAdamFace)" assert wk_button.label_title.text() == "Workspace (shared wi..." assert not autoclose_dialog.dialogs await aqtbot.wait_until(_workspace_listed, timeout=3000) login_w = await logged_gui.test_logout_and_switch_to_login_widget() accounts_w = login_w.widget.layout().itemAt(0).widget() for i in range(accounts_w.accounts_widget.layout().count() - 1): acc_w = accounts_w.accounts_widget.layout().itemAt(i).widget() if acc_w.label_name.text() == user_name: async with aqtbot.wait_signal(accounts_w.account_clicked): await aqtbot.mouse_click(acc_w, QtCore.Qt.LeftButton) break def _password_widget_shown(): assert isinstance(login_w.widget.layout().itemAt(0).widget(), LoginPasswordInputWidget) await aqtbot.wait_until(_password_widget_shown, timeout=4000) password_w = login_w.widget.layout().itemAt(0).widget() await aqtbot.key_clicks(password_w.line_edit_password, password) tabw = logged_gui.test_get_tab() async with aqtbot.wait_signals( [login_w.login_with_password_clicked, tabw.logged_in]): await aqtbot.mouse_click(password_w.button_login, QtCore.Qt.LeftButton) w_w = await logged_gui.test_switch_to_workspaces_widget() def _workspace_listed(): assert w_w.layout_workspaces.count() == 1 wk_button = w_w.layout_workspaces.itemAt(0).widget() assert isinstance(wk_button, WorkspaceButton) assert wk_button.name == "Workspace" assert not autoclose_dialog.dialogs await aqtbot.wait_until(_workspace_listed, timeout=4000) w_b = w_w.layout_workspaces.itemAt(0).widget() assert isinstance(w_b, WorkspaceButton) assert w_b.workspace_name == "Workspace" assert w_b.is_owner is False await aqtbot.mouse_click(w_b.button_share, QtCore.Qt.LeftButton) share_w_w = await catch_share_workspace_widget() def _users_listed(): assert share_w_w.scroll_content.layout().count() == 4 await aqtbot.wait_until(_users_listed) user_w = share_w_w.scroll_content.layout().itemAt(0).widget() assert user_w.combo_role.currentIndex() == 4 assert user_w.isEnabled() is False user_w = share_w_w.scroll_content.layout().itemAt(1).widget() assert user_w.combo_role.currentIndex() == 3 assert user_w.isEnabled() is False user_w = share_w_w.scroll_content.layout().itemAt(2).widget() assert user_w.combo_role.currentIndex() == 0 assert user_w.isEnabled() is True