Skip to content

Commit 69eca40

Browse files
authored
Merge branch 'master' into is468/empty-trash
2 parents 7b12d1e + 7cba3a6 commit 69eca40

File tree

9 files changed

+162
-92
lines changed

9 files changed

+162
-92
lines changed

packages/pytest-simcore/src/pytest_simcore/helpers/playwright.py

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@
1010
from enum import Enum, unique
1111
from typing import Any, Final
1212

13-
import httpx
1413
from playwright._impl._sync_base import EventContextManager
15-
from playwright.sync_api import FrameLocator, Page, Request
14+
from playwright.sync_api import APIRequestContext, FrameLocator, Page, Request
1615
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError
1716
from playwright.sync_api import WebSocket
1817
from pydantic import AnyUrl
@@ -179,7 +178,7 @@ def _attempt_reconnect(self, logger: logging.Logger) -> None:
179178
self.ws.expect_event(event, predicate)
180179

181180
except Exception as e: # pylint: disable=broad-except
182-
logger.error("🚨 Failed to reconnect WebSocket: %s", e)
181+
logger.exception("🚨 Failed to reconnect WebSocket: %s", e)
183182

184183
def expect_event(
185184
self,
@@ -294,6 +293,7 @@ class SocketIONodeProgressCompleteWaiter:
294293
node_id: str
295294
logger: logging.Logger
296295
product_url: AnyUrl
296+
api_request_context: APIRequestContext
297297
_current_progress: dict[NodeProgressType, float] = field(
298298
default_factory=defaultdict
299299
)
@@ -326,7 +326,7 @@ def __call__(self, message: str) -> bool:
326326
self.logger.info(
327327
"Current startup progress [expected number of node-progress-types=%d]: %s",
328328
len(NodeProgressType.required_types_for_started_service()),
329-
f"{json.dumps({k:round(v,1) for k,v in self._current_progress.items()})}",
329+
f"{json.dumps({k: round(v, 2) for k, v in self._current_progress.items()})}",
330330
)
331331

332332
return self.got_expected_node_progress_types() and all(
@@ -337,23 +337,25 @@ def __call__(self, message: str) -> bool:
337337
_current_timestamp = datetime.now(UTC)
338338
if _current_timestamp - self._last_poll_timestamp > timedelta(seconds=5):
339339
url = f"https://{self.node_id}.services.{self.get_partial_product_url()}"
340-
response = httpx.get(url, timeout=10)
341-
self.logger.info(
342-
"Querying the service endpoint from the E2E test. Url: %s Response: %s TIP: %s",
340+
response = self.api_request_context.get(url, timeout=1000)
341+
level = logging.DEBUG
342+
if (response.status >= 400) and (response.status not in (502, 503)):
343+
level = logging.ERROR
344+
self.logger.log(
345+
level,
346+
"Querying service endpoint in case we missed some websocket messages. Url: %s Response: '%s' TIP: %s",
343347
url,
344-
response,
348+
f"{response.status}: {response.text()}",
345349
(
346-
"Response 401 is OK. It means that service is ready."
347-
if response.status_code == 401
348-
else "We are emulating the frontend; a 500 response is acceptable if the service is not yet ready."
350+
"We are emulating the frontend; a 5XX response is acceptable if the service is not yet ready."
349351
),
350352
)
351-
if response.status_code <= 401:
353+
354+
if response.status <= 400:
352355
# NOTE: If the response status is less than 400, it means that the backend is ready (There are some services that respond with a 3XX)
353-
# MD: for now I have included 401 - as this also means that backend is ready
354356
if self.got_expected_node_progress_types():
355357
self.logger.warning(
356-
"⚠️ Progress bar didn't receive 100 percent but service is already running: %s ⚠️", # https://github.com/ITISFoundation/osparc-simcore/issues/6449
358+
"⚠️ Progress bar didn't receive 100 percent but service is already running: %s. TIP: we missed some websocket messages! ⚠️", # https://github.com/ITISFoundation/osparc-simcore/issues/6449
357359
self.get_current_progress(),
358360
)
359361
return True
@@ -408,8 +410,9 @@ def _node_started_predicate(request: Request) -> bool:
408410

409411

410412
def _trigger_service_start(page: Page, node_id: str) -> None:
411-
with log_context(logging.INFO, msg="trigger start button"), page.expect_request(
412-
_node_started_predicate, timeout=35 * SECOND
413+
with (
414+
log_context(logging.INFO, msg="trigger start button"),
415+
page.expect_request(_node_started_predicate, timeout=35 * SECOND),
413416
):
414417
page.get_by_test_id(f"Start_{node_id}").click()
415418

@@ -433,12 +436,14 @@ def expected_service_running(
433436
logging.INFO, msg=f"Waiting for node to run. Timeout: {timeout}"
434437
) as ctx:
435438
waiter = SocketIONodeProgressCompleteWaiter(
436-
node_id=node_id, logger=ctx.logger, product_url=product_url
439+
node_id=node_id,
440+
logger=ctx.logger,
441+
product_url=product_url,
442+
api_request_context=page.request,
437443
)
438444
service_running = ServiceRunning(iframe_locator=None)
439445

440446
try:
441-
442447
with websocket.expect_event("framereceived", waiter, timeout=timeout):
443448
if press_start_button:
444449
_trigger_service_start(page, node_id)
@@ -475,7 +480,10 @@ def wait_for_service_running(
475480
logging.INFO, msg=f"Waiting for node to run. Timeout: {timeout}"
476481
) as ctx:
477482
waiter = SocketIONodeProgressCompleteWaiter(
478-
node_id=node_id, logger=ctx.logger, product_url=product_url
483+
node_id=node_id,
484+
logger=ctx.logger,
485+
product_url=product_url,
486+
api_request_context=page.request,
479487
)
480488
with websocket.expect_event("framereceived", waiter, timeout=timeout):
481489
if press_start_button:

packages/service-library/src/servicelib/archiving_utils/_interface_7zip.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import logging
44
import os
55
import re
6+
import shlex
67
from collections.abc import Awaitable, Callable
78
from contextlib import AsyncExitStack
89
from pathlib import Path
@@ -214,7 +215,7 @@ async def archive_dir(
214215
"-mta=off", # Don't store file access time
215216
]
216217
)
217-
command = f"{_7ZIP_EXECUTABLE} {options} {destination} {dir_to_compress}/*"
218+
command = f"{_7ZIP_EXECUTABLE} {options} {shlex.quote(f'{destination}')} {shlex.quote(f'{dir_to_compress}')}/*"
218219

219220
folder_size_bytes = sum(
220221
file.stat().st_size for file in iter_files_to_compress(dir_to_compress)
@@ -295,7 +296,7 @@ async def unarchive_dir(
295296
# get archive information
296297
archive_info_parser = _7ZipArchiveInfoParser()
297298
list_output = await _run_cli_command(
298-
f"{_7ZIP_EXECUTABLE} l {archive_to_extract}",
299+
f"{_7ZIP_EXECUTABLE} l {shlex.quote(f'{archive_to_extract}')}",
299300
output_handler=archive_info_parser.parse_chunk,
300301
)
301302
file_names_in_archive = _extract_file_names_from_archive(list_output)
@@ -330,7 +331,7 @@ async def _decompressed_bytes(byte_progress: NonNegativeInt) -> None:
330331
]
331332
)
332333
await _run_cli_command(
333-
f"{_7ZIP_EXECUTABLE} {options} {archive_to_extract} -o{destination_folder}",
334+
f"{_7ZIP_EXECUTABLE} {options} {shlex.quote(f'{archive_to_extract}')} -o{shlex.quote(f'{destination_folder}')}",
334335
output_handler=_7ZipProgressParser(_decompressed_bytes).parse_chunk,
335336
)
336337

packages/service-library/tests/archiving_utils/test_archiving__interface_7zip.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from pathlib import Path
66

77
import pytest
8+
from helpers import print_tree
89
from pydantic import NonNegativeInt
910
from servicelib.archiving_utils._interface_7zip import (
1011
_7ZipProgressParser,
@@ -61,12 +62,19 @@ async def _progress_handler(byte_progress: NonNegativeInt) -> None:
6162
assert sum(detected_entries) == expected_size
6263

6364

65+
def _assert_same_folder_content(f1: Path, f2: Path) -> None:
66+
in_f1 = {x.relative_to(f1) for x in f1.rglob("*")}
67+
in_f2 = {x.relative_to(f2) for x in f2.rglob("*")}
68+
assert in_f1 == in_f2
69+
70+
6471
@pytest.mark.parametrize("compress", [True, False])
6572
async def test_archive_unarchive(
6673
mixed_file_types: Path, archive_path: Path, unpacked_archive: Path, compress: bool
6774
):
6875
await archive_dir(mixed_file_types, archive_path, compress=compress)
6976
await unarchive_dir(archive_path, unpacked_archive)
77+
_assert_same_folder_content(mixed_file_types, unpacked_archive)
7078

7179

7280
@pytest.fixture
@@ -82,6 +90,7 @@ async def test_archive_unarchive_empty_folder(
8290
):
8391
await archive_dir(empty_folder, archive_path, compress=compress)
8492
await unarchive_dir(archive_path, unpacked_archive)
93+
_assert_same_folder_content(empty_folder, unpacked_archive)
8594

8695

8796
@pytest.mark.parametrize(
@@ -102,3 +111,27 @@ def test__extract_file_names_from_archive(
102111
archive_list_stdout_path.read_text()
103112
files = _extract_file_names_from_archive(archive_list_stdout_path.read_text())
104113
assert len(files) == expected_file_count
114+
115+
116+
@pytest.mark.parametrize("compress", [True, False])
117+
async def test_archive_unarchive_with_names_with_spaces(tmp_path: Path, compress: bool):
118+
to_archive_path = tmp_path / "'source of files!a ads now strange'"
119+
to_archive_path.mkdir(parents=True, exist_ok=True)
120+
assert to_archive_path.exists()
121+
122+
# generate some content
123+
for i in range(10):
124+
(to_archive_path / f"f{i}.txt").write_text("*" * i)
125+
print_tree(to_archive_path)
126+
127+
archive_path = tmp_path / "archived version herre!)!(/£)!'"
128+
assert not archive_path.exists()
129+
130+
extracted_to_path = tmp_path / "this is where i want them to be extracted to''''"
131+
extracted_to_path.mkdir(parents=True, exist_ok=True)
132+
assert extracted_to_path.exists()
133+
134+
# source and destination all with spaces
135+
await archive_dir(to_archive_path, archive_path, compress=compress)
136+
await unarchive_dir(archive_path, extracted_to_path)
137+
_assert_same_folder_content(to_archive_path, extracted_to_path)

services/static-webserver/client/source/class/osparc/desktop/organizations/MembersList.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,14 @@ qx.Class.define("osparc.desktop.organizations.MembersList", {
137137
const usersStore = osparc.store.Users.getInstance();
138138
selectedMembers.forEach(selectedMemberGId => promises.push(usersStore.getUser(selectedMemberGId)));
139139
Promise.all(promises)
140-
.then(users => {
141-
users.forEach(user => this.__addMember(user.getUsername()));
142-
})
143-
.catch(err => {
144-
console.error(err);
140+
.then(values => {
141+
values.forEach(user => {
142+
if (user) {
143+
this.__addMember(user.getUsername());
144+
}
145+
});
145146
})
147+
.catch(console.error)
146148
.finally(collaboratorsManager.close());
147149
} else {
148150
collaboratorsManager.close();

services/static-webserver/client/source/class/osparc/store/Users.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ qx.Class.define("osparc.store.Users", {
2828
},
2929

3030
members: {
31-
fetchUser: function(groupId) {
31+
__fetchUser: function(groupId) {
3232
const params = {
3333
url: {
3434
gid: groupId
@@ -41,14 +41,22 @@ qx.Class.define("osparc.store.Users", {
4141
});
4242
},
4343

44-
getUser: function(groupId, fetchIfNotFound = true) {
44+
getUser: async function(groupId, fetchIfNotFound = true) {
4545
const userFound = this.getUsers().find(user => user.getGroupId() === groupId);
4646
if (userFound) {
47-
return new Promise(resolve => resolve(userFound));
48-
} else if (fetchIfNotFound) {
49-
return this.fetchUser(groupId);
47+
return userFound;
5048
}
51-
return new Promise(reject => reject());
49+
if (fetchIfNotFound) {
50+
try {
51+
const user = await this.__fetchUser(groupId);
52+
if (user) {
53+
return user;
54+
}
55+
} catch (error) {
56+
console.error(error);
57+
}
58+
}
59+
return null;
5260
},
5361

5462
addUser: function(userData) {
Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
annotated-types==0.7.0
22
# via pydantic
3-
anyio==4.6.2.post1
3+
anyio==4.8.0
44
# via httpx
55
arrow==1.3.0
66
# via -r requirements/_test.in
7-
certifi==2024.8.30
7+
certifi==2024.12.14
88
# via
99
# httpcore
1010
# httpx
1111
# requests
12-
charset-normalizer==3.3.2
12+
charset-normalizer==3.4.1
1313
# via requests
14-
dnspython==2.6.1
14+
dnspython==2.7.0
1515
# via email-validator
1616
docker==7.1.0
1717
# via -r requirements/_test.in
1818
email-validator==2.2.0
1919
# via pydantic
20-
faker==29.0.0
20+
faker==33.3.1
2121
# via -r requirements/_test.in
22-
greenlet==3.0.3
22+
greenlet==3.1.1
2323
# via playwright
2424
h11==0.14.0
2525
# via httpcore
2626
httpcore==1.0.7
2727
# via httpx
28-
httpx==0.27.2
28+
httpx==0.28.1
2929
# via -r requirements/_test.in
3030
idna==3.10
3131
# via
@@ -35,25 +35,25 @@ idna==3.10
3535
# requests
3636
iniconfig==2.0.0
3737
# via pytest
38-
jinja2==3.1.4
38+
jinja2==3.1.5
3939
# via pytest-html
40-
markupsafe==2.1.5
40+
markupsafe==3.0.2
4141
# via jinja2
42-
packaging==24.1
42+
packaging==24.2
4343
# via
4444
# pytest
4545
# pytest-sugar
46-
playwright==1.47.0
46+
playwright==1.49.1
4747
# via pytest-playwright
4848
pluggy==1.5.0
4949
# via pytest
50-
pydantic==2.10.3
50+
pydantic==2.10.5
5151
# via -r requirements/_test.in
52-
pydantic-core==2.27.1
52+
pydantic-core==2.27.2
5353
# via pydantic
5454
pyee==12.0.0
5555
# via playwright
56-
pytest==8.3.3
56+
pytest==8.3.4
5757
# via
5858
# pytest-base-url
5959
# pytest-html
@@ -69,7 +69,7 @@ pytest-instafail==0.5.0
6969
# via -r requirements/_test.in
7070
pytest-metadata==3.1.1
7171
# via pytest-html
72-
pytest-playwright==0.5.2
72+
pytest-playwright==0.6.2
7373
# via -r requirements/_test.in
7474
pytest-runner==6.0.1
7575
# via -r requirements/_test.in
@@ -87,26 +87,26 @@ requests==2.32.3
8787
# via
8888
# docker
8989
# pytest-base-url
90-
six==1.16.0
90+
six==1.17.0
9191
# via python-dateutil
9292
sniffio==1.3.1
93-
# via
94-
# anyio
95-
# httpx
93+
# via anyio
9694
tenacity==9.0.0
9795
# via -r requirements/_test.in
98-
termcolor==2.4.0
96+
termcolor==2.5.0
9997
# via pytest-sugar
10098
text-unidecode==1.3
10199
# via python-slugify
102-
types-python-dateutil==2.9.0.20240906
100+
types-python-dateutil==2.9.0.20241206
103101
# via arrow
104102
typing-extensions==4.12.2
105103
# via
104+
# anyio
105+
# faker
106106
# pydantic
107107
# pydantic-core
108108
# pyee
109-
urllib3==2.2.3
109+
urllib3==2.3.0
110110
# via
111111
# docker
112112
# requests

0 commit comments

Comments
 (0)