Skip to content

Commit a0d454e

Browse files
new bulk list test
1 parent 918305e commit a0d454e

2 files changed

Lines changed: 153 additions & 0 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__pycache__
2+
databunkerpro.egg-info

tests/test_bulk_list_users.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"""
2+
Tests for BulkListUsers — the batch lookup used to reconcile a migration.
3+
4+
Verifies the request shape the SDK sends, that lookups by `custom` resolve back to
5+
the tokens returned at creation time, and that the documented `limit` default of 10
6+
is not applied to this endpoint.
7+
"""
8+
9+
import os
10+
import random
11+
import unittest
12+
13+
import requests
14+
15+
from databunkerpro import DatabunkerproAPI
16+
17+
# Deliberately more than the `limit: 10` default documented for this endpoint.
18+
USER_COUNT = 12
19+
20+
21+
class TestBulkListUsers(unittest.TestCase):
22+
"""BulkListUsers batch lookup by indexed field."""
23+
24+
@classmethod
25+
def setUpClass(cls):
26+
cls.api_url = os.getenv("DATABUNKER_API_URL", "https://pro.databunker.org")
27+
cls.api_token = os.getenv("DATABUNKER_API_TOKEN", "")
28+
cls.tenant_name = os.getenv("DATABUNKER_TENANT_NAME", "")
29+
30+
if not all([cls.api_token, cls.tenant_name]):
31+
try:
32+
response = requests.get(
33+
"https://databunker.org/api/newtenant.php", verify=False
34+
)
35+
data = response.json() if response.ok else None
36+
if not data or data.get("status") != "ok":
37+
raise unittest.SkipTest("Failed to get credentials from sandbox")
38+
cls.tenant_name = data["tenantname"]
39+
cls.api_token = data["xtoken"]
40+
print(f"\nSandbox tenant: {cls.tenant_name} at {cls.api_url}")
41+
except unittest.SkipTest:
42+
raise
43+
except Exception as e:
44+
raise unittest.SkipTest(f"Failed to get credentials: {e}")
45+
46+
cls.api = DatabunkerproAPI(cls.api_url, cls.api_token, cls.tenant_name)
47+
48+
# Create users the way a migration does: the legacy primary key in `custom`.
49+
cls.run_id = random.randint(100000, 999999)
50+
records = [
51+
{
52+
"profile": {
53+
"custom": f"legacy-{cls.run_id}-{i}",
54+
"email": f"blu{cls.run_id}x{i}@example.com",
55+
"name": f"Bulk List User {i}",
56+
}
57+
}
58+
for i in range(USER_COUNT)
59+
]
60+
result = cls.api.create_users_bulk(records)
61+
if result.get("status") != "ok":
62+
raise unittest.SkipTest(f"Setup bulk create failed: {result}")
63+
64+
# custom value -> token, as returned by UserCreateBulk
65+
cls.expected = {
66+
item["profile"]["custom"]: item["token"] for item in result["created"]
67+
}
68+
print(f"Created {len(cls.expected)}/{USER_COUNT} users for lookup")
69+
70+
def _unlock(self):
71+
result = self.api.bulk_list_unlock()
72+
self.assertEqual(result.get("status"), "ok", f"unlock failed: {result}")
73+
self.assertIn("unlockuuid", result)
74+
return result["unlockuuid"]
75+
76+
def _list_users(self, criteria, unlock_uuid=None):
77+
"""BulkListUsers, skipping when the server has the feature turned off.
78+
79+
BulkListUsers is gated by the `list_users` configuration flag, which is off by
80+
default. Skipping rather than failing keeps a server-side setting from breaking
81+
the build — and, since `deploy` needs `test`, from blocking a release.
82+
"""
83+
if unlock_uuid is None:
84+
unlock_uuid = self._unlock()
85+
result = self.api.bulk_list_users(unlock_uuid, criteria)
86+
if result.get("message") == "BulkListUsers is disabled":
87+
self.skipTest("list_users is not enabled on this server")
88+
return result
89+
90+
def test_bulk_list_unlock(self):
91+
"""BulkListUnlock issues a UUID."""
92+
self.assertTrue(self._unlock())
93+
94+
def test_bulk_list_users_by_custom(self):
95+
"""Every created user is found by its `custom` value, with a matching token."""
96+
criteria = [{"mode": "custom", "identity": c} for c in self.expected]
97+
result = self._list_users(criteria)
98+
99+
self.assertEqual(result.get("status"), "ok", f"unexpected response: {result}")
100+
rows = result.get("rows") or []
101+
self.assertTrue(rows, f"no rows returned: {result}")
102+
103+
found = {
104+
row["profile"]["custom"]: row["token"]
105+
for row in rows
106+
if row.get("profile", {}).get("custom")
107+
}
108+
self.assertEqual(
109+
found,
110+
self.expected,
111+
"token mapping from BulkListUsers does not match UserCreateBulk",
112+
)
113+
114+
def test_limit_default_is_not_applied(self):
115+
"""More than 10 criteria must all come back — `limit` is not honoured here."""
116+
criteria = [{"mode": "custom", "identity": c} for c in self.expected]
117+
self.assertGreater(len(criteria), 10, "test needs >10 records to be meaningful")
118+
119+
result = self._list_users(criteria)
120+
rows = result.get("rows") or []
121+
122+
self.assertEqual(
123+
len(rows),
124+
len(self.expected),
125+
f"expected {len(self.expected)} rows, got {len(rows)} — "
126+
f"limit appears to be applied (total={result.get('total')})",
127+
)
128+
129+
def test_unknown_identity_returns_no_row(self):
130+
"""A `custom` value that was never stored yields nothing, not an error."""
131+
result = self._list_users(
132+
[{"mode": "custom", "identity": f"legacy-{self.run_id}-absent"}]
133+
)
134+
self.assertEqual(result.get("status"), "ok", f"unexpected response: {result}")
135+
self.assertEqual(len(result.get("rows") or []), 0)
136+
137+
def test_invalid_unlockuuid_is_rejected(self):
138+
"""unlockuuid is enforced — a bogus UUID must not return data."""
139+
result = self._list_users(
140+
[{"mode": "custom", "identity": next(iter(self.expected))}],
141+
unlock_uuid="00000000-0000-0000-0000-000000000000",
142+
)
143+
self.assertNotEqual(
144+
result.get("status"),
145+
"ok",
146+
f"a bogus unlockuuid returned data: {result}",
147+
)
148+
149+
150+
if __name__ == "__main__":
151+
unittest.main()

0 commit comments

Comments
 (0)