Skip to content

Commit e5b43ea

Browse files
kikkomepEttoreM
authored andcommitted
test(http): add unit tests
1 parent 61ae512 commit e5b43ea

1 file changed

Lines changed: 154 additions & 0 deletions

File tree

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# Copyright (c) 2024-2026 CRS4
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from __future__ import annotations
16+
17+
from unittest.mock import MagicMock
18+
19+
import pytest
20+
21+
from rocrate_validator.utils.http import HttpRequester
22+
23+
24+
@pytest.fixture(autouse=True)
25+
def _reset_singleton():
26+
HttpRequester.reset()
27+
yield
28+
HttpRequester.reset()
29+
30+
31+
def _initialize(cache_path, offline=False, cache_max_age=-1):
32+
return HttpRequester.initialize_cache(
33+
cache_path=str(cache_path),
34+
cache_max_age=cache_max_age,
35+
offline=offline,
36+
)
37+
38+
39+
def _fake_session(status_code=200):
40+
"""A session-like mock whose ``get`` returns a sentinel response."""
41+
session = MagicMock()
42+
response = MagicMock(status_code=status_code, from_cache=False)
43+
session.get.return_value = response
44+
return session, response
45+
46+
47+
def test_initialize_cache_creates_instance_when_absent(tmp_path):
48+
assert HttpRequester._instance is None
49+
requester = _initialize(tmp_path / "cache")
50+
assert isinstance(requester, HttpRequester)
51+
assert HttpRequester._instance is requester
52+
53+
54+
def test_initialize_cache_reuses_existing_instance(tmp_path):
55+
first = _initialize(tmp_path / "cache-1")
56+
second = _initialize(tmp_path / "cache-2")
57+
# The singleton is reconfigured in place rather than recreated.
58+
assert second is first
59+
60+
61+
def test_reconfigure_applies_new_settings(tmp_path):
62+
requester = _initialize(tmp_path / "cache", offline=False, cache_max_age=60)
63+
assert requester.offline is False
64+
65+
same = _initialize(tmp_path / "cache", offline=True, cache_max_age=-1)
66+
assert same is requester
67+
assert same.offline is True
68+
# Offline mode is enforced on the freshly rebuilt session.
69+
assert getattr(same.session.settings, "only_if_cached", False) is True
70+
71+
72+
def test_reconfigure_rebuilds_underlying_session(tmp_path):
73+
requester = _initialize(tmp_path / "cache-1", cache_max_age=60)
74+
old_session = requester.session
75+
_initialize(tmp_path / "cache-2", cache_max_age=60)
76+
assert requester.session is not old_session
77+
78+
79+
def test_reconfigure_preserves_instance_attributes(tmp_path):
80+
"""Regression: reconfiguring the cache must not discard state set on the
81+
singleton (e.g. methods patched by tests)."""
82+
requester = _initialize(tmp_path / "cache-1", cache_max_age=60)
83+
sentinel = object()
84+
requester.custom_marker = sentinel
85+
86+
_initialize(tmp_path / "cache-2", cache_max_age=60)
87+
88+
assert requester.custom_marker is sentinel
89+
90+
91+
def test_method_wrapper_targets_current_session(tmp_path):
92+
"""The ``__getattr__`` HTTP wrappers resolve the session at call time, so a
93+
wrapper obtained before a session swap still hits the live session."""
94+
requester = _initialize(tmp_path / "cache", cache_max_age=60)
95+
96+
first_session, _ = _fake_session()
97+
requester.session = first_session
98+
wrapper = requester.get # captured before swapping the session
99+
100+
second_session, expected = _fake_session(status_code=201)
101+
requester.session = second_session
102+
103+
result = wrapper("https://example.org/x")
104+
105+
assert result is expected
106+
second_session.get.assert_called_once()
107+
first_session.get.assert_not_called()
108+
109+
110+
def test_pinned_wrapper_survives_reconfigure(tmp_path):
111+
"""Mimics how ``pytest.monkeypatch`` teardown leaves a method wrapper pinned
112+
as an instance attribute: after a reconfigure rebuilds the session, that
113+
wrapper must still target the live session, not a closed one."""
114+
requester = _initialize(tmp_path / "cache-1", cache_max_age=60)
115+
requester.get = requester.get # pin the wrapper as an instance attribute
116+
117+
_initialize(tmp_path / "cache-2", cache_max_age=60) # rebuilds the session
118+
119+
mock_session, expected = _fake_session()
120+
requester.session = mock_session
121+
122+
result = requester.get("https://example.org/x")
123+
124+
assert result is expected
125+
mock_session.get.assert_called_once()
126+
127+
128+
def test_reset_drops_instance(tmp_path):
129+
requester = _initialize(tmp_path / "cache", cache_max_age=60)
130+
HttpRequester.reset()
131+
assert HttpRequester._instance is None
132+
# A subsequent initialization yields a brand-new instance.
133+
assert _initialize(tmp_path / "cache", cache_max_age=60) is not requester
134+
135+
136+
def test_validation_settings_preserves_singleton(tmp_path):
137+
"""Constructing ``ValidationSettings`` reconfigures the cache in place and
138+
must not drop the existing requester (nor any state held on it)."""
139+
from rocrate_validator.models import ValidationSettings
140+
from rocrate_validator.utils.uri import URI
141+
142+
requester = _initialize(tmp_path / "cache", cache_max_age=60)
143+
marker = object()
144+
requester.custom_marker = marker
145+
146+
# ``offline=True`` keeps the construction self-contained (no warm-up/network).
147+
ValidationSettings(
148+
rocrate_uri=URI("."),
149+
offline=True,
150+
cache_path=tmp_path / "cache",
151+
)
152+
153+
assert HttpRequester._instance is requester
154+
assert requester.custom_marker is marker

0 commit comments

Comments
 (0)