diff options
| author | drebs <drebs@leap.se> | 2016-07-10 11:08:18 +0200 | 
|---|---|---|
| committer | Kali Kaneko <kali@leap.se> | 2016-07-12 03:09:34 +0200 | 
| commit | 769ce3d7a014b1e1e0fba23df6993271cfd647ba (patch) | |
| tree | a03b4c96c8268b085e3b0679f2329156add1154d | |
| parent | f406ccbaf2b79db1d65827463f830f4ffbe5856c (diff) | |
[test] refactor test files
| -rw-r--r-- | testing/tests/couch/common.py | 81 | ||||
| -rw-r--r-- | testing/tests/couch/test_atomicity.py (renamed from testing/tests/couch/test_couch_operations_atomicity.py) | 0 | ||||
| -rw-r--r-- | testing/tests/couch/test_backend.py | 115 | ||||
| -rw-r--r-- | testing/tests/couch/test_command.py | 28 | ||||
| -rw-r--r-- | testing/tests/couch/test_ddocs.py | 209 | ||||
| -rw-r--r-- | testing/tests/couch/test_sync.py (renamed from testing/tests/couch/test_couch.py) | 760 | ||||
| -rw-r--r-- | testing/tests/couch/test_sync_target.py | 343 | ||||
| -rw-r--r-- | testing/tests/sqlcipher/hacker_crackdown.txt (renamed from testing/tests/client/hacker_crackdown.txt) | 0 | ||||
| -rw-r--r-- | testing/tests/sqlcipher/test_backend.py (renamed from testing/tests/sqlcipher/test_sqlcipher.py) | 0 | 
9 files changed, 785 insertions, 751 deletions
| diff --git a/testing/tests/couch/common.py b/testing/tests/couch/common.py new file mode 100644 index 00000000..b08e1fa3 --- /dev/null +++ b/testing/tests/couch/common.py @@ -0,0 +1,81 @@ +from uuid import uuid4 +from urlparse import urljoin +from couchdb.client import Server + +from leap.soledad.common import couch +from leap.soledad.common.document import ServerDocument + +from test_soledad import u1db_tests as tests + + +simple_doc = tests.simple_doc +nested_doc = tests.nested_doc + + +def make_couch_database_for_test(test, replica_uid): +    port = str(test.couch_port) +    dbname = ('test-%s' % uuid4().hex) +    db = couch.CouchDatabase.open_database( +        urljoin('http://localhost:' + port, dbname), +        create=True, +        replica_uid=replica_uid or 'test', +        ensure_ddocs=True) +    test.addCleanup(test.delete_db, dbname) +    return db + + +def copy_couch_database_for_test(test, db): +    port = str(test.couch_port) +    couch_url = 'http://localhost:' + port +    new_dbname = db._dbname + '_copy' +    new_db = couch.CouchDatabase.open_database( +        urljoin(couch_url, new_dbname), +        create=True, +        replica_uid=db._replica_uid or 'test') +    # copy all docs +    session = couch.Session() +    old_couch_db = Server(couch_url, session=session)[db._dbname] +    new_couch_db = Server(couch_url, session=session)[new_dbname] +    for doc_id in old_couch_db: +        doc = old_couch_db.get(doc_id) +        # bypass u1db_config document +        if doc_id == 'u1db_config': +            pass +        # copy design docs +        elif doc_id.startswith('_design'): +            del doc['_rev'] +            new_couch_db.save(doc) +        # copy u1db docs +        elif 'u1db_rev' in doc: +            new_doc = { +                '_id': doc['_id'], +                'u1db_transactions': doc['u1db_transactions'], +                'u1db_rev': doc['u1db_rev'] +            } +            attachments = [] +            if ('u1db_conflicts' in doc): +                new_doc['u1db_conflicts'] = doc['u1db_conflicts'] +                for c_rev in doc['u1db_conflicts']: +                    attachments.append('u1db_conflict_%s' % c_rev) +            new_couch_db.save(new_doc) +            # save conflict data +            attachments.append('u1db_content') +            for att_name in attachments: +                att = old_couch_db.get_attachment(doc_id, att_name) +                if (att is not None): +                    new_couch_db.put_attachment(new_doc, att, +                                                filename=att_name) +    # cleanup connections to prevent file descriptor leaking +    return new_db + + +def make_document_for_test(test, doc_id, rev, content, has_conflicts=False): +    return ServerDocument( +        doc_id, rev, content, has_conflicts=has_conflicts) + + +COUCH_SCENARIOS = [ +    ('couch', {'make_database_for_test': make_couch_database_for_test, +               'copy_database_for_test': copy_couch_database_for_test, +               'make_document_for_test': make_document_for_test, }), +] diff --git a/testing/tests/couch/test_couch_operations_atomicity.py b/testing/tests/couch/test_atomicity.py index aec9c6cf..aec9c6cf 100644 --- a/testing/tests/couch/test_couch_operations_atomicity.py +++ b/testing/tests/couch/test_atomicity.py diff --git a/testing/tests/couch/test_backend.py b/testing/tests/couch/test_backend.py new file mode 100644 index 00000000..f178e8a5 --- /dev/null +++ b/testing/tests/couch/test_backend.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +# test_couch.py +# Copyright (C) 2013-2016 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Test ObjectStore and Couch backend bits. +""" + +from uuid import uuid4 +from urlparse import urljoin +from testscenarios import TestWithScenarios +from twisted.trial import unittest + +from leap.soledad.common import couch + +from test_soledad.util import CouchDBTestCase +from test_soledad.u1db_tests import test_backends + +from common import COUCH_SCENARIOS + + +# ----------------------------------------------------------------------------- +# The following tests come from `u1db.tests.test_common_backend`. +# ----------------------------------------------------------------------------- + +class TestCouchBackendImpl(CouchDBTestCase): + +    def test__allocate_doc_id(self): +        db = couch.CouchDatabase.open_database( +            urljoin( +                'http://localhost:' + str(self.couch_port), +                ('test-%s' % uuid4().hex) +            ), +            create=True, +            ensure_ddocs=True) +        doc_id1 = db._allocate_doc_id() +        self.assertTrue(doc_id1.startswith('D-')) +        self.assertEqual(34, len(doc_id1)) +        int(doc_id1[len('D-'):], 16) +        self.assertNotEqual(doc_id1, db._allocate_doc_id()) +        self.delete_db(db._dbname) + + +# ----------------------------------------------------------------------------- +# The following tests come from `u1db.tests.test_backends`. +# ----------------------------------------------------------------------------- + +class CouchTests( +        TestWithScenarios, test_backends.AllDatabaseTests, CouchDBTestCase): + +    scenarios = COUCH_SCENARIOS + + +class CouchBackendTests( +        TestWithScenarios, +        test_backends.LocalDatabaseTests, +        CouchDBTestCase): + +    scenarios = COUCH_SCENARIOS + + +class CouchValidateGenNTransIdTests( +        TestWithScenarios, +        test_backends.LocalDatabaseValidateGenNTransIdTests, +        CouchDBTestCase): + +    scenarios = COUCH_SCENARIOS + + +class CouchValidateSourceGenTests( +        TestWithScenarios, +        test_backends.LocalDatabaseValidateSourceGenTests, +        CouchDBTestCase): + +    scenarios = COUCH_SCENARIOS + + +class CouchWithConflictsTests( +        TestWithScenarios, +        test_backends.LocalDatabaseWithConflictsTests, +        CouchDBTestCase): + +        scenarios = COUCH_SCENARIOS + + +# Notice: the CouchDB backend does not have indexing capabilities, so we do +# not test indexing now. + +# class CouchIndexTests(test_backends.DatabaseIndexTests, CouchDBTestCase): +# +#     scenarios = COUCH_SCENARIOS +# +#     def tearDown(self): +#         self.db.delete_database() +#         test_backends.DatabaseIndexTests.tearDown(self) + + +class DatabaseNameValidationTest(unittest.TestCase): + +    def test_database_name_validation(self): +        inject = couch.state.is_db_name_valid("user-deadbeef | cat /secret") +        self.assertFalse(inject) +        self.assertTrue(couch.state.is_db_name_valid("user-cafe1337")) diff --git a/testing/tests/couch/test_command.py b/testing/tests/couch/test_command.py new file mode 100644 index 00000000..f61e118d --- /dev/null +++ b/testing/tests/couch/test_command.py @@ -0,0 +1,28 @@ +from twisted.trial import unittest + +from leap.soledad.common import couch +from leap.soledad.common.l2db import errors as u1db_errors + +from mock import Mock + + +class CommandBasedDBCreationTest(unittest.TestCase): + +    def test_ensure_db_using_custom_command(self): +        state = couch.state.CouchServerState("url", create_cmd="echo") +        mock_db = Mock() +        mock_db.replica_uid = 'replica_uid' +        state.open_database = Mock(return_value=mock_db) +        db, replica_uid = state.ensure_database("user-1337")  # works +        self.assertEquals(mock_db, db) +        self.assertEquals(mock_db.replica_uid, replica_uid) + +    def test_raises_unauthorized_on_failure(self): +        state = couch.state.CouchServerState("url", create_cmd="inexistent") +        self.assertRaises(u1db_errors.Unauthorized, +                          state.ensure_database, "user-1337") + +    def test_raises_unauthorized_by_default(self): +        state = couch.state.CouchServerState("url") +        self.assertRaises(u1db_errors.Unauthorized, +                          state.ensure_database, "user-1337") diff --git a/testing/tests/couch/test_ddocs.py b/testing/tests/couch/test_ddocs.py new file mode 100644 index 00000000..9ff32633 --- /dev/null +++ b/testing/tests/couch/test_ddocs.py @@ -0,0 +1,209 @@ +from uuid import uuid4 + +from leap.soledad.common.couch import errors +from leap.soledad.common import couch + +from test_soledad.util import CouchDBTestCase + + +class CouchDesignDocsTests(CouchDBTestCase): + +    def setUp(self): +        CouchDBTestCase.setUp(self) + +    def create_db(self, ensure=True, dbname=None): +        if not dbname: +            dbname = ('test-%s' % uuid4().hex) +        if dbname not in self.couch_server: +            self.couch_server.create(dbname) +        self.db = couch.CouchDatabase( +            ('http://127.0.0.1:%d' % self.couch_port), +            dbname, +            ensure_ddocs=ensure) + +    def tearDown(self): +        self.db.delete_database() +        self.db.close() +        CouchDBTestCase.tearDown(self) + +    def test_missing_design_doc_raises(self): +        """ +        Test that all methods that access design documents will raise if the +        design docs are not present. +        """ +        self.create_db(ensure=False) +        # get_generation_info() +        self.assertRaises( +            errors.MissingDesignDocError, +            self.db.get_generation_info) +        # get_trans_id_for_gen() +        self.assertRaises( +            errors.MissingDesignDocError, +            self.db.get_trans_id_for_gen, 1) +        # get_transaction_log() +        self.assertRaises( +            errors.MissingDesignDocError, +            self.db.get_transaction_log) +        # whats_changed() +        self.assertRaises( +            errors.MissingDesignDocError, +            self.db.whats_changed) + +    def test_missing_design_doc_functions_raises(self): +        """ +        Test that all methods that access design documents list functions +        will raise if the functions are not present. +        """ +        self.create_db(ensure=True) +        # erase views from _design/transactions +        transactions = self.db._database['_design/transactions'] +        transactions['lists'] = {} +        self.db._database.save(transactions) +        # get_generation_info() +        self.assertRaises( +            errors.MissingDesignDocListFunctionError, +            self.db.get_generation_info) +        # get_trans_id_for_gen() +        self.assertRaises( +            errors.MissingDesignDocListFunctionError, +            self.db.get_trans_id_for_gen, 1) +        # whats_changed() +        self.assertRaises( +            errors.MissingDesignDocListFunctionError, +            self.db.whats_changed) + +    def test_absent_design_doc_functions_raises(self): +        """ +        Test that all methods that access design documents list functions +        will raise if the functions are not present. +        """ +        self.create_db(ensure=True) +        # erase views from _design/transactions +        transactions = self.db._database['_design/transactions'] +        del transactions['lists'] +        self.db._database.save(transactions) +        # get_generation_info() +        self.assertRaises( +            errors.MissingDesignDocListFunctionError, +            self.db.get_generation_info) +        # _get_trans_id_for_gen() +        self.assertRaises( +            errors.MissingDesignDocListFunctionError, +            self.db.get_trans_id_for_gen, 1) +        # whats_changed() +        self.assertRaises( +            errors.MissingDesignDocListFunctionError, +            self.db.whats_changed) + +    def test_missing_design_doc_named_views_raises(self): +        """ +        Test that all methods that access design documents' named views  will +        raise if the views are not present. +        """ +        self.create_db(ensure=True) +        # erase views from _design/docs +        docs = self.db._database['_design/docs'] +        del docs['views'] +        self.db._database.save(docs) +        # erase views from _design/syncs +        syncs = self.db._database['_design/syncs'] +        del syncs['views'] +        self.db._database.save(syncs) +        # erase views from _design/transactions +        transactions = self.db._database['_design/transactions'] +        del transactions['views'] +        self.db._database.save(transactions) +        # get_generation_info() +        self.assertRaises( +            errors.MissingDesignDocNamedViewError, +            self.db.get_generation_info) +        # _get_trans_id_for_gen() +        self.assertRaises( +            errors.MissingDesignDocNamedViewError, +            self.db.get_trans_id_for_gen, 1) +        # _get_transaction_log() +        self.assertRaises( +            errors.MissingDesignDocNamedViewError, +            self.db.get_transaction_log) +        # whats_changed() +        self.assertRaises( +            errors.MissingDesignDocNamedViewError, +            self.db.whats_changed) + +    def test_deleted_design_doc_raises(self): +        """ +        Test that all methods that access design documents will raise if the +        design docs are not present. +        """ +        self.create_db(ensure=True) +        # delete _design/docs +        del self.db._database['_design/docs'] +        # delete _design/syncs +        del self.db._database['_design/syncs'] +        # delete _design/transactions +        del self.db._database['_design/transactions'] +        # get_generation_info() +        self.assertRaises( +            errors.MissingDesignDocDeletedError, +            self.db.get_generation_info) +        # get_trans_id_for_gen() +        self.assertRaises( +            errors.MissingDesignDocDeletedError, +            self.db.get_trans_id_for_gen, 1) +        # get_transaction_log() +        self.assertRaises( +            errors.MissingDesignDocDeletedError, +            self.db.get_transaction_log) +        # whats_changed() +        self.assertRaises( +            errors.MissingDesignDocDeletedError, +            self.db.whats_changed) + +    def test_ensure_ddoc_independently(self): +        """ +        Test that a missing ddocs other than _design/docs will be ensured +        even if _design/docs is there. +        """ +        self.create_db(ensure=True) +        del self.db._database['_design/transactions'] +        self.assertRaises( +            errors.MissingDesignDocDeletedError, +            self.db.get_transaction_log) +        self.create_db(ensure=True, dbname=self.db._dbname) +        self.db.get_transaction_log() + +    def test_ensure_security_doc(self): +        """ +        Ensure_security creates a _security ddoc to ensure that only soledad +        will have the lowest privileged access to an user db. +        """ +        self.create_db(ensure=False) +        self.assertFalse(self.db._database.resource.get_json('_security')[2]) +        self.db.ensure_security_ddoc() +        security_ddoc = self.db._database.resource.get_json('_security')[2] +        self.assertIn('admins', security_ddoc) +        self.assertFalse(security_ddoc['admins']['names']) +        self.assertIn('members', security_ddoc) +        self.assertIn('soledad', security_ddoc['members']['names']) + +    def test_ensure_security_from_configuration(self): +        """ +        Given a configuration, follow it to create the security document +        """ +        self.create_db(ensure=False) +        configuration = {'members': ['user1', 'user2'], +                         'members_roles': ['role1', 'role2'], +                         'admins': ['admin'], +                         'admins_roles': ['administrators'] +                         } +        self.db.ensure_security_ddoc(configuration) + +        security_ddoc = self.db._database.resource.get_json('_security')[2] +        self.assertEquals(configuration['admins'], +                          security_ddoc['admins']['names']) +        self.assertEquals(configuration['admins_roles'], +                          security_ddoc['admins']['roles']) +        self.assertEquals(configuration['members'], +                          security_ddoc['members']['names']) +        self.assertEquals(configuration['members_roles'], +                          security_ddoc['members']['roles']) diff --git a/testing/tests/couch/test_couch.py b/testing/tests/couch/test_sync.py index 94c6ca92..bccbfe43 100644 --- a/testing/tests/couch/test_couch.py +++ b/testing/tests/couch/test_sync.py @@ -1,529 +1,16 @@ -# -*- coding: utf-8 -*- -# test_couch.py -# Copyright (C) 2013-2016 LEAP -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -""" -Test ObjectStore and Couch backend bits. -""" -import json - -from uuid import uuid4 -from urlparse import urljoin - -from couchdb.client import Server - -from testscenarios import TestWithScenarios -from twisted.trial import unittest -from mock import Mock - -from leap.soledad.common.l2db import errors as u1db_errors -from leap.soledad.common.l2db import SyncTarget  from leap.soledad.common.l2db import vectorclock +from leap.soledad.common.l2db import errors as u1db_errors -from leap.soledad.common import couch -from leap.soledad.common.document import ServerDocument -from leap.soledad.common.couch import errors +from testscenarios import TestWithScenarios  from test_soledad import u1db_tests as tests  from test_soledad.util import CouchDBTestCase -from test_soledad.util import make_local_db_and_target  from test_soledad.util import sync_via_synchronizer - -from test_soledad.u1db_tests import test_backends  from test_soledad.u1db_tests import DatabaseBaseTests +from common import simple_doc +from common import COUCH_SCENARIOS -# ----------------------------------------------------------------------------- -# The following tests come from `u1db.tests.test_common_backend`. -# ----------------------------------------------------------------------------- - -class TestCouchBackendImpl(CouchDBTestCase): - -    def test__allocate_doc_id(self): -        db = couch.CouchDatabase.open_database( -            urljoin( -                'http://localhost:' + str(self.couch_port), -                ('test-%s' % uuid4().hex) -            ), -            create=True, -            ensure_ddocs=True) -        doc_id1 = db._allocate_doc_id() -        self.assertTrue(doc_id1.startswith('D-')) -        self.assertEqual(34, len(doc_id1)) -        int(doc_id1[len('D-'):], 16) -        self.assertNotEqual(doc_id1, db._allocate_doc_id()) -        self.delete_db(db._dbname) - - -# ----------------------------------------------------------------------------- -# The following tests come from `u1db.tests.test_backends`. -# ----------------------------------------------------------------------------- - -def make_couch_database_for_test(test, replica_uid): -    port = str(test.couch_port) -    dbname = ('test-%s' % uuid4().hex) -    db = couch.CouchDatabase.open_database( -        urljoin('http://localhost:' + port, dbname), -        create=True, -        replica_uid=replica_uid or 'test', -        ensure_ddocs=True) -    test.addCleanup(test.delete_db, dbname) -    return db - - -def copy_couch_database_for_test(test, db): -    port = str(test.couch_port) -    couch_url = 'http://localhost:' + port -    new_dbname = db._dbname + '_copy' -    new_db = couch.CouchDatabase.open_database( -        urljoin(couch_url, new_dbname), -        create=True, -        replica_uid=db._replica_uid or 'test') -    # copy all docs -    session = couch.Session() -    old_couch_db = Server(couch_url, session=session)[db._dbname] -    new_couch_db = Server(couch_url, session=session)[new_dbname] -    for doc_id in old_couch_db: -        doc = old_couch_db.get(doc_id) -        # bypass u1db_config document -        if doc_id == 'u1db_config': -            pass -        # copy design docs -        elif doc_id.startswith('_design'): -            del doc['_rev'] -            new_couch_db.save(doc) -        # copy u1db docs -        elif 'u1db_rev' in doc: -            new_doc = { -                '_id': doc['_id'], -                'u1db_transactions': doc['u1db_transactions'], -                'u1db_rev': doc['u1db_rev'] -            } -            attachments = [] -            if ('u1db_conflicts' in doc): -                new_doc['u1db_conflicts'] = doc['u1db_conflicts'] -                for c_rev in doc['u1db_conflicts']: -                    attachments.append('u1db_conflict_%s' % c_rev) -            new_couch_db.save(new_doc) -            # save conflict data -            attachments.append('u1db_content') -            for att_name in attachments: -                att = old_couch_db.get_attachment(doc_id, att_name) -                if (att is not None): -                    new_couch_db.put_attachment(new_doc, att, -                                                filename=att_name) -    # cleanup connections to prevent file descriptor leaking -    return new_db - - -def make_document_for_test(test, doc_id, rev, content, has_conflicts=False): -    return ServerDocument( -        doc_id, rev, content, has_conflicts=has_conflicts) - - -COUCH_SCENARIOS = [ -    ('couch', {'make_database_for_test': make_couch_database_for_test, -               'copy_database_for_test': copy_couch_database_for_test, -               'make_document_for_test': make_document_for_test, }), -] - - -class CouchTests( -        TestWithScenarios, test_backends.AllDatabaseTests, CouchDBTestCase): - -    scenarios = COUCH_SCENARIOS - - -class SoledadBackendTests( -        TestWithScenarios, -        test_backends.LocalDatabaseTests, -        CouchDBTestCase): - -    scenarios = COUCH_SCENARIOS - - -class CouchValidateGenNTransIdTests( -        TestWithScenarios, -        test_backends.LocalDatabaseValidateGenNTransIdTests, -        CouchDBTestCase): - -    scenarios = COUCH_SCENARIOS - - -class CouchValidateSourceGenTests( -        TestWithScenarios, -        test_backends.LocalDatabaseValidateSourceGenTests, -        CouchDBTestCase): - -    scenarios = COUCH_SCENARIOS - - -class CouchWithConflictsTests( -        TestWithScenarios, -        test_backends.LocalDatabaseWithConflictsTests, -        CouchDBTestCase): - -        scenarios = COUCH_SCENARIOS - - -# Notice: the CouchDB backend does not have indexing capabilities, so we do -# not test indexing now. - -# class CouchIndexTests(test_backends.DatabaseIndexTests, CouchDBTestCase): -# -#     scenarios = COUCH_SCENARIOS -# -#     def tearDown(self): -#         self.db.delete_database() -#         test_backends.DatabaseIndexTests.tearDown(self) - - -# ----------------------------------------------------------------------------- -# The following tests come from `u1db.tests.test_sync`. -# ----------------------------------------------------------------------------- - -target_scenarios = [ -    ('local', {'create_db_and_target': make_local_db_and_target}), ] - - -simple_doc = tests.simple_doc -nested_doc = tests.nested_doc - - -class SoledadBackendSyncTargetTests( -        TestWithScenarios, -        DatabaseBaseTests, -        CouchDBTestCase): - -    # TODO: implement _set_trace_hook(_shallow) in CouchSyncTarget so -    #       skipped tests can be succesfully executed. - -    # whitebox true means self.db is the actual local db object -    # against which the sync is performed -    whitebox = True - -    scenarios = (tests.multiply_scenarios(COUCH_SCENARIOS, target_scenarios)) - -    def set_trace_hook(self, callback, shallow=False): -        setter = (self.st._set_trace_hook if not shallow else -                  self.st._set_trace_hook_shallow) -        try: -            setter(callback) -        except NotImplementedError: -            self.skipTest("%s does not implement _set_trace_hook" -                          % (self.st.__class__.__name__,)) - -    def setUp(self): -        CouchDBTestCase.setUp(self) -        # other stuff -        self.db, self.st = self.create_db_and_target(self) -        self.other_changes = [] - -    def tearDown(self): -        self.db.close() -        CouchDBTestCase.tearDown(self) - -    def receive_doc(self, doc, gen, trans_id): -        self.other_changes.append( -            (doc.doc_id, doc.rev, doc.get_json(), gen, trans_id)) - -    def test_sync_exchange_returns_many_new_docs(self): -        # This test was replicated to allow dictionaries to be compared after -        # JSON expansion (because one dictionary may have many different -        # serialized representations). -        doc = self.db.create_doc_from_json(simple_doc) -        doc2 = self.db.create_doc_from_json(nested_doc) -        self.assertTransactionLog([doc.doc_id, doc2.doc_id], self.db) -        new_gen, _ = self.st.sync_exchange( -            [], 'other-replica', last_known_generation=0, -            last_known_trans_id=None, return_doc_cb=self.receive_doc) -        self.assertTransactionLog([doc.doc_id, doc2.doc_id], self.db) -        self.assertEqual(2, new_gen) -        self.assertEqual( -            [(doc.doc_id, doc.rev, json.loads(simple_doc), 1), -             (doc2.doc_id, doc2.rev, json.loads(nested_doc), 2)], -            [c[:-3] + (json.loads(c[-3]), c[-2]) for c in self.other_changes]) -        if self.whitebox: -            self.assertEqual( -                self.db._last_exchange_log['return'], -                {'last_gen': 2, 'docs': -                 [(doc.doc_id, doc.rev), (doc2.doc_id, doc2.rev)]}) - -    def test_get_sync_target(self): -        self.assertIsNot(None, self.st) - -    def test_get_sync_info(self): -        self.assertEqual( -            ('test', 0, '', 0, ''), self.st.get_sync_info('other')) - -    def test_create_doc_updates_sync_info(self): -        self.assertEqual( -            ('test', 0, '', 0, ''), self.st.get_sync_info('other')) -        self.db.create_doc_from_json(simple_doc) -        self.assertEqual(1, self.st.get_sync_info('other')[1]) - -    def test_record_sync_info(self): -        self.st.record_sync_info('replica', 10, 'T-transid') -        self.assertEqual( -            ('test', 0, '', 10, 'T-transid'), self.st.get_sync_info('replica')) - -    def test_sync_exchange(self): -        docs_by_gen = [ -            (self.make_document('doc-id', 'replica:1', simple_doc), 10, -             'T-sid')] -        new_gen, trans_id = self.st.sync_exchange( -            docs_by_gen, 'replica', last_known_generation=0, -            last_known_trans_id=None, return_doc_cb=self.receive_doc) -        self.assertGetDoc(self.db, 'doc-id', 'replica:1', simple_doc, False) -        self.assertTransactionLog(['doc-id'], self.db) -        last_trans_id = self.getLastTransId(self.db) -        self.assertEqual(([], 1, last_trans_id), -                         (self.other_changes, new_gen, last_trans_id)) -        self.assertEqual(10, self.st.get_sync_info('replica')[3]) - -    def test_sync_exchange_deleted(self): -        doc = self.db.create_doc_from_json('{}') -        edit_rev = 'replica:1|' + doc.rev -        docs_by_gen = [ -            (self.make_document(doc.doc_id, edit_rev, None), 10, 'T-sid')] -        new_gen, trans_id = self.st.sync_exchange( -            docs_by_gen, 'replica', last_known_generation=0, -            last_known_trans_id=None, return_doc_cb=self.receive_doc) -        self.assertGetDocIncludeDeleted( -            self.db, doc.doc_id, edit_rev, None, False) -        self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db) -        last_trans_id = self.getLastTransId(self.db) -        self.assertEqual(([], 2, last_trans_id), -                         (self.other_changes, new_gen, trans_id)) -        self.assertEqual(10, self.st.get_sync_info('replica')[3]) - -    def test_sync_exchange_push_many(self): -        docs_by_gen = [ -            (self.make_document('doc-id', 'replica:1', simple_doc), 10, 'T-1'), -            (self.make_document('doc-id2', 'replica:1', nested_doc), 11, -             'T-2')] -        new_gen, trans_id = self.st.sync_exchange( -            docs_by_gen, 'replica', last_known_generation=0, -            last_known_trans_id=None, return_doc_cb=self.receive_doc) -        self.assertGetDoc(self.db, 'doc-id', 'replica:1', simple_doc, False) -        self.assertGetDoc(self.db, 'doc-id2', 'replica:1', nested_doc, False) -        self.assertTransactionLog(['doc-id', 'doc-id2'], self.db) -        last_trans_id = self.getLastTransId(self.db) -        self.assertEqual(([], 2, last_trans_id), -                         (self.other_changes, new_gen, trans_id)) -        self.assertEqual(11, self.st.get_sync_info('replica')[3]) - -    def test_sync_exchange_refuses_conflicts(self): -        doc = self.db.create_doc_from_json(simple_doc) -        self.assertTransactionLog([doc.doc_id], self.db) -        new_doc = '{"key": "altval"}' -        docs_by_gen = [ -            (self.make_document(doc.doc_id, 'replica:1', new_doc), 10, -             'T-sid')] -        new_gen, _ = self.st.sync_exchange( -            docs_by_gen, 'replica', last_known_generation=0, -            last_known_trans_id=None, return_doc_cb=self.receive_doc) -        self.assertTransactionLog([doc.doc_id], self.db) -        self.assertEqual( -            (doc.doc_id, doc.rev, simple_doc, 1), self.other_changes[0][:-1]) -        self.assertEqual(1, new_gen) -        if self.whitebox: -            self.assertEqual(self.db._last_exchange_log['return'], -                             {'last_gen': 1, 'docs': [(doc.doc_id, doc.rev)]}) - -    def test_sync_exchange_ignores_convergence(self): -        doc = self.db.create_doc_from_json(simple_doc) -        self.assertTransactionLog([doc.doc_id], self.db) -        gen, txid = self.db._get_generation_info() -        docs_by_gen = [ -            (self.make_document(doc.doc_id, doc.rev, simple_doc), 10, 'T-sid')] -        new_gen, _ = self.st.sync_exchange( -            docs_by_gen, 'replica', last_known_generation=gen, -            last_known_trans_id=txid, return_doc_cb=self.receive_doc) -        self.assertTransactionLog([doc.doc_id], self.db) -        self.assertEqual(([], 1), (self.other_changes, new_gen)) - -    def test_sync_exchange_returns_new_docs(self): -        doc = self.db.create_doc_from_json(simple_doc) -        self.assertTransactionLog([doc.doc_id], self.db) -        new_gen, _ = self.st.sync_exchange( -            [], 'other-replica', last_known_generation=0, -            last_known_trans_id=None, return_doc_cb=self.receive_doc) -        self.assertTransactionLog([doc.doc_id], self.db) -        self.assertEqual( -            (doc.doc_id, doc.rev, simple_doc, 1), self.other_changes[0][:-1]) -        self.assertEqual(1, new_gen) -        if self.whitebox: -            self.assertEqual(self.db._last_exchange_log['return'], -                             {'last_gen': 1, 'docs': [(doc.doc_id, doc.rev)]}) - -    def test_sync_exchange_returns_deleted_docs(self): -        doc = self.db.create_doc_from_json(simple_doc) -        self.db.delete_doc(doc) -        self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db) -        new_gen, _ = self.st.sync_exchange( -            [], 'other-replica', last_known_generation=0, -            last_known_trans_id=None, return_doc_cb=self.receive_doc) -        self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db) -        self.assertEqual( -            (doc.doc_id, doc.rev, None, 2), self.other_changes[0][:-1]) -        self.assertEqual(2, new_gen) -        if self.whitebox: -            self.assertEqual(self.db._last_exchange_log['return'], -                             {'last_gen': 2, 'docs': [(doc.doc_id, doc.rev)]}) - -    def test_sync_exchange_getting_newer_docs(self): -        doc = self.db.create_doc_from_json(simple_doc) -        self.assertTransactionLog([doc.doc_id], self.db) -        new_doc = '{"key": "altval"}' -        docs_by_gen = [ -            (self.make_document(doc.doc_id, 'test:1|z:2', new_doc), 10, -             'T-sid')] -        new_gen, _ = self.st.sync_exchange( -            docs_by_gen, 'other-replica', last_known_generation=0, -            last_known_trans_id=None, return_doc_cb=self.receive_doc) -        self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db) -        self.assertEqual(([], 2), (self.other_changes, new_gen)) - -    def test_sync_exchange_with_concurrent_updates_of_synced_doc(self): -        expected = [] - -        def before_whatschanged_cb(state): -            if state != 'before whats_changed': -                return -            cont = '{"key": "cuncurrent"}' -            conc_rev = self.db.put_doc( -                self.make_document(doc.doc_id, 'test:1|z:2', cont)) -            expected.append((doc.doc_id, conc_rev, cont, 3)) - -        self.set_trace_hook(before_whatschanged_cb) -        doc = self.db.create_doc_from_json(simple_doc) -        self.assertTransactionLog([doc.doc_id], self.db) -        new_doc = '{"key": "altval"}' -        docs_by_gen = [ -            (self.make_document(doc.doc_id, 'test:1|z:2', new_doc), 10, -             'T-sid')] -        new_gen, _ = self.st.sync_exchange( -            docs_by_gen, 'other-replica', last_known_generation=0, -            last_known_trans_id=None, return_doc_cb=self.receive_doc) -        self.assertEqual(expected, [c[:-1] for c in self.other_changes]) -        self.assertEqual(3, new_gen) - -    def test_sync_exchange_with_concurrent_updates(self): - -        def after_whatschanged_cb(state): -            if state != 'after whats_changed': -                return -            self.db.create_doc_from_json('{"new": "doc"}') - -        self.set_trace_hook(after_whatschanged_cb) -        doc = self.db.create_doc_from_json(simple_doc) -        self.assertTransactionLog([doc.doc_id], self.db) -        new_doc = '{"key": "altval"}' -        docs_by_gen = [ -            (self.make_document(doc.doc_id, 'test:1|z:2', new_doc), 10, -             'T-sid')] -        new_gen, _ = self.st.sync_exchange( -            docs_by_gen, 'other-replica', last_known_generation=0, -            last_known_trans_id=None, return_doc_cb=self.receive_doc) -        self.assertEqual(([], 2), (self.other_changes, new_gen)) - -    def test_sync_exchange_converged_handling(self): -        doc = self.db.create_doc_from_json(simple_doc) -        docs_by_gen = [ -            (self.make_document('new', 'other:1', '{}'), 4, 'T-foo'), -            (self.make_document(doc.doc_id, doc.rev, doc.get_json()), 5, -             'T-bar')] -        new_gen, _ = self.st.sync_exchange( -            docs_by_gen, 'other-replica', last_known_generation=0, -            last_known_trans_id=None, return_doc_cb=self.receive_doc) -        self.assertEqual(([], 2), (self.other_changes, new_gen)) - -    def test_sync_exchange_detect_incomplete_exchange(self): -        def before_get_docs_explode(state): -            if state != 'before get_docs': -                return -            raise u1db_errors.U1DBError("fail") -        self.set_trace_hook(before_get_docs_explode) -        # suppress traceback printing in the wsgiref server -        # self.patch(simple_server.ServerHandler, -        #           'log_exception', lambda h, exc_info: None) -        doc = self.db.create_doc_from_json(simple_doc) -        self.assertTransactionLog([doc.doc_id], self.db) -        self.assertRaises( -            (u1db_errors.U1DBError, u1db_errors.BrokenSyncStream), -            self.st.sync_exchange, [], 'other-replica', -            last_known_generation=0, last_known_trans_id=None, -            return_doc_cb=self.receive_doc) - -    def test_sync_exchange_doc_ids(self): -        sync_exchange_doc_ids = getattr(self.st, 'sync_exchange_doc_ids', None) -        if sync_exchange_doc_ids is None: -            self.skipTest("sync_exchange_doc_ids not implemented") -        db2 = self.create_database('test2') -        doc = db2.create_doc_from_json(simple_doc) -        new_gen, trans_id = sync_exchange_doc_ids( -            db2, [(doc.doc_id, 10, 'T-sid')], 0, None, -            return_doc_cb=self.receive_doc) -        self.assertGetDoc(self.db, doc.doc_id, doc.rev, simple_doc, False) -        self.assertTransactionLog([doc.doc_id], self.db) -        last_trans_id = self.getLastTransId(self.db) -        self.assertEqual(([], 1, last_trans_id), -                         (self.other_changes, new_gen, trans_id)) -        self.assertEqual(10, self.st.get_sync_info(db2._replica_uid)[3]) - -    def test__set_trace_hook(self): -        called = [] - -        def cb(state): -            called.append(state) - -        self.set_trace_hook(cb) -        self.st.sync_exchange([], 'replica', 0, None, self.receive_doc) -        self.st.record_sync_info('replica', 0, 'T-sid') -        self.assertEqual(['before whats_changed', -                          'after whats_changed', -                          'before get_docs', -                          'record_sync_info', -                          ], -                         called) - -    def test__set_trace_hook_shallow(self): -        st_trace_shallow = self.st._set_trace_hook_shallow -        target_st_trace_shallow = SyncTarget._set_trace_hook_shallow -        same_meth = st_trace_shallow == self.st._set_trace_hook -        same_fun = st_trace_shallow.im_func == target_st_trace_shallow.im_func -        if (same_meth or same_fun): -            # shallow same as full -            expected = ['before whats_changed', -                        'after whats_changed', -                        'before get_docs', -                        'record_sync_info', -                        ] -        else: -            expected = ['sync_exchange', 'record_sync_info'] - -        called = [] - -        def cb(state): -            called.append(state) - -        self.set_trace_hook(cb, shallow=True) -        self.st.sync_exchange([], 'replica', 0, None, self.receive_doc) -        self.st.record_sync_info('replica', 0, 'T-sid') -        self.assertEqual(expected, called)  sync_scenarios = []  for name, scenario in COUCH_SCENARIOS: @@ -533,7 +20,11 @@ for name, scenario in COUCH_SCENARIOS:      scenario = dict(scenario) -class SoledadBackendSyncTests( +# ----------------------------------------------------------------------------- +# The following tests come from `u1db.tests.test_sync`. +# ----------------------------------------------------------------------------- + +class CouchBackendSyncTests(          TestWithScenarios,          DatabaseBaseTests,          CouchDBTestCase): @@ -1207,236 +698,3 @@ class SoledadBackendSyncTests(          self.sync(self.db1, self.db2)          self.assertEqual(cont1, self.db2.get_doc("1").get_json())          self.assertEqual(cont2, self.db1.get_doc("2").get_json()) - - -class SoledadBackendExceptionsTests(CouchDBTestCase): - -    def setUp(self): -        CouchDBTestCase.setUp(self) - -    def create_db(self, ensure=True, dbname=None): -        if not dbname: -            dbname = ('test-%s' % uuid4().hex) -        if dbname not in self.couch_server: -            self.couch_server.create(dbname) -        self.db = couch.CouchDatabase( -            ('http://127.0.0.1:%d' % self.couch_port), -            dbname, -            ensure_ddocs=ensure) - -    def tearDown(self): -        self.db.delete_database() -        self.db.close() -        CouchDBTestCase.tearDown(self) - -    def test_missing_design_doc_raises(self): -        """ -        Test that all methods that access design documents will raise if the -        design docs are not present. -        """ -        self.create_db(ensure=False) -        # get_generation_info() -        self.assertRaises( -            errors.MissingDesignDocError, -            self.db.get_generation_info) -        # get_trans_id_for_gen() -        self.assertRaises( -            errors.MissingDesignDocError, -            self.db.get_trans_id_for_gen, 1) -        # get_transaction_log() -        self.assertRaises( -            errors.MissingDesignDocError, -            self.db.get_transaction_log) -        # whats_changed() -        self.assertRaises( -            errors.MissingDesignDocError, -            self.db.whats_changed) - -    def test_missing_design_doc_functions_raises(self): -        """ -        Test that all methods that access design documents list functions -        will raise if the functions are not present. -        """ -        self.create_db(ensure=True) -        # erase views from _design/transactions -        transactions = self.db._database['_design/transactions'] -        transactions['lists'] = {} -        self.db._database.save(transactions) -        # get_generation_info() -        self.assertRaises( -            errors.MissingDesignDocListFunctionError, -            self.db.get_generation_info) -        # get_trans_id_for_gen() -        self.assertRaises( -            errors.MissingDesignDocListFunctionError, -            self.db.get_trans_id_for_gen, 1) -        # whats_changed() -        self.assertRaises( -            errors.MissingDesignDocListFunctionError, -            self.db.whats_changed) - -    def test_absent_design_doc_functions_raises(self): -        """ -        Test that all methods that access design documents list functions -        will raise if the functions are not present. -        """ -        self.create_db(ensure=True) -        # erase views from _design/transactions -        transactions = self.db._database['_design/transactions'] -        del transactions['lists'] -        self.db._database.save(transactions) -        # get_generation_info() -        self.assertRaises( -            errors.MissingDesignDocListFunctionError, -            self.db.get_generation_info) -        # _get_trans_id_for_gen() -        self.assertRaises( -            errors.MissingDesignDocListFunctionError, -            self.db.get_trans_id_for_gen, 1) -        # whats_changed() -        self.assertRaises( -            errors.MissingDesignDocListFunctionError, -            self.db.whats_changed) - -    def test_missing_design_doc_named_views_raises(self): -        """ -        Test that all methods that access design documents' named views  will -        raise if the views are not present. -        """ -        self.create_db(ensure=True) -        # erase views from _design/docs -        docs = self.db._database['_design/docs'] -        del docs['views'] -        self.db._database.save(docs) -        # erase views from _design/syncs -        syncs = self.db._database['_design/syncs'] -        del syncs['views'] -        self.db._database.save(syncs) -        # erase views from _design/transactions -        transactions = self.db._database['_design/transactions'] -        del transactions['views'] -        self.db._database.save(transactions) -        # get_generation_info() -        self.assertRaises( -            errors.MissingDesignDocNamedViewError, -            self.db.get_generation_info) -        # _get_trans_id_for_gen() -        self.assertRaises( -            errors.MissingDesignDocNamedViewError, -            self.db.get_trans_id_for_gen, 1) -        # _get_transaction_log() -        self.assertRaises( -            errors.MissingDesignDocNamedViewError, -            self.db.get_transaction_log) -        # whats_changed() -        self.assertRaises( -            errors.MissingDesignDocNamedViewError, -            self.db.whats_changed) - -    def test_deleted_design_doc_raises(self): -        """ -        Test that all methods that access design documents will raise if the -        design docs are not present. -        """ -        self.create_db(ensure=True) -        # delete _design/docs -        del self.db._database['_design/docs'] -        # delete _design/syncs -        del self.db._database['_design/syncs'] -        # delete _design/transactions -        del self.db._database['_design/transactions'] -        # get_generation_info() -        self.assertRaises( -            errors.MissingDesignDocDeletedError, -            self.db.get_generation_info) -        # get_trans_id_for_gen() -        self.assertRaises( -            errors.MissingDesignDocDeletedError, -            self.db.get_trans_id_for_gen, 1) -        # get_transaction_log() -        self.assertRaises( -            errors.MissingDesignDocDeletedError, -            self.db.get_transaction_log) -        # whats_changed() -        self.assertRaises( -            errors.MissingDesignDocDeletedError, -            self.db.whats_changed) - -    def test_ensure_ddoc_independently(self): -        """ -        Test that a missing ddocs other than _design/docs will be ensured -        even if _design/docs is there. -        """ -        self.create_db(ensure=True) -        del self.db._database['_design/transactions'] -        self.assertRaises( -            errors.MissingDesignDocDeletedError, -            self.db.get_transaction_log) -        self.create_db(ensure=True, dbname=self.db._dbname) -        self.db.get_transaction_log() - -    def test_ensure_security_doc(self): -        """ -        Ensure_security creates a _security ddoc to ensure that only soledad -        will have the lowest privileged access to an user db. -        """ -        self.create_db(ensure=False) -        self.assertFalse(self.db._database.resource.get_json('_security')[2]) -        self.db.ensure_security_ddoc() -        security_ddoc = self.db._database.resource.get_json('_security')[2] -        self.assertIn('admins', security_ddoc) -        self.assertFalse(security_ddoc['admins']['names']) -        self.assertIn('members', security_ddoc) -        self.assertIn('soledad', security_ddoc['members']['names']) - -    def test_ensure_security_from_configuration(self): -        """ -        Given a configuration, follow it to create the security document -        """ -        self.create_db(ensure=False) -        configuration = {'members': ['user1', 'user2'], -                         'members_roles': ['role1', 'role2'], -                         'admins': ['admin'], -                         'admins_roles': ['administrators'] -                         } -        self.db.ensure_security_ddoc(configuration) - -        security_ddoc = self.db._database.resource.get_json('_security')[2] -        self.assertEquals(configuration['admins'], -                          security_ddoc['admins']['names']) -        self.assertEquals(configuration['admins_roles'], -                          security_ddoc['admins']['roles']) -        self.assertEquals(configuration['members'], -                          security_ddoc['members']['names']) -        self.assertEquals(configuration['members_roles'], -                          security_ddoc['members']['roles']) - - -class DatabaseNameValidationTest(unittest.TestCase): - -    def test_database_name_validation(self): -        inject = couch.state.is_db_name_valid("user-deadbeef | cat /secret") -        self.assertFalse(inject) -        self.assertTrue(couch.state.is_db_name_valid("user-cafe1337")) - - -class CommandBasedDBCreationTest(unittest.TestCase): - -    def test_ensure_db_using_custom_command(self): -        state = couch.state.CouchServerState("url", create_cmd="echo") -        mock_db = Mock() -        mock_db.replica_uid = 'replica_uid' -        state.open_database = Mock(return_value=mock_db) -        db, replica_uid = state.ensure_database("user-1337")  # works -        self.assertEquals(mock_db, db) -        self.assertEquals(mock_db.replica_uid, replica_uid) - -    def test_raises_unauthorized_on_failure(self): -        state = couch.state.CouchServerState("url", create_cmd="inexistent") -        self.assertRaises(u1db_errors.Unauthorized, -                          state.ensure_database, "user-1337") - -    def test_raises_unauthorized_by_default(self): -        state = couch.state.CouchServerState("url") -        self.assertRaises(u1db_errors.Unauthorized, -                          state.ensure_database, "user-1337") diff --git a/testing/tests/couch/test_sync_target.py b/testing/tests/couch/test_sync_target.py new file mode 100644 index 00000000..e792fb76 --- /dev/null +++ b/testing/tests/couch/test_sync_target.py @@ -0,0 +1,343 @@ +import json + +from leap.soledad.common.l2db import SyncTarget +from leap.soledad.common.l2db import errors as u1db_errors + +from testscenarios import TestWithScenarios + +from test_soledad import u1db_tests as tests +from test_soledad.util import CouchDBTestCase +from test_soledad.util import make_local_db_and_target +from test_soledad.u1db_tests import DatabaseBaseTests + +from common import simple_doc +from common import nested_doc +from common import COUCH_SCENARIOS + + +target_scenarios = [ +    ('local', {'create_db_and_target': make_local_db_and_target}), ] + + +class CouchBackendSyncTargetTests( +        TestWithScenarios, +        DatabaseBaseTests, +        CouchDBTestCase): + +    # TODO: implement _set_trace_hook(_shallow) in CouchSyncTarget so +    #       skipped tests can be succesfully executed. + +    # whitebox true means self.db is the actual local db object +    # against which the sync is performed +    whitebox = True + +    scenarios = (tests.multiply_scenarios(COUCH_SCENARIOS, target_scenarios)) + +    def set_trace_hook(self, callback, shallow=False): +        setter = (self.st._set_trace_hook if not shallow else +                  self.st._set_trace_hook_shallow) +        try: +            setter(callback) +        except NotImplementedError: +            self.skipTest("%s does not implement _set_trace_hook" +                          % (self.st.__class__.__name__,)) + +    def setUp(self): +        CouchDBTestCase.setUp(self) +        # other stuff +        self.db, self.st = self.create_db_and_target(self) +        self.other_changes = [] + +    def tearDown(self): +        self.db.close() +        CouchDBTestCase.tearDown(self) + +    def receive_doc(self, doc, gen, trans_id): +        self.other_changes.append( +            (doc.doc_id, doc.rev, doc.get_json(), gen, trans_id)) + +    def test_sync_exchange_returns_many_new_docs(self): +        # This test was replicated to allow dictionaries to be compared after +        # JSON expansion (because one dictionary may have many different +        # serialized representations). +        doc = self.db.create_doc_from_json(simple_doc) +        doc2 = self.db.create_doc_from_json(nested_doc) +        self.assertTransactionLog([doc.doc_id, doc2.doc_id], self.db) +        new_gen, _ = self.st.sync_exchange( +            [], 'other-replica', last_known_generation=0, +            last_known_trans_id=None, return_doc_cb=self.receive_doc) +        self.assertTransactionLog([doc.doc_id, doc2.doc_id], self.db) +        self.assertEqual(2, new_gen) +        self.assertEqual( +            [(doc.doc_id, doc.rev, json.loads(simple_doc), 1), +             (doc2.doc_id, doc2.rev, json.loads(nested_doc), 2)], +            [c[:-3] + (json.loads(c[-3]), c[-2]) for c in self.other_changes]) +        if self.whitebox: +            self.assertEqual( +                self.db._last_exchange_log['return'], +                {'last_gen': 2, 'docs': +                 [(doc.doc_id, doc.rev), (doc2.doc_id, doc2.rev)]}) + +    def test_get_sync_target(self): +        self.assertIsNot(None, self.st) + +    def test_get_sync_info(self): +        self.assertEqual( +            ('test', 0, '', 0, ''), self.st.get_sync_info('other')) + +    def test_create_doc_updates_sync_info(self): +        self.assertEqual( +            ('test', 0, '', 0, ''), self.st.get_sync_info('other')) +        self.db.create_doc_from_json(simple_doc) +        self.assertEqual(1, self.st.get_sync_info('other')[1]) + +    def test_record_sync_info(self): +        self.st.record_sync_info('replica', 10, 'T-transid') +        self.assertEqual( +            ('test', 0, '', 10, 'T-transid'), self.st.get_sync_info('replica')) + +    def test_sync_exchange(self): +        docs_by_gen = [ +            (self.make_document('doc-id', 'replica:1', simple_doc), 10, +             'T-sid')] +        new_gen, trans_id = self.st.sync_exchange( +            docs_by_gen, 'replica', last_known_generation=0, +            last_known_trans_id=None, return_doc_cb=self.receive_doc) +        self.assertGetDoc(self.db, 'doc-id', 'replica:1', simple_doc, False) +        self.assertTransactionLog(['doc-id'], self.db) +        last_trans_id = self.getLastTransId(self.db) +        self.assertEqual(([], 1, last_trans_id), +                         (self.other_changes, new_gen, last_trans_id)) +        self.assertEqual(10, self.st.get_sync_info('replica')[3]) + +    def test_sync_exchange_deleted(self): +        doc = self.db.create_doc_from_json('{}') +        edit_rev = 'replica:1|' + doc.rev +        docs_by_gen = [ +            (self.make_document(doc.doc_id, edit_rev, None), 10, 'T-sid')] +        new_gen, trans_id = self.st.sync_exchange( +            docs_by_gen, 'replica', last_known_generation=0, +            last_known_trans_id=None, return_doc_cb=self.receive_doc) +        self.assertGetDocIncludeDeleted( +            self.db, doc.doc_id, edit_rev, None, False) +        self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db) +        last_trans_id = self.getLastTransId(self.db) +        self.assertEqual(([], 2, last_trans_id), +                         (self.other_changes, new_gen, trans_id)) +        self.assertEqual(10, self.st.get_sync_info('replica')[3]) + +    def test_sync_exchange_push_many(self): +        docs_by_gen = [ +            (self.make_document('doc-id', 'replica:1', simple_doc), 10, 'T-1'), +            (self.make_document('doc-id2', 'replica:1', nested_doc), 11, +             'T-2')] +        new_gen, trans_id = self.st.sync_exchange( +            docs_by_gen, 'replica', last_known_generation=0, +            last_known_trans_id=None, return_doc_cb=self.receive_doc) +        self.assertGetDoc(self.db, 'doc-id', 'replica:1', simple_doc, False) +        self.assertGetDoc(self.db, 'doc-id2', 'replica:1', nested_doc, False) +        self.assertTransactionLog(['doc-id', 'doc-id2'], self.db) +        last_trans_id = self.getLastTransId(self.db) +        self.assertEqual(([], 2, last_trans_id), +                         (self.other_changes, new_gen, trans_id)) +        self.assertEqual(11, self.st.get_sync_info('replica')[3]) + +    def test_sync_exchange_refuses_conflicts(self): +        doc = self.db.create_doc_from_json(simple_doc) +        self.assertTransactionLog([doc.doc_id], self.db) +        new_doc = '{"key": "altval"}' +        docs_by_gen = [ +            (self.make_document(doc.doc_id, 'replica:1', new_doc), 10, +             'T-sid')] +        new_gen, _ = self.st.sync_exchange( +            docs_by_gen, 'replica', last_known_generation=0, +            last_known_trans_id=None, return_doc_cb=self.receive_doc) +        self.assertTransactionLog([doc.doc_id], self.db) +        self.assertEqual( +            (doc.doc_id, doc.rev, simple_doc, 1), self.other_changes[0][:-1]) +        self.assertEqual(1, new_gen) +        if self.whitebox: +            self.assertEqual(self.db._last_exchange_log['return'], +                             {'last_gen': 1, 'docs': [(doc.doc_id, doc.rev)]}) + +    def test_sync_exchange_ignores_convergence(self): +        doc = self.db.create_doc_from_json(simple_doc) +        self.assertTransactionLog([doc.doc_id], self.db) +        gen, txid = self.db._get_generation_info() +        docs_by_gen = [ +            (self.make_document(doc.doc_id, doc.rev, simple_doc), 10, 'T-sid')] +        new_gen, _ = self.st.sync_exchange( +            docs_by_gen, 'replica', last_known_generation=gen, +            last_known_trans_id=txid, return_doc_cb=self.receive_doc) +        self.assertTransactionLog([doc.doc_id], self.db) +        self.assertEqual(([], 1), (self.other_changes, new_gen)) + +    def test_sync_exchange_returns_new_docs(self): +        doc = self.db.create_doc_from_json(simple_doc) +        self.assertTransactionLog([doc.doc_id], self.db) +        new_gen, _ = self.st.sync_exchange( +            [], 'other-replica', last_known_generation=0, +            last_known_trans_id=None, return_doc_cb=self.receive_doc) +        self.assertTransactionLog([doc.doc_id], self.db) +        self.assertEqual( +            (doc.doc_id, doc.rev, simple_doc, 1), self.other_changes[0][:-1]) +        self.assertEqual(1, new_gen) +        if self.whitebox: +            self.assertEqual(self.db._last_exchange_log['return'], +                             {'last_gen': 1, 'docs': [(doc.doc_id, doc.rev)]}) + +    def test_sync_exchange_returns_deleted_docs(self): +        doc = self.db.create_doc_from_json(simple_doc) +        self.db.delete_doc(doc) +        self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db) +        new_gen, _ = self.st.sync_exchange( +            [], 'other-replica', last_known_generation=0, +            last_known_trans_id=None, return_doc_cb=self.receive_doc) +        self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db) +        self.assertEqual( +            (doc.doc_id, doc.rev, None, 2), self.other_changes[0][:-1]) +        self.assertEqual(2, new_gen) +        if self.whitebox: +            self.assertEqual(self.db._last_exchange_log['return'], +                             {'last_gen': 2, 'docs': [(doc.doc_id, doc.rev)]}) + +    def test_sync_exchange_getting_newer_docs(self): +        doc = self.db.create_doc_from_json(simple_doc) +        self.assertTransactionLog([doc.doc_id], self.db) +        new_doc = '{"key": "altval"}' +        docs_by_gen = [ +            (self.make_document(doc.doc_id, 'test:1|z:2', new_doc), 10, +             'T-sid')] +        new_gen, _ = self.st.sync_exchange( +            docs_by_gen, 'other-replica', last_known_generation=0, +            last_known_trans_id=None, return_doc_cb=self.receive_doc) +        self.assertTransactionLog([doc.doc_id, doc.doc_id], self.db) +        self.assertEqual(([], 2), (self.other_changes, new_gen)) + +    def test_sync_exchange_with_concurrent_updates_of_synced_doc(self): +        expected = [] + +        def before_whatschanged_cb(state): +            if state != 'before whats_changed': +                return +            cont = '{"key": "cuncurrent"}' +            conc_rev = self.db.put_doc( +                self.make_document(doc.doc_id, 'test:1|z:2', cont)) +            expected.append((doc.doc_id, conc_rev, cont, 3)) + +        self.set_trace_hook(before_whatschanged_cb) +        doc = self.db.create_doc_from_json(simple_doc) +        self.assertTransactionLog([doc.doc_id], self.db) +        new_doc = '{"key": "altval"}' +        docs_by_gen = [ +            (self.make_document(doc.doc_id, 'test:1|z:2', new_doc), 10, +             'T-sid')] +        new_gen, _ = self.st.sync_exchange( +            docs_by_gen, 'other-replica', last_known_generation=0, +            last_known_trans_id=None, return_doc_cb=self.receive_doc) +        self.assertEqual(expected, [c[:-1] for c in self.other_changes]) +        self.assertEqual(3, new_gen) + +    def test_sync_exchange_with_concurrent_updates(self): + +        def after_whatschanged_cb(state): +            if state != 'after whats_changed': +                return +            self.db.create_doc_from_json('{"new": "doc"}') + +        self.set_trace_hook(after_whatschanged_cb) +        doc = self.db.create_doc_from_json(simple_doc) +        self.assertTransactionLog([doc.doc_id], self.db) +        new_doc = '{"key": "altval"}' +        docs_by_gen = [ +            (self.make_document(doc.doc_id, 'test:1|z:2', new_doc), 10, +             'T-sid')] +        new_gen, _ = self.st.sync_exchange( +            docs_by_gen, 'other-replica', last_known_generation=0, +            last_known_trans_id=None, return_doc_cb=self.receive_doc) +        self.assertEqual(([], 2), (self.other_changes, new_gen)) + +    def test_sync_exchange_converged_handling(self): +        doc = self.db.create_doc_from_json(simple_doc) +        docs_by_gen = [ +            (self.make_document('new', 'other:1', '{}'), 4, 'T-foo'), +            (self.make_document(doc.doc_id, doc.rev, doc.get_json()), 5, +             'T-bar')] +        new_gen, _ = self.st.sync_exchange( +            docs_by_gen, 'other-replica', last_known_generation=0, +            last_known_trans_id=None, return_doc_cb=self.receive_doc) +        self.assertEqual(([], 2), (self.other_changes, new_gen)) + +    def test_sync_exchange_detect_incomplete_exchange(self): +        def before_get_docs_explode(state): +            if state != 'before get_docs': +                return +            raise u1db_errors.U1DBError("fail") +        self.set_trace_hook(before_get_docs_explode) +        # suppress traceback printing in the wsgiref server +        # self.patch(simple_server.ServerHandler, +        #           'log_exception', lambda h, exc_info: None) +        doc = self.db.create_doc_from_json(simple_doc) +        self.assertTransactionLog([doc.doc_id], self.db) +        self.assertRaises( +            (u1db_errors.U1DBError, u1db_errors.BrokenSyncStream), +            self.st.sync_exchange, [], 'other-replica', +            last_known_generation=0, last_known_trans_id=None, +            return_doc_cb=self.receive_doc) + +    def test_sync_exchange_doc_ids(self): +        sync_exchange_doc_ids = getattr(self.st, 'sync_exchange_doc_ids', None) +        if sync_exchange_doc_ids is None: +            self.skipTest("sync_exchange_doc_ids not implemented") +        db2 = self.create_database('test2') +        doc = db2.create_doc_from_json(simple_doc) +        new_gen, trans_id = sync_exchange_doc_ids( +            db2, [(doc.doc_id, 10, 'T-sid')], 0, None, +            return_doc_cb=self.receive_doc) +        self.assertGetDoc(self.db, doc.doc_id, doc.rev, simple_doc, False) +        self.assertTransactionLog([doc.doc_id], self.db) +        last_trans_id = self.getLastTransId(self.db) +        self.assertEqual(([], 1, last_trans_id), +                         (self.other_changes, new_gen, trans_id)) +        self.assertEqual(10, self.st.get_sync_info(db2._replica_uid)[3]) + +    def test__set_trace_hook(self): +        called = [] + +        def cb(state): +            called.append(state) + +        self.set_trace_hook(cb) +        self.st.sync_exchange([], 'replica', 0, None, self.receive_doc) +        self.st.record_sync_info('replica', 0, 'T-sid') +        self.assertEqual(['before whats_changed', +                          'after whats_changed', +                          'before get_docs', +                          'record_sync_info', +                          ], +                         called) + +    def test__set_trace_hook_shallow(self): +        st_trace_shallow = self.st._set_trace_hook_shallow +        target_st_trace_shallow = SyncTarget._set_trace_hook_shallow +        same_meth = st_trace_shallow == self.st._set_trace_hook +        same_fun = st_trace_shallow.im_func == target_st_trace_shallow.im_func +        if (same_meth or same_fun): +            # shallow same as full +            expected = ['before whats_changed', +                        'after whats_changed', +                        'before get_docs', +                        'record_sync_info', +                        ] +        else: +            expected = ['sync_exchange', 'record_sync_info'] + +        called = [] + +        def cb(state): +            called.append(state) + +        self.set_trace_hook(cb, shallow=True) +        self.st.sync_exchange([], 'replica', 0, None, self.receive_doc) +        self.st.record_sync_info('replica', 0, 'T-sid') +        self.assertEqual(expected, called) diff --git a/testing/tests/client/hacker_crackdown.txt b/testing/tests/sqlcipher/hacker_crackdown.txt index a01eb509..a01eb509 100644 --- a/testing/tests/client/hacker_crackdown.txt +++ b/testing/tests/sqlcipher/hacker_crackdown.txt diff --git a/testing/tests/sqlcipher/test_sqlcipher.py b/testing/tests/sqlcipher/test_backend.py index 11472d46..11472d46 100644 --- a/testing/tests/sqlcipher/test_sqlcipher.py +++ b/testing/tests/sqlcipher/test_backend.py | 
