-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_core_initialization.py
More file actions
148 lines (102 loc) · 4.7 KB
/
Copy pathtest_core_initialization.py
File metadata and controls
148 lines (102 loc) · 4.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""Story 1.2 — Core initialization tests (SDK-3).
Covers ``Core`` initialization from either direct config data or an ``sdkKey``,
authoritative readiness state, and immutable snapshot access:
* Direct-config init does NOT invoke transport and stores an immutable snapshot
(AC #1).
* sdkKey init fetches config through the transport path and stores the snapshot
(AC #2).
* Malformed config and transport failures raise typed, diagnosable errors and
the Core does not become ready (AC #3).
* A non-HTTPS transport raises a typed error before any I/O (AC #4 / NFR8).
Transport is exercised through a lightweight in-test fake implementing the
transport protocol, so these tests stay at the Core/orchestration layer and do
not depend on real HTTP. (Route-shape parity is asserted in
``test_httpx_transport.py``.)
"""
import pytest
from convert_sdk import Core, SDKConfig, TransportConfig
from convert_sdk.domain.config_snapshot import ConfigSnapshot
from convert_sdk.errors import ConfigLoadError, InvalidConfigError, TransportError
VALID_CONFIG = {
"account_id": "100123",
"project": {"id": "200456"},
"experiences": [{"id": "e1", "key": "exp-one", "variations": []}],
"features": [],
}
class _RecordingTransport:
"""Fake transport implementing the protocol; records whether it was called."""
def __init__(self, *, response=None, error=None):
self._response = response if response is not None else VALID_CONFIG
self._error = error
self.fetch_calls = 0
self.closed = False
def fetch_config(self, config):
self.fetch_calls += 1
if self._error is not None:
raise self._error
return self._response
def close(self):
self.closed = True
def __enter__(self):
return self
def __exit__(self, *exc):
self.close()
return False
# --- Direct-config initialization (AC #1) ---------------------------------
def test_direct_config_init_does_not_invoke_transport():
transport = _RecordingTransport()
core = Core(SDKConfig(data=VALID_CONFIG), transport=transport)
core.initialize()
assert core.is_ready is True
assert transport.fetch_calls == 0
def test_direct_config_stores_immutable_snapshot():
core = Core(SDKConfig(data=VALID_CONFIG))
core.initialize()
snap = core.current_config
assert isinstance(snap, ConfigSnapshot)
assert snap.account_id == "100123"
with pytest.raises(Exception):
snap.account_id = "tampered" # type: ignore[misc]
def test_not_ready_before_initialize():
core = Core(SDKConfig(data=VALID_CONFIG))
assert core.is_ready is False
assert core.current_config is None
# --- sdkKey initialization (AC #2) ----------------------------------------
def test_sdk_key_init_fetches_through_transport():
transport = _RecordingTransport(response=VALID_CONFIG)
core = Core(SDKConfig(sdk_key="sdkkey123"), transport=transport)
core.initialize()
assert transport.fetch_calls == 1
assert core.is_ready is True
assert core.current_config.account_id == "100123"
# --- Failure paths (AC #3) ------------------------------------------------
def test_malformed_direct_config_raises_typed_error_and_stays_not_ready():
core = Core(SDKConfig(data={"experiences": []})) # missing account_id/project
with pytest.raises(InvalidConfigError):
core.initialize()
assert core.is_ready is False
assert core.current_config is None
def test_transport_failure_raises_config_load_error():
transport = _RecordingTransport(
error=ConfigLoadError("config fetch failed", endpoint="https://x/config/k", status_code=503)
)
core = Core(SDKConfig(sdk_key="sdkkey123"), transport=transport)
with pytest.raises(ConfigLoadError):
core.initialize()
assert core.is_ready is False
def test_malformed_fetched_config_raises_typed_error():
transport = _RecordingTransport(response={"experiences": []}) # invalid shape
core = Core(SDKConfig(sdk_key="sdkkey123"), transport=transport)
with pytest.raises(InvalidConfigError):
core.initialize()
assert core.is_ready is False
# --- TLS-only (AC #4 / NFR8) ----------------------------------------------
def test_non_https_transport_config_raises_before_any_io():
"""The typed error fires at TransportConfig construction — there is no path
to reach Core.initialize() with an insecure transport."""
with pytest.raises(TransportError):
SDKConfig(sdk_key="k", transport=TransportConfig(base_url="http://nope"))
# --- Frozen Story 1.1 boundary --------------------------------------------
def test_core_importable_from_frozen_boundary():
from convert_sdk import Context, Core, __version__ # noqa: F401
assert Core is not None