Skip to content

Commit 94b21e0

Browse files
committed
Cover remaining client-surface variations (revisions + pull replication)
Pull replication (secured store, using the workflow TLS setup): - hub with filtering + SinkToHub/combined mode - full register -> get -> unregister certificate-based hub-access flow Pull replication (client-side): - ReplicationType "Type" serialization - use_server_certificate drops the certificate private key Revisions: - enforce with max_ops_per_second (+ its validation) - delete by date range, delete for multiple documents - revert multiple documents by id, bin-cleaner disabled
1 parent de35e84 commit 94b21e0

3 files changed

Lines changed: 164 additions & 0 deletions

File tree

ravendb/tests/jvm_migrated_tests/client_tests/replication_tests/test_pull_replication.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
import unittest
44
from datetime import timedelta
5+
from types import SimpleNamespace
56

67
from ravendb import (
78
ExternalReplication,
@@ -179,3 +180,23 @@ def test_pull_replication_as_sink_json_round_trip(self):
179180
self.assertEqual("s", parsed.name)
180181
self.assertEqual(PullReplicationMode.HUB_TO_SINK, parsed.mode)
181182
self.assertEqual(["users/*"], parsed.allowed_hub_to_sink_paths)
183+
184+
def test_replication_node_to_json_includes_type(self):
185+
sink_json = PullReplicationAsSink(database="db", connection_string_name="cs", hub_name="h").to_json()
186+
self.assertEqual("PullAsSink", sink_json["Type"])
187+
external_json = ExternalReplication(database="db", connection_string_name="cs").to_json()
188+
self.assertEqual("External", external_json["Type"])
189+
190+
def test_update_sink_with_server_certificate_omits_private_key(self):
191+
node = SimpleNamespace(url="http://localhost:8080", database="db")
192+
sink = PullReplicationAsSink(
193+
database="db", connection_string_name="cs", hub_name="h", name="s", certificate_with_private_key="SECRET"
194+
)
195+
196+
with_server_cert = UpdatePullReplicationAsSinkOperation(sink, use_server_certificate=True).get_command(None)
197+
sent = with_server_cert.create_request(node).data["PullReplicationAsSink"]
198+
self.assertIsNone(sent["CertificateWithPrivateKey"])
199+
200+
without = UpdatePullReplicationAsSinkOperation(sink, use_server_certificate=False).get_command(None)
201+
sent = without.create_request(node).data["PullReplicationAsSink"]
202+
self.assertEqual("SECRET", sent["CertificateWithPrivateKey"])
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import base64
2+
import hashlib
3+
import os
4+
import re
5+
import unittest
6+
7+
from ravendb import (
8+
GetPullReplicationTasksInfoOperation,
9+
GetReplicationHubAccessOperation,
10+
PullReplicationDefinition,
11+
PullReplicationMode,
12+
PutPullReplicationAsHubOperation,
13+
RegisterReplicationHubAccessOperation,
14+
ReplicationHubAccess,
15+
UnregisterReplicationHubAccessOperation,
16+
)
17+
from ravendb.tests.test_base import TestBase
18+
19+
20+
def _certificate_base64_and_thumbprint(pem_path):
21+
"""Extract the certificate (base64 DER) and its SHA-1 thumbprint from a PEM file."""
22+
with open(pem_path, "r") as pem_file:
23+
pem = pem_file.read()
24+
match = re.search(r"-----BEGIN CERTIFICATE-----(.+?)-----END CERTIFICATE-----", pem, re.DOTALL)
25+
certificate_base64 = "".join(match.group(1).split())
26+
der = base64.b64decode(certificate_base64)
27+
return certificate_base64, hashlib.sha1(der).hexdigest().upper()
28+
29+
30+
@unittest.skipIf(os.environ.get("RAVENDB_LICENSE") is None, "Insufficient license permissions. Skipping on CI/CD.")
31+
class TestPullReplicationSecured(TestBase):
32+
"""Pull-replication surface that the server only permits over SSL (filtering, the
33+
SinkToHub direction, and certificate-based hub access) - exercised against a secured store."""
34+
35+
def setUp(self):
36+
super().setUp()
37+
38+
def test_can_define_hub_with_filtering_and_combined_mode(self):
39+
store = self.secured_document_store
40+
definition = PullReplicationDefinition(
41+
"filtered-hub", mode=PullReplicationMode.HUB_TO_SINK_AND_SINK_TO_HUB, with_filtering=True
42+
)
43+
44+
result = store.maintenance.send(PutPullReplicationAsHubOperation(definition))
45+
self.assertIsNotNone(result.task_id)
46+
47+
info = store.maintenance.send(GetPullReplicationTasksInfoOperation(result.task_id))
48+
self.assertEqual("filtered-hub", info.definition.name)
49+
self.assertEqual(PullReplicationMode.HUB_TO_SINK_AND_SINK_TO_HUB, info.definition.mode)
50+
self.assertTrue(info.definition.with_filtering)
51+
52+
def test_register_get_and_unregister_replication_hub_access(self):
53+
store = self.secured_document_store
54+
store.maintenance.send(
55+
PutPullReplicationAsHubOperation(PullReplicationDefinition("access-hub", with_filtering=True))
56+
)
57+
58+
certificate_base64, thumbprint = _certificate_base64_and_thumbprint(self.test_client_certificate_url)
59+
access = ReplicationHubAccess(
60+
name="sink-access", certificate_base64=certificate_base64, allowed_hub_to_sink_paths=["users/*"]
61+
)
62+
store.maintenance.send(RegisterReplicationHubAccessOperation("access-hub", access))
63+
64+
registered = store.maintenance.send(GetReplicationHubAccessOperation("access-hub"))
65+
self.assertEqual(1, len(registered))
66+
self.assertEqual(thumbprint.lower(), registered[0].thumbprint.lower())
67+
self.assertEqual(1, len(registered[0].allowed_hub_to_sink_paths))
68+
69+
store.maintenance.send(UnregisterReplicationHubAccessOperation("access-hub", thumbprint))
70+
self.assertEqual([], store.maintenance.send(GetReplicationHubAccessOperation("access-hub")))

ravendb/tests/jvm_migrated_tests/client_tests/revisions_tests/test_revisions_operations.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,76 @@ def test_adopt_orphaned_revisions_completes(self):
182182
# wait_for_completion raises if the operation faults.
183183
operation = self.store.operations.send_async(AdoptOrphanedRevisionsOperation())
184184
operation.wait_for_completion()
185+
186+
def test_enforce_revisions_configuration_with_max_ops_per_second(self):
187+
self.setup_revisions(self.store, False, 100)
188+
company = self._create_company_with_revisions(4)
189+
190+
configuration = RevisionsConfiguration()
191+
default_config = RevisionsCollectionConfiguration()
192+
default_config.minimum_revisions_to_keep = 2
193+
configuration.default_config = default_config
194+
self.store.maintenance.send(ConfigureRevisionsOperation(configuration))
195+
196+
parameters = EnforceRevisionsConfigurationOperation.Parameters(max_ops_per_second=10000)
197+
operation = self.store.operations.send_async(EnforceRevisionsConfigurationOperation(parameters))
198+
operation.wait_for_completion()
199+
200+
with self.store.open_session() as session:
201+
self.assertEqual(2, session.advanced.revisions.get_count_for(company.Id))
202+
203+
def test_enforce_parameters_reject_non_positive_max_ops_per_second(self):
204+
self.assertRaises(ValueError, lambda: EnforceRevisionsConfigurationOperation.Parameters(max_ops_per_second=0))
205+
206+
def test_delete_revisions_by_date_range(self):
207+
self.setup_revisions(self.store, False, 100)
208+
company = self._create_company_with_revisions(4)
209+
210+
result = self.store.maintenance.send(
211+
DeleteRevisionsOperation(
212+
document_id=company.Id, from_date=datetime(2000, 1, 1), to_date=datetime(2100, 1, 1)
213+
)
214+
)
215+
self.assertEqual(5, result.total_deletes)
216+
with self.store.open_session() as session:
217+
self.assertEqual(0, session.advanced.revisions.get_count_for(company.Id))
218+
219+
def test_delete_revisions_for_multiple_documents(self):
220+
self.setup_revisions(self.store, False, 100)
221+
first = self._create_company_with_revisions(4)
222+
second = self._create_company_with_revisions(4)
223+
224+
result = self.store.maintenance.send(DeleteRevisionsOperation(document_ids=[first.Id, second.Id]))
225+
self.assertEqual(10, result.total_deletes)
226+
with self.store.open_session() as session:
227+
self.assertEqual(0, session.advanced.revisions.get_count_for(first.Id))
228+
self.assertEqual(0, session.advanced.revisions.get_count_for(second.Id))
229+
230+
def test_revert_multiple_revisions_by_id(self):
231+
self.setup_revisions(self.store, False, 100)
232+
233+
id_to_old_change_vector = {}
234+
for _ in range(2):
235+
company = Company(name="Old Name")
236+
with self.store.open_session() as session:
237+
session.store(company)
238+
session.save_changes()
239+
with self.store.open_session() as session:
240+
session.load(company.Id, Company).name = "New Name"
241+
session.save_changes()
242+
with self.store.open_session() as session:
243+
metadata = session.advanced.revisions.get_metadata_for(company.Id)
244+
id_to_old_change_vector[company.Id] = metadata[1][constants.Documents.Metadata.CHANGE_VECTOR]
245+
246+
self.store.operations.send(RevertRevisionsByIdOperation(id_to_change_vector=id_to_old_change_vector))
247+
248+
with self.store.open_session() as session:
249+
for company_id in id_to_old_change_vector:
250+
self.assertEqual("Old Name", session.load(company_id, Company).name)
251+
252+
def test_configure_revisions_bin_cleaner_disabled(self):
253+
result = self.store.maintenance.send(
254+
ConfigureRevisionsBinCleanerOperation(RevisionsBinConfiguration(disabled=True))
255+
)
256+
self.assertIsNotNone(result.raft_command_index)
257+
self.assertGreater(result.raft_command_index, 0)

0 commit comments

Comments
 (0)