-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathtest_api.py
More file actions
203 lines (156 loc) · 8.09 KB
/
test_api.py
File metadata and controls
203 lines (156 loc) · 8.09 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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
"""Tests for DigitalOcean API client."""
from unittest.mock import patch
import pytest
from dropkit.api import DigitalOceanAPI
class TestDigitalOceanAPI:
"""Tests for DigitalOceanAPI class."""
def test_sanitize_email_for_username_trailofbits_backwards_compat(self):
"""Test backwards compatibility: trailofbits.com emails still work."""
username = DigitalOceanAPI._sanitize_email_for_username("john.doe@trailofbits.com")
assert username == "john_doe"
def test_sanitize_email_for_username_other_domain(self):
"""Test sanitizing other domain email."""
username = DigitalOceanAPI._sanitize_email_for_username("john.doe@example.com")
assert username == "john_doe"
def test_sanitize_email_for_username_special_chars(self):
"""Test sanitizing email with special characters."""
username = DigitalOceanAPI._sanitize_email_for_username("john-doe+test@trailofbits.com")
assert username == "john_doe_test"
def test_sanitize_email_for_username_starts_with_number(self):
"""Test sanitizing email that starts with number."""
username = DigitalOceanAPI._sanitize_email_for_username("123user@trailofbits.com")
assert username == "u123user"
def test_sanitize_email_for_username_empty_fallback(self):
"""Test sanitizing empty email."""
username = DigitalOceanAPI._sanitize_email_for_username("@trailofbits.com")
assert username == "user"
def test_sanitize_email_for_username_google(self):
"""Test sanitizing google.com email."""
username = DigitalOceanAPI._sanitize_email_for_username("jane.smith@google.com")
assert username == "jane_smith"
def test_sanitize_email_for_username_gmail(self):
"""Test sanitizing gmail.com email."""
username = DigitalOceanAPI._sanitize_email_for_username("user123@gmail.com")
assert username == "user123"
def test_sanitize_email_for_username_corporate(self):
"""Test sanitizing corporate email with subdomain."""
username = DigitalOceanAPI._sanitize_email_for_username("dev.ops@corp.company.com")
assert username == "dev_ops"
def test_sanitize_email_for_username_plus_addressing(self):
"""Test sanitizing email with plus addressing (any domain)."""
username = DigitalOceanAPI._sanitize_email_for_username("user+tag@outlook.com")
assert username == "user_tag"
class TestValidatePositiveInt:
"""Tests for _validate_positive_int method."""
def test_valid_positive_int(self):
"""Test validation passes for positive integer."""
# Should not raise
DigitalOceanAPI._validate_positive_int(1, "test_id")
DigitalOceanAPI._validate_positive_int(100, "test_id")
DigitalOceanAPI._validate_positive_int(999999, "test_id")
def test_zero_raises_error(self):
"""Test validation fails for zero."""
with pytest.raises(ValueError, match="test_id must be a positive integer"):
DigitalOceanAPI._validate_positive_int(0, "test_id")
def test_negative_raises_error(self):
"""Test validation fails for negative integer."""
with pytest.raises(ValueError, match="droplet_id must be a positive integer"):
DigitalOceanAPI._validate_positive_int(-1, "droplet_id")
with pytest.raises(ValueError, match="action_id must be a positive integer"):
DigitalOceanAPI._validate_positive_int(-100, "action_id")
def test_error_message_includes_value(self):
"""Test error message includes the invalid value."""
with pytest.raises(ValueError, match="got: -5"):
DigitalOceanAPI._validate_positive_int(-5, "snapshot_id")
class TestGetDropletUrn:
"""Tests for get_droplet_urn static method."""
def test_urn_format(self):
"""Test URN is in correct format."""
assert DigitalOceanAPI.get_droplet_urn(12345) == "do:droplet:12345"
def test_urn_with_large_id(self):
"""Test URN with large droplet ID."""
assert DigitalOceanAPI.get_droplet_urn(999999999) == "do:droplet:999999999"
class TestListDropletActions:
"""Tests for list_droplet_actions method validation."""
def test_list_droplet_actions_invalid_id_zero(self):
"""Test that list_droplet_actions raises ValueError for zero droplet_id."""
api = DigitalOceanAPI("fake-token")
with pytest.raises(ValueError, match="droplet_id must be a positive integer"):
api.list_droplet_actions(0)
def test_list_droplet_actions_invalid_id_negative(self):
"""Test that list_droplet_actions raises ValueError for negative droplet_id."""
api = DigitalOceanAPI("fake-token")
with pytest.raises(ValueError, match="droplet_id must be a positive integer"):
api.list_droplet_actions(-1)
class TestRenameDroplet:
"""Tests for rename_droplet method validation."""
def test_rename_droplet_invalid_id_zero(self):
"""Test that rename_droplet raises ValueError for zero droplet_id."""
api = DigitalOceanAPI("fake-token")
with pytest.raises(ValueError, match="droplet_id must be a positive integer"):
api.rename_droplet(0, "new-name")
def test_rename_droplet_invalid_id_negative(self):
"""Test that rename_droplet raises ValueError for negative droplet_id."""
api = DigitalOceanAPI("fake-token")
with pytest.raises(ValueError, match="droplet_id must be a positive integer"):
api.rename_droplet(-1, "new-name")
class TestUntagResource:
"""Tests for untag_resource method."""
@patch.object(DigitalOceanAPI, "_request")
def test_untag_resource_calls_delete(self, mock_request):
"""Test that untag_resource sends DELETE with correct payload."""
api = DigitalOceanAPI("fake-token")
api.untag_resource("size:s-1vcpu-1gb", "12345", "image")
mock_request.assert_called_once_with(
"DELETE",
"/tags/size:s-1vcpu-1gb/resources",
json={
"resources": [
{
"resource_id": "12345",
"resource_type": "image",
}
]
},
)
def test_untag_owner_tag_raises(self):
"""Test that untag_resource raises ValueError for owner tags."""
api = DigitalOceanAPI("fake-token")
with pytest.raises(ValueError, match="Cannot remove protected tag: owner:john"):
api.untag_resource("owner:john", "12345", "droplet")
def test_untag_firewall_tag_raises(self):
"""Test that untag_resource raises ValueError for firewall tag."""
api = DigitalOceanAPI("fake-token")
with pytest.raises(ValueError, match="Cannot remove protected tag: firewall"):
api.untag_resource("firewall", "12345", "droplet")
class TestCreateDropletFromSnapshot:
"""Tests for create_droplet_from_snapshot method."""
@patch.object(DigitalOceanAPI, "_request")
def test_with_user_data(self, mock_request):
"""Test that user_data is included in the API payload when provided."""
mock_request.return_value = {"droplet": {"id": 123}}
api = DigitalOceanAPI("fake-token")
api.create_droplet_from_snapshot(
name="test",
region="nyc3",
size="s-1vcpu-1gb",
snapshot_id=456,
tags=["owner:test"],
user_data="#!/bin/bash\nufw allow in on eth0 to any port 22\n",
)
payload = mock_request.call_args[1]["json"]
assert payload["user_data"] == "#!/bin/bash\nufw allow in on eth0 to any port 22\n"
@patch.object(DigitalOceanAPI, "_request")
def test_without_user_data(self, mock_request):
"""Test that user_data is omitted from payload when not provided."""
mock_request.return_value = {"droplet": {"id": 123}}
api = DigitalOceanAPI("fake-token")
api.create_droplet_from_snapshot(
name="test",
region="nyc3",
size="s-1vcpu-1gb",
snapshot_id=456,
tags=["owner:test"],
)
payload = mock_request.call_args[1]["json"]
assert "user_data" not in payload