Skip to content

Commit 7996320

Browse files
authored
Migrated the 2 ttl based tests (#312)
* Clean TTL migration changes * cleaner use_public_api + using the same in the first test
1 parent 1794b4e commit 7996320

3 files changed

Lines changed: 289 additions & 44 deletions

File tree

client/src/cbltest/api/syncgateway.py

Lines changed: 35 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,27 @@ async def _send_request(
538538
payload: JSONSerializable | None = None,
539539
params: dict[str, str] | None = None,
540540
session: ClientSession | None = None,
541+
use_public_api: bool = False,
541542
) -> Any:
543+
if use_public_api and session is None:
544+
# Use public port (4984) with current session's credentials
545+
scheme = "https://" if self.__secure else "http://"
546+
async with self._create_session(
547+
self.__secure,
548+
scheme,
549+
self.__hostname,
550+
4984,
551+
self.__admin_session.auth,
552+
) as public_session:
553+
return await self._send_request(
554+
method,
555+
path,
556+
payload,
557+
params,
558+
session=public_session,
559+
use_public_api=False,
560+
)
561+
542562
if session is None:
543563
session = self.__admin_session
544564

@@ -922,27 +942,11 @@ async def get_all_documents(
922942
"cbl.collection.name": collection,
923943
},
924944
):
925-
if use_public_api:
926-
# Use public port (4984) - required for regular user access
927-
scheme = "https://" if self.__secure else "http://"
928-
# Create session with user's credentials on public port
929-
async with self._create_session(
930-
self.__secure,
931-
scheme,
932-
self.__hostname,
933-
4984,
934-
self.__admin_session.auth,
935-
) as session:
936-
resp = await self._send_request(
937-
"get",
938-
f"/{db_name}.{scope}.{collection}/_all_docs",
939-
session=session,
940-
)
941-
else:
942-
# Use admin port (4985) - default behavior
943-
resp = await self._send_request(
944-
"get", f"/{db_name}.{scope}.{collection}/_all_docs"
945-
)
945+
resp = await self._send_request(
946+
"get",
947+
f"/{db_name}.{scope}.{collection}/_all_docs",
948+
use_public_api=use_public_api,
949+
)
946950

947951
assert isinstance(resp, dict)
948952
return AllDocumentsResponse(cast(dict, resp))
@@ -973,28 +977,11 @@ async def get_changes(
973977
},
974978
):
975979
query_params = f"version_type={version_type}"
976-
977-
if use_public_api:
978-
# Use public port (4984) - required for regular user access
979-
scheme = "https://" if self.__secure else "http://"
980-
# Create session with user's credentials on public port
981-
async with self._create_session(
982-
self.__secure,
983-
scheme,
984-
self.__hostname,
985-
4984,
986-
self.__admin_session.auth,
987-
) as session:
988-
resp = await self._send_request(
989-
"get",
990-
f"/{db_name}.{scope}.{collection}/_changes?{query_params}",
991-
session=session,
992-
)
993-
else:
994-
# Use admin port (4985) - default behavior
995-
resp = await self._send_request(
996-
"get", f"/{db_name}.{scope}.{collection}/_changes?{query_params}"
997-
)
980+
resp = await self._send_request(
981+
"get",
982+
f"/{db_name}.{scope}.{collection}/_changes?{query_params}",
983+
use_public_api=use_public_api,
984+
)
998985

999986
assert isinstance(resp, dict)
1000987
return ChangesResponse(cast(dict, resp))
@@ -1210,6 +1197,7 @@ async def get_document(
12101197
doc_id: str,
12111198
scope: str = "_default",
12121199
collection: str = "_default",
1200+
use_public_api: bool = False,
12131201
) -> RemoteDocument | None:
12141202
"""
12151203
Gets a document from Sync Gateway
@@ -1218,6 +1206,7 @@ async def get_document(
12181206
:param doc_id: The document ID to get
12191207
:param scope: The scope that the document exists in (default '_default')
12201208
:param collection: The collection that the document exists in (default '_default')
1209+
:param use_public_api: If True, uses public port (4984) - automatically set when using user client
12211210
"""
12221211
with self.__tracer.start_as_current_span(
12231212
"get_document",
@@ -1229,7 +1218,9 @@ async def get_document(
12291218
},
12301219
):
12311220
response = await self._send_request(
1232-
"get", f"/{db_name}.{scope}.{collection}/{doc_id}"
1221+
"get",
1222+
f"/{db_name}.{scope}.{collection}/{doc_id}",
1223+
use_public_api=use_public_api,
12331224
)
12341225
if not isinstance(response, dict):
12351226
raise ValueError(

spec/tests/QE/test_ttl.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# TTL (Time To Live) Tests
2+
3+
## test_document_expiry_unix_timestamp
4+
5+
Test document expiration using Unix timestamp format.
6+
7+
1. Create bucket and default collection
8+
2. Configure Sync Gateway database endpoint
9+
3. Create user 'vipul' with access to NBC, ABC
10+
4. Create documents with different expiry times
11+
5. Verify both documents exist initially
12+
6. Wait for exp_3 document to expire
13+
7. Verify exp_3 document is expired (not accessible)
14+
8. Verify exp_years document is still accessible
15+
16+
## test_string_expiry_as_iso_8601_date
17+
18+
Test document expiration using ISO-8601 date format.
19+
20+
1. Create bucket and default collection
21+
2. Configure Sync Gateway database endpoint
22+
3. Create user 'vipul' with access to NBC, ABC
23+
4. Create documents with ISO-8601 expiry dates
24+
5. Verify both documents exist initially
25+
6. Wait for exp_3 document to expire
26+
7. Verify exp_3 document is expired (not accessible)
27+
8. Verify exp_years document is still accessible

tests/QE/test_ttl.py

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import time
2+
from datetime import datetime, timedelta
3+
from pathlib import Path
4+
5+
import pytest
6+
from cbltest import CBLPyTest
7+
from cbltest.api.cbltestclass import CBLTestClass
8+
from cbltest.api.error import CblSyncGatewayBadResponseError
9+
from cbltest.api.syncgateway import DocumentUpdateEntry, PutDatabasePayload
10+
11+
12+
@pytest.mark.sgw
13+
@pytest.mark.min_sync_gateways(1)
14+
@pytest.mark.min_couchbase_servers(1)
15+
class TestTTL(CBLTestClass):
16+
@pytest.mark.asyncio(loop_scope="session")
17+
async def test_document_expiry_unix_timestamp(
18+
self, cblpytest: CBLPyTest, dataset_path: Path
19+
) -> None:
20+
sg = cblpytest.sync_gateways[0]
21+
cbs = cblpytest.couchbase_servers[0]
22+
sg_db = "db"
23+
bucket_name = "data-bucket"
24+
channels = ["NBC", "ABC"]
25+
username = "vipul"
26+
password = "pass"
27+
28+
self.mark_test_step("Create bucket and default collection")
29+
cbs.drop_bucket(bucket_name)
30+
cbs.create_bucket(bucket_name)
31+
32+
self.mark_test_step("Configure Sync Gateway database endpoint")
33+
db_config = {
34+
"bucket": bucket_name,
35+
"index": {"num_replicas": 0},
36+
"scopes": {"_default": {"collections": {"_default": {}}}},
37+
}
38+
db_payload = PutDatabasePayload(db_config)
39+
db_status = await sg.get_database_status(sg_db)
40+
if db_status is not None:
41+
await sg.delete_database(sg_db)
42+
await sg.put_database(sg_db, db_payload)
43+
44+
self.mark_test_step(f"Create user '{username}' with access to {channels}")
45+
sg_user = await sg.create_user_client(sg, sg_db, username, password, channels)
46+
47+
self.mark_test_step("Create documents with different expiry times")
48+
current_time = datetime.now()
49+
expire_3s = int((current_time + timedelta(seconds=3)).timestamp())
50+
expire_years = int((current_time + timedelta(days=365)).timestamp())
51+
await sg.update_documents(
52+
sg_db,
53+
[
54+
DocumentUpdateEntry(
55+
"exp_3",
56+
None,
57+
body={
58+
"type": "test_doc",
59+
"channels": channels,
60+
"_exp": str(expire_3s),
61+
},
62+
)
63+
],
64+
"_default",
65+
"_default",
66+
)
67+
await sg.update_documents(
68+
sg_db,
69+
[
70+
DocumentUpdateEntry(
71+
"exp_years",
72+
None,
73+
body={
74+
"type": "test_doc",
75+
"channels": channels,
76+
"_exp": str(expire_years),
77+
},
78+
)
79+
],
80+
"_default",
81+
"_default",
82+
)
83+
84+
self.mark_test_step("Verify both documents exist initially")
85+
doc_exp_3 = await sg_user.get_document(
86+
sg_db, "exp_3", "_default", "_default", use_public_api=True
87+
)
88+
doc_exp_years = await sg_user.get_document(
89+
sg_db, "exp_years", "_default", "_default", use_public_api=True
90+
)
91+
assert doc_exp_3 is not None, "exp_3 should exist"
92+
assert doc_exp_years is not None, "exp_years should exist"
93+
94+
self.mark_test_step("Wait for exp_3 document to expire")
95+
time.sleep(10)
96+
97+
self.mark_test_step("Verify exp_3 document is expired (not accessible)")
98+
try:
99+
expired_doc = await sg_user.get_document(
100+
sg_db, "exp_3", "_default", "_default", use_public_api=True
101+
)
102+
if expired_doc is not None:
103+
pytest.fail("exp_3 should be expired/inaccessible")
104+
except CblSyncGatewayBadResponseError as e:
105+
assert e.code in [403, 404], (
106+
f"Expected 403/404 for expired doc, got {e.code}"
107+
)
108+
sdk_doc = cbs.get_document(bucket_name, "exp_3", "_default", "_default")
109+
assert sdk_doc is None, "exp_3 should be purged"
110+
111+
self.mark_test_step("Verify exp_years document is still accessible")
112+
doc_still_valid = await sg_user.get_document(
113+
sg_db, "exp_years", "_default", "_default", use_public_api=True
114+
)
115+
assert doc_still_valid is not None, "exp_years should still be accessible"
116+
assert doc_still_valid.id == "exp_years"
117+
118+
await sg_user.close()
119+
await sg.delete_database(sg_db)
120+
cbs.drop_bucket(bucket_name)
121+
122+
@pytest.mark.asyncio(loop_scope="session")
123+
async def test_string_expiry_as_iso_8601_date(
124+
self, cblpytest: CBLPyTest, dataset_path: Path
125+
) -> None:
126+
sg = cblpytest.sync_gateways[0]
127+
cbs = cblpytest.couchbase_servers[0]
128+
sg_db = "db"
129+
bucket_name = "data-bucket"
130+
channels = ["NBC", "ABC"]
131+
username = "vipul"
132+
password = "pass"
133+
134+
self.mark_test_step("Create bucket and default collection")
135+
cbs.drop_bucket(bucket_name)
136+
cbs.create_bucket(bucket_name)
137+
138+
self.mark_test_step("Configure Sync Gateway database endpoint")
139+
db_config = {
140+
"bucket": bucket_name,
141+
"index": {"num_replicas": 0},
142+
"scopes": {"_default": {"collections": {"_default": {}}}},
143+
}
144+
db_payload = PutDatabasePayload(db_config)
145+
db_status = await sg.get_database_status(sg_db)
146+
if db_status is not None:
147+
await sg.delete_database(sg_db)
148+
await sg.put_database(sg_db, db_payload)
149+
150+
self.mark_test_step(f"Create user '{username}' with access to {channels}")
151+
sg_user = await sg.create_user_client(sg, sg_db, username, password, channels)
152+
153+
self.mark_test_step("Create documents with ISO-8601 expiry dates")
154+
current_time = datetime.now().astimezone()
155+
expire_3s_iso = (current_time + timedelta(seconds=3)).isoformat()
156+
expire_years_iso = (current_time + timedelta(days=365)).isoformat()
157+
158+
await sg.update_documents(
159+
sg_db,
160+
[
161+
DocumentUpdateEntry(
162+
"exp_3",
163+
None,
164+
body={
165+
"type": "test_doc",
166+
"channels": channels,
167+
"_exp": expire_3s_iso,
168+
},
169+
)
170+
],
171+
"_default",
172+
"_default",
173+
)
174+
await sg.update_documents(
175+
sg_db,
176+
[
177+
DocumentUpdateEntry(
178+
"exp_years",
179+
None,
180+
body={
181+
"type": "test_doc",
182+
"channels": channels,
183+
"_exp": expire_years_iso,
184+
},
185+
)
186+
],
187+
"_default",
188+
"_default",
189+
)
190+
191+
self.mark_test_step("Verify both documents exist initially")
192+
doc_exp_3 = await sg_user.get_document(
193+
sg_db, "exp_3", "_default", "_default", use_public_api=True
194+
)
195+
doc_exp_years = await sg_user.get_document(
196+
sg_db, "exp_years", "_default", "_default", use_public_api=True
197+
)
198+
assert doc_exp_3 is not None, "exp_3 should exist"
199+
assert doc_exp_years is not None, "exp_years should exist"
200+
201+
self.mark_test_step("Wait for exp_3 document to expire")
202+
time.sleep(10)
203+
204+
self.mark_test_step("Verify exp_3 document is expired (not accessible)")
205+
try:
206+
expired_doc = await sg_user.get_document(
207+
sg_db, "exp_3", "_default", "_default", use_public_api=True
208+
)
209+
if expired_doc is not None:
210+
pytest.fail("exp_3 should be expired/inaccessible")
211+
except CblSyncGatewayBadResponseError as e:
212+
assert e.code in [403, 404], (
213+
f"Expected 403/404 for expired doc, got {e.code}"
214+
)
215+
sdk_doc = cbs.get_document(bucket_name, "exp_3", "_default", "_default")
216+
assert sdk_doc is None, "exp_3 should be purged from bucket"
217+
218+
self.mark_test_step("Verify exp_years document is still accessible")
219+
doc_still_valid = await sg_user.get_document(
220+
sg_db, "exp_years", "_default", "_default", use_public_api=True
221+
)
222+
assert doc_still_valid is not None, "exp_years should still be accessible"
223+
assert doc_still_valid.id == "exp_years"
224+
225+
await sg_user.close()
226+
await sg.delete_database(sg_db)
227+
cbs.drop_bucket(bucket_name)

0 commit comments

Comments
 (0)