Skip to content

Commit 52c721f

Browse files
update tests
1 parent 1c1c3ad commit 52c721f

File tree

4 files changed

+236
-51
lines changed

4 files changed

+236
-51
lines changed

src/onepasswordconnectsdk/config.py

+19-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import List, Dict, Optional
44
import httpx
55
from onepasswordconnectsdk.client import Client
6-
from onepasswordconnectsdk.utils import get_timeout
6+
from onepasswordconnectsdk.utils import get_timeout, build_headers
77

88
class ClientConfig(httpx.Client):
99
"""Configuration class for 1Password Connect synchronous clients"""
@@ -22,6 +22,15 @@ def __init__(
2222
"""
2323
if 'timeout' not in kwargs:
2424
kwargs['timeout'] = get_timeout()
25+
elif kwargs['timeout'] is None:
26+
# Pass None directly to disable timeouts
27+
kwargs['timeout'] = None
28+
29+
# Merge custom headers with authorization header
30+
auth_headers = build_headers(token)
31+
if 'headers' in kwargs:
32+
auth_headers.update(kwargs['headers'])
33+
kwargs['headers'] = auth_headers
2534

2635
super().__init__(base_url=url, **kwargs)
2736
self.url = url
@@ -45,6 +54,15 @@ def __init__(
4554
"""
4655
if 'timeout' not in kwargs:
4756
kwargs['timeout'] = get_timeout()
57+
elif kwargs['timeout'] is None:
58+
# Pass None directly to disable timeouts
59+
kwargs['timeout'] = None
60+
61+
# Merge custom headers with authorization header
62+
auth_headers = build_headers(token)
63+
if 'headers' in kwargs:
64+
auth_headers.update(kwargs['headers'])
65+
kwargs['headers'] = auth_headers
4866

4967
super().__init__(base_url=url, **kwargs)
5068
self.url = url

src/tests/test_client_items.py

+56-45
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,24 @@
22
import pytest
33
from unittest import mock
44

5-
from httpx import Response
5+
from httpx import Response, Timeout
66
from onepasswordconnectsdk import async_client, client, models
7+
from onepasswordconnectsdk.config import ClientConfig, AsyncClientConfig
8+
from onepasswordconnectsdk.utils import get_timeout, ENV_CLIENT_REQUEST_TIMEOUT
79

810
VAULT_ID = "hfnjvi6aymbsnfc2xeeoheizda"
911
VAULT_TITLE = "VaultA"
1012
ITEM_ID = "wepiqdxdzncjtnvmv5fegud4qy"
1113
ITEM_TITLE = "Test Login"
1214
HOST = "https://mock_host"
1315
TOKEN = "jwt_token"
14-
SS_CLIENT = client.new_client(HOST, TOKEN)
15-
SS_CLIENT_ASYNC = async_client.new_async_client(HOST, TOKEN)
16+
17+
# Create clients using new config pattern
18+
client_config = ClientConfig(url=HOST, token=TOKEN)
19+
SS_CLIENT = client.Client(client_config)
20+
21+
async_config = AsyncClientConfig(url=HOST, token=TOKEN)
22+
SS_CLIENT_ASYNC = async_client.AsyncClient(async_config)
1623

1724

1825
def test_get_item_by_id(respx_mock):
@@ -446,54 +453,58 @@ def generate_full_item():
446453

447454

448455
def test_default_timeout():
449-
client_instance = client.new_client(HOST, TOKEN)
450-
assert client_instance.session.timeout.read == DEFAULT_TIMEOUT_CONFIG.read
456+
"""Test default timeout is used when not specified"""
457+
config = ClientConfig(url=HOST, token=TOKEN)
458+
client_instance = client.Client(config)
459+
assert client_instance.session.timeout == get_timeout()
451460

452461

453-
def test_set_timeout_using_env_variable():
454-
with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: '120'}):
455-
client_instance = client.new_client(HOST, TOKEN)
456-
assert client_instance.session.timeout.read == 120
462+
def test_custom_timeout():
463+
"""Test custom timeout is properly set"""
464+
custom_timeout = 120.0
465+
config = ClientConfig(url=HOST, token=TOKEN, timeout=custom_timeout)
466+
client_instance = client.Client(config)
467+
assert client_instance.session.timeout == Timeout(custom_timeout)
457468

458469

459470
@pytest.mark.asyncio
460-
def test_set_timeout_using_env_variable_async():
461-
with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: '120'}):
462-
client_instance = client.new_client(HOST, TOKEN, is_async=True)
463-
assert client_instance.session.timeout.read == 120
464-
465-
466-
def test_disable_all_timeouts():
467-
with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: 'None'}):
468-
client_instance = client.new_client(HOST, TOKEN)
469-
assert client_instance.session.timeout.read is None
470-
471+
async def test_async_custom_timeout():
472+
"""Test custom timeout is properly set for async client"""
473+
custom_timeout = 120.0
474+
config = AsyncClientConfig(url=HOST, token=TOKEN, timeout=custom_timeout)
475+
client_instance = async_client.AsyncClient(config)
476+
assert client_instance.session.timeout == Timeout(custom_timeout)
471477

472-
def test_env_client_request_timeout_env_var_is_empty_string():
473-
with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: ''}):
474-
client_instance = client.new_client(HOST, TOKEN)
475-
assert client_instance.session.timeout.read == DEFAULT_TIMEOUT_CONFIG.read
476478

479+
def test_disable_timeout():
480+
"""Test timeout can be disabled"""
481+
config = ClientConfig(url=HOST, token=TOKEN, timeout=None)
482+
client_instance = client.Client(config)
483+
assert isinstance(client_instance.session.timeout, Timeout)
484+
assert client_instance.session.timeout == Timeout(None)
477485

478-
def test_env_client_request_timeout_env_var_is_single_space_string():
479-
with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: ' '}):
480-
client_instance = client.new_client(HOST, TOKEN)
481-
assert client_instance.session.timeout.read == DEFAULT_TIMEOUT_CONFIG.read
482486

483-
484-
def test_env_client_request_timeout_env_var_is_not_numeric_string():
485-
with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: 'abc'}):
486-
client_instance = client.new_client(HOST, TOKEN)
487-
assert client_instance.session.timeout.read == DEFAULT_TIMEOUT_CONFIG.read
488-
489-
490-
def test_env_client_request_timeout_env_var_is_zero():
491-
with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: '0'}):
492-
client_instance = client.new_client(HOST, TOKEN)
493-
assert client_instance.session.timeout.read == DEFAULT_TIMEOUT_CONFIG.read
494-
495-
496-
def test_env_client_request_timeout_env_var_is_negative_number():
497-
with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: '-10'}):
498-
client_instance = client.new_client(HOST, TOKEN)
499-
assert client_instance.session.timeout.read == DEFAULT_TIMEOUT_CONFIG.read
487+
def test_timeout_from_env():
488+
"""Test timeout from environment variable"""
489+
with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: '120'}):
490+
config = ClientConfig(url=HOST, token=TOKEN)
491+
client_instance = client.Client(config)
492+
assert client_instance.session.timeout == Timeout(120.0)
493+
494+
495+
def test_invalid_timeout_from_env():
496+
"""Test invalid timeout values from environment fall back to default"""
497+
test_cases = [
498+
'', # empty string
499+
' ', # space
500+
'abc', # non-numeric
501+
'0', # zero
502+
'-10', # negative
503+
'None', # string None
504+
]
505+
506+
for test_value in test_cases:
507+
with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: test_value}):
508+
config = ClientConfig(url=HOST, token=TOKEN)
509+
client_instance = client.Client(config)
510+
assert client_instance.session.timeout == get_timeout(), f"Failed for value: {test_value}"

src/tests/test_client_vaults.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import pytest
22
from httpx import Response
3-
from onepasswordconnectsdk import async_client, client
3+
from onepasswordconnectsdk import client, async_client
4+
from onepasswordconnectsdk.config import ClientConfig, AsyncClientConfig
45

56
VAULT_ID = "hfnjvi6aymbsnfc2xeeoheizda"
67
VAULT_NAME = "VaultA"
78
HOST = "https://mock_host"
89
TOKEN = "jwt_token"
9-
SS_CLIENT = client.new_client(HOST, TOKEN)
10-
SS_CLIENT_ASYNC = async_client.new_async_client(HOST, TOKEN)
10+
# Create clients using new config pattern
11+
client_config = ClientConfig(url=HOST, token=TOKEN)
12+
SS_CLIENT = client.Client(client_config)
13+
14+
async_config = AsyncClientConfig(url=HOST, token=TOKEN)
15+
SS_CLIENT_ASYNC = async_client.AsyncClient(async_config)
1116

1217

1318
def test_get_vaults(respx_mock):

src/tests/test_config.py

+153-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
from httpx import Response
1+
import os
2+
import pytest
3+
from httpx import Response, Client, AsyncClient, Timeout
24
import onepasswordconnectsdk
35
from onepasswordconnectsdk import client
6+
from onepasswordconnectsdk.config import ClientConfig, AsyncClientConfig
7+
from onepasswordconnectsdk.utils import get_timeout
8+
9+
# Optional cert path for SSL verification in tests
10+
# Set this to a valid cert path if available, otherwise verification will be disabled
11+
CERT_PATH = os.getenv('TEST_CERT_PATH', False)
412

513
VAULT_ID = "abcdefghijklmnopqrstuvwxyz"
614
ITEM_NAME1 = "TEST USER"
@@ -9,12 +17,119 @@
917
ITEM_ID2 = "wepiqdxdzncjtnvmv5fegud4q2"
1018
HOST = "https://mock_host"
1119
TOKEN = "jwt_token"
12-
SS_CLIENT = client.new_client(HOST, TOKEN)
20+
21+
# Create client config
22+
client_config = ClientConfig(url=HOST, token=TOKEN)
23+
SS_CLIENT = client.Client(client_config)
1324

1425
USERNAME_VALUE = "new_user"
1526
PASSWORD_VALUE = "password"
1627
HOST_VALUE = "http://somehost"
1728

29+
def test_client_config_initialization():
30+
"""Test ClientConfig initialization and inheritance"""
31+
config = ClientConfig(url=HOST, token=TOKEN)
32+
assert isinstance(config, Client)
33+
assert config.url == HOST
34+
assert config.token == TOKEN
35+
assert config.timeout == get_timeout()
36+
37+
def test_async_client_config_initialization():
38+
"""Test AsyncClientConfig initialization and inheritance"""
39+
config = AsyncClientConfig(url=HOST, token=TOKEN)
40+
assert isinstance(config, AsyncClient)
41+
assert config.url == HOST
42+
assert config.token == TOKEN
43+
assert config.timeout == get_timeout()
44+
45+
def test_client_config_with_options():
46+
"""Test ClientConfig with httpx options
47+
48+
Examples of certificate configuration:
49+
# Using a certificate file
50+
config = ClientConfig(url=HOST, token=TOKEN, verify="path/to/cert.pem")
51+
52+
# Using a certificate bundle
53+
config = ClientConfig(url=HOST, token=TOKEN, verify="/etc/ssl/certs")
54+
55+
# Disable certificate verification (not recommended for production)
56+
config = ClientConfig(url=HOST, token=TOKEN, verify=False)
57+
58+
# Custom client certificate
59+
config = ClientConfig(
60+
url=HOST,
61+
token=TOKEN,
62+
cert=("path/to/client.crt", "path/to/client.key")
63+
)
64+
"""
65+
# Test basic options
66+
custom_timeout = 30.0
67+
config = ClientConfig(
68+
url=HOST,
69+
token=TOKEN,
70+
timeout=custom_timeout,
71+
verify=CERT_PATH, # Use configured cert path or disable verification
72+
cert=None, # Client certificate (if needed)
73+
follow_redirects=True
74+
)
75+
assert isinstance(config.timeout, Timeout)
76+
assert config.timeout == Timeout(custom_timeout)
77+
# If we got here without an error, the verify parameter was accepted
78+
assert config.follow_redirects is True
79+
80+
81+
# Just verify that these configurations are accepted without error
82+
ClientConfig(
83+
url=HOST,
84+
token=TOKEN,
85+
verify=False # Disable SSL verification
86+
)
87+
88+
89+
90+
def test_async_client_config_with_options():
91+
"""Test AsyncClientConfig with httpx options
92+
93+
Examples of certificate configuration:
94+
# Using a certificate file
95+
config = AsyncClientConfig(url=HOST, token=TOKEN, verify="path/to/cert.pem")
96+
97+
# Using a certificate bundle
98+
config = AsyncClientConfig(url=HOST, token=TOKEN, verify="/etc/ssl/certs")
99+
100+
# Disable certificate verification (not recommended for production)
101+
config = AsyncClientConfig(url=HOST, token=TOKEN, verify=False)
102+
103+
# Custom client certificate
104+
config = AsyncClientConfig(
105+
url=HOST,
106+
token=TOKEN,
107+
cert=("path/to/client.crt", "path/to/client.key")
108+
)
109+
"""
110+
custom_timeout = 30.0
111+
config = AsyncClientConfig(
112+
url=HOST,
113+
token=TOKEN,
114+
timeout=custom_timeout,
115+
verify=CERT_PATH, # Use configured cert path or disable verification
116+
cert=None, # Client certificate (if needed)
117+
follow_redirects=True
118+
)
119+
assert isinstance(config.timeout, Timeout)
120+
assert config.timeout == Timeout(custom_timeout)
121+
# If we got here without an error, the verify parameter was accepted
122+
assert config.follow_redirects is True
123+
124+
# Just verify that these configurations are accepted without error
125+
AsyncClientConfig(
126+
url=HOST,
127+
token=TOKEN,
128+
verify=False # Disable SSL verification
129+
)
130+
131+
# Note: Client certificate tests are skipped as they require actual certificate files
132+
18133

19134
class Config:
20135
username: f'opitem:"{ITEM_NAME1}" opfield:.username opvault:{VAULT_ID}' = None
@@ -25,6 +140,42 @@ class Config:
25140
CONFIG_CLASS = Config()
26141

27142

143+
def test_client_config_errors():
144+
"""Test ClientConfig error cases"""
145+
with pytest.raises(TypeError):
146+
ClientConfig() # Missing required arguments
147+
148+
with pytest.raises(TypeError):
149+
ClientConfig(url=HOST) # Missing token
150+
151+
with pytest.raises(TypeError):
152+
ClientConfig(token=TOKEN) # Missing url
153+
154+
def test_async_client_config_errors():
155+
"""Test AsyncClientConfig error cases"""
156+
with pytest.raises(TypeError):
157+
AsyncClientConfig() # Missing required arguments
158+
159+
with pytest.raises(TypeError):
160+
AsyncClientConfig(url=HOST) # Missing token
161+
162+
with pytest.raises(TypeError):
163+
AsyncClientConfig(token=TOKEN) # Missing url
164+
165+
def test_client_config_headers():
166+
"""Test that client config properly handles headers"""
167+
config = ClientConfig(
168+
url=HOST,
169+
token=TOKEN,
170+
headers={"Custom-Header": "test"}
171+
)
172+
# Headers should be merged, not overwritten
173+
assert "Custom-Header" in config.headers
174+
assert config.headers["Custom-Header"] == "test"
175+
# Authorization header should still be present
176+
assert "Authorization" in config.headers
177+
178+
28179
def test_load(respx_mock):
29180
mock_items_list1 = respx_mock.get(f"v1/vaults/{VAULT_ID}/items?filter=title eq \"{ITEM_NAME1}\"").mock(
30181
return_value=Response(200, json=[item])

0 commit comments

Comments
 (0)