From 7ea0db5a187f5271cb1356196e383a3fa88d7771 Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Mon, 25 Nov 2024 15:48:37 -0800 Subject: [PATCH] CBL-6495: Fix missing roles when pushing tasks in test-fest tests (#101) * Problem: The contributor role required by the task in the tasks' sync function is assigned to the user when the list is pushed in lists' sync function. However, the list doc and its task docs are pushed independenly and there is no order guaranteed that the list will be pushed first. As a result, if the task doc is pushed first, the task doc will be rejected by the sync function as the contributor for the user is missing. * Instead of using sync function to assign the contributor role to the user, assign the required roles to the users using SG REST API at the beginning of the test where the roles are being created now. * Sync test and test spec --- client/src/cbltest/api/cloud.py | 3 + client/src/cbltest/api/syncgateway.py | 12 ++ dataset/sg/todo-sg-config.json | 2 +- spec/tests/005-test-fest.md | 284 ++++++++++++++------------ tests/test_fest.py | 178 +++++++++------- 5 files changed, 280 insertions(+), 199 deletions(-) diff --git a/client/src/cbltest/api/cloud.py b/client/src/cbltest/api/cloud.py index 4346f6c5..e9544a89 100644 --- a/client/src/cbltest/api/cloud.py +++ b/client/src/cbltest/api/cloud.py @@ -39,6 +39,9 @@ def _wait_for_all_indexed_removed(self, bucket: str) -> None: async def create_role(self, db_name: str, role: str, collection_access: dict) -> None: await self.__sync_gateway.add_role(db_name, role, collection_access) + async def assign_roles(self, db_name: str, name: str, roles: List[str]) -> None: + await self.__sync_gateway.assign_roles(db_name, name, roles) + async def configure_dataset(self, dataset_path: Path, dataset_name: str, sg_config_options: Optional[List[str]] = None) -> None: """ diff --git a/client/src/cbltest/api/syncgateway.py b/client/src/cbltest/api/syncgateway.py index 0fa3c6d5..50efd421 100644 --- a/client/src/cbltest/api/syncgateway.py +++ b/client/src/cbltest/api/syncgateway.py @@ -463,6 +463,18 @@ async def add_role(self, db_name: str, role: str, collection_access: dict) -> No else: raise + async def assign_roles(self, db_name: str, name: str, roles: List[str]) -> None: + """ + Assign the roles to a user. + + :param db_name: The name of the Database. + :param name: The username to assign the roles to. + :param roles: A list of roles to be assigned. + """ + with self.__tracer.start_as_current_span("assign_role", attributes={"cbl.user.name": name}): + body = { "admin_roles": roles } + await self._send_request("put", f"/{db_name}/_user/{name}", JSONDictionary(body)) + def _analyze_dataset_response(self, response: list) -> None: assert isinstance(response, list), "Invalid bulk docs response (not a list)" typed_response = cast(list, response) diff --git a/dataset/sg/todo-sg-config.json b/dataset/sg/todo-sg-config.json index 206b6c86..de35e783 100644 --- a/dataset/sg/todo-sg-config.json +++ b/dataset/sg/todo-sg-config.json @@ -5,7 +5,7 @@ "_default": { "collections": { "lists": { - "sync": "function foo(doc,oldDoc,meta){var owner=doc._deleted?oldDoc.owner:doc.owner;requireUser(owner);var listChannel='lists.'+owner+'.'+doc._id;var contributorRole='role:'+listChannel+'.contributor';role(owner,contributorRole);access(contributorRole,listChannel);channel(listChannel)}" + "sync": "function foo(doc,oldDoc,meta){var owner=doc._deleted?oldDoc.owner:doc.owner;requireUser(owner);var listChannel='lists.'+owner+'.'+doc._id;var contributorRoleName=listChannel+'.contributor';var contributorRole='role:'+contributorRoleName;requireRole(contributorRoleName);access(contributorRole,listChannel);channel(listChannel)}" }, "tasks": { "sync": "function foo(doc,oldDoc,meta){var listId=doc._deleted?oldDoc.taskList.id:doc.taskList.id;var listOwner=doc._deleted?oldDoc.taskList.owner:doc.taskList.owner;var listChannel='lists.'+listOwner+'.'+listId;var contributorRoleName=listChannel+'.contributor';var contributorRole='role:'+contributorRoleName;requireRole(contributorRoleName);var tasksChannel=listChannel+'.tasks';access(contributorRole,tasksChannel);channel(tasksChannel)}" diff --git a/spec/tests/005-test-fest.md b/spec/tests/005-test-fest.md index 78e062cb..5f3dd96f 100644 --- a/spec/tests/005-test-fest.md +++ b/spec/tests/005-test-fest.md @@ -11,9 +11,18 @@ the availability of the test servers. ### Steps -1. Reset SG and load `todo` dataset. -2. Reset local database, and load `todo` dataset into database `db1` and `db2`. -3. Start a replicator: +1. Setup test env and assign SG roles to users: + * Reset SG and load `todo` dataset. + * Reset local database, and load `todo` dataset into database `db1` and `db2`. + * Assign SG roles to users: + * user1 : `lists.user1.db1-list1.contributor`, `lists.user1.db2-list1.contributor` +2. Snapshot db1: + * `_default.lists`.`db2-list1` + * `_default.tasks`.`db2-list1-task1` +3. Snapshot db2: + * `_default.lists`.`db1-list1` + * `_default.tasks`.`db1-list1-task1` +4. Start a replicator: * endpoint: `/todo` * database: `db1` * collections : `_default.lists`, `_default.tasks`, `_default.users` @@ -21,7 +30,7 @@ the availability of the test servers. * continuous: true * enableDocumentListener: true * credentials: user1/pass -4. Start a replicator: +5. Start a replicator: * endpoint: `/todo` * database: `db2` * collections : `_default.lists`, `_default.tasks`, `_default.users` @@ -29,34 +38,26 @@ the availability of the test servers. * continuous: true * enableDocumentListener: true * credentials: user1/pass -5. Snapshot documents in `db1`: - * `_default.lists`.`db2-list1` - * `_default.tasks`.`db2-list1-task1` -6. Snapshot documents in `db2` - * `_default.lists`.`db1-list1` - * `_default.tasks`.`db1-list1-task1` -7. Create a list and a task in `db1`: - * Create SG role named `lists.user1.db1-list1.contributor`. +6. Create a list and a task in db1: * Create a list document in `_default.lists` as * { "_id": "db1-list1", "name": "db1 list1", "owner": "user1" } * Create a task document in `_default.tasks` as * { "_id": "db1-list1-task1", "name": "db1 list1 task1", "complete": true, "image": null, "taskList" : { "id" : "db1-list1", "owner" : "user1" } } * Set the `image` key with the `l1.jpg` blob. -8. Create a list and a task in `db2`: - * Create SG role named `lists.user1.db2-list1.contributor`. +7. Create a list and a task in db2: * Create a list document in `_default.lists` as * { "_id": "db2-list1", "name": "db2 list1", "owner": "user1" } * Create a task document in `_default.tasks` as * { "_id": "db2-list1-task1", "name": "db2 list1 task1", "complete": true, "image": null, taskList" : { "id" : "db2-list1", "owner" : "user1" } } * Set the `image` key with the `l2.jpg` blob. -9. Wait and check the pull document replication events in `db1`: +8. Wait for the new docs to be pulled to db1: * `_default.lists`.`db2-list1` * `_default.tasks`.`db2-list1-task1` -10. Wait and check the pull document replication events in `db2`: +9. Wait for the new docs to be pulled to db2: * `_default.lists`.`db1-list1` * `_default.tasks`.`db1-list1-task1` -11. Verify the snapshot from step 5 by checking the content of the snapshot documents. -12. Verify the snapshot from step 6 by checking the content of the snapshot documents. +10. Verify that the new docs are in db1. +11. Verify that the new docs are in db2. ## test_update_task @@ -69,8 +70,14 @@ the availability of the test servers. ### Steps -1. Reset SG and load `todo` dataset. -2. Reset local database, and load `todo` dataset into database `db1` and `db2`. +1. Setup test env and assign SG roles to users: + * Reset SG and load `todo` dataset. + * Reset local database, and load `todo` dataset into database `db1` and `db2`. + * Assign SG roles to users: + * user1 : `lists.user1.db1-list1.contributor` +2. Snapshot db2: + * `_default.lists`.`db1-list1` + * `_default.tasks`.`db1-list1-task1` 3. Start a replicator: * endpoint: `/todo` * database: `db1` @@ -87,30 +94,26 @@ the availability of the test servers. * continuous: true * enableDocumentListener: true * credentials: user1/pass -5. Snapshot documents in `db2`: - * `_default.lists`.`db1-list1` - * `_default.tasks`.`db1-list1-task1` -6. Create a list and a task in `db1`: - * Create SG role named `lists.user1.db1-list1.contributor`. +5. Create a list and a task in db1: * Create a list document in `_default.lists` as * { "_id": "db1-list1", "name": "db1 list1", "owner": "user1" } * Create a task document in `_default.tasks` as * { "_id": "db1-list1-task1", "name": "db1 list1 task1", "complete": false, "image": null, "taskList" : { "id" : "db1-list1", "owner" : "user1" } } * Set the `image` key with the `l5.jpg` blob. -7. Wait and check the pull document replication events in `db2`: +6. Wait for the new docs to be pulled to db2: * `_default.lists`.`db1-list1` * `_default.tasks`.`db1-list1-task1` -8. Verify the snapshot from step 5 by checking the content of the snapshot documents. -9. Snapshot documents in `db1`: +7. Verify that the new docs are in db2: +8. Snapshot db1: * `_default.lists`.`db1-list1` * `_default.tasks`.`db1-list1-task1` -10. Update the `db1-list1-task1` task in `db2`: +9. Update the db1-list1-task1 task in db2: * Set the `name` key with `Updated db1 list1 task1`. * Set the `complete` key with `true`. * Set the `image` key with the `l10.jpg` blob. -11. Wait and check the pull document replication events in `db1`: +10. Wait for the new doc to be pulled to db1: * `_default.tasks`.`db1-list1-task1` -12. Verify the snapshot from step 9: +11. Verify that the doc has been updated in db1: * Check that `_default.lists`.`db1-list1` has no changes. * Check the content of `_default.tasks`.`db1-list1-task1`. @@ -125,9 +128,12 @@ the availability of the test servers. ### Steps -1. Reset SG and load `todo` dataset. -2. Reset local database, and load `todo` dataset into database `db1` and `db2`. -3. Start a replicator: +1. Setup test env and assign SG roles to users: + * Reset SG and load `todo` dataset. + * Reset local database, and load `todo` dataset into database `db1` and `db2`. + * Assign SG roles to users: + * user1 : `lists.user1.db1-list1.contributor` +2. Start a replicator: * endpoint: `/todo` * database: `db1` * collections : `_default.lists`, `_default.tasks`, `_default.users` @@ -135,7 +141,7 @@ the availability of the test servers. * continuous: true * enableDocumentListener: true * credentials: user1/pass -4. Start a replicator: +3. Start a replicator: * endpoint: `/todo` * database: `db2` * collections : `_default.lists`, `_default.tasks`, `_default.users` @@ -143,21 +149,26 @@ the availability of the test servers. * continuous: true * enableDocumentListener: true * credentials: user1/pass -5. Create a list and a task in `db1`: - * Create SG role named `lists.user1.db1-list1.contributor`. +4. Create a list and a task in db1: * Create a list document in `_default.lists` as * { "_id": "db1-list1", "name": "db1 list1", "owner": "user1" } * Create a task document in `_default.tasks` as * { "_id": "db1-list1-task1", "name": "db1 list1 task1", "complete": false, "image": null, "taskList" : { "id" : "db1-list1", "owner" : "user1" } } * Set the `image` key with the `l1.jpg` blob. -6. Wait and check the pull document replication events in `db2` +5. Wait for the updated doc to be pulled to db2: * `_default.lists`.`db1-list1` * `_default.tasks`.`db1-list1-task1` -7. Delete the `_default.tasks`.`db1-list1-task1` task in `db1` -8. Wait and check the pull deleted document replication event in `db2` +6. Snapshot documents in db1: + * `_default.lists`.`db1-list1` + * `_default.tasks`.`db1-list1-task1` +7. Snapshot documents in db2: + * `_default.lists`.`db1-list1` + * `_default.tasks`.`db1-list1-task1` +8. Delete the task _default.tasks.db1-list1-task1 in db1. +9. Wait for the deleted document to be pulled to db2: * `_default.tasks`.`db1-list1-task1` (Deleted) -9. Check that `_default.tasks`.`db1-list1-task1` was deleted from `db1`. -10. Check that `_default.tasks`.`db1-list1-task1` was deleted from `db2`. +10. Verify that _default.tasks.db1-list1-task1 was deleted from db1. +11. Verify that _default.tasks.db1-list1-task1 was deleted from db2. ## test_delete_list @@ -170,9 +181,12 @@ the availability of the test servers. ### Steps -1. Reset SG and load `todo` dataset. -2. Reset local database, and load `todo` dataset into database `db1` and `db2`. -3. Start a replicator: +1. Setup test env and assign SG roles to users: + * Reset SG and load `todo` dataset. + * Reset local database, and load `todo` dataset into database `db1` and `db2`. + * Assign SG roles to users: + * user1 : `lists.user1.db1-list1.contributor` +2. Start a replicator: * endpoint: `/todo` * database: `db1` * collections : `_default.lists`, `_default.tasks`, `_default.users` @@ -180,7 +194,7 @@ the availability of the test servers. * continuous: true * enableDocumentListener: true * credentials: user1/pass -4. Start a replicator: +3. Start a replicator: * endpoint: `/todo` * database: `db2` * collections : `_default.lists`, `_default.tasks`, `_default.users` @@ -188,8 +202,7 @@ the availability of the test servers. * continuous: true * enableDocumentListener: true * credentials: user1/pass -5. Create a list and two tasks in `db1`: - * Create SG role named `lists.user1.db1-list1.contributor`. +4. Create a list and two tasks in db1: * Create a list document in `_default.lists` as * { "_id": "db1-list1", "name": "db1 list1", "owner": "user1" } * Create a task document in `_default.tasks` as @@ -197,20 +210,24 @@ the availability of the test servers. * Set the `image` key with the `s1.jpg` blob. * Create a task document in `_default.tasks` as * { "_id": "db1-list1-task2", "name": "db1 list1 task2", "complete": true, "image": null, "taskList" : { "id" : "db1-list1", "owner" : "user1" } } -6. Wait and check the pull document replication events in `db2` +5. Snapshot documents in db1: * `_default.lists`.`db1-list1` * `_default.tasks`.`db1-list1-task1` * `_default.tasks`.`db1-list1-task2` -7. Delete the list and its tasks from `db1`. +6. Snapshot documents in `db2`: + * `_default.lists`.`db1-list1` + * `_default.tasks`.`db1-list1-task1` + * `_default.tasks`.`db1-list1-task2` +7. Delete the the list1 and the two tasks in db1. * Delete `_default.lists`.`db1-list1` * Delete `_default.tasks`.`db1-list1-task1` * Delete `_default.tasks`.`db1-list1-task2` -8. Wait and check the pull document replication events in `db2`. +8. Wait for the deleted documents to be pulled to db2. * `_default.lists`.`db1-list1` (deleted) * `_default.tasks`.`db1-list1-task1` (deleted) * `_default.tasks`.`db1-list1-task2` (deleted) -9. Check that the list and two tasks are deleted from `db1`. -10. Check that the list and two tasks are deleted from `db2`. +9. Verify that the list and two tasks were deleted from db1. +10. Verify that the list and two tasks were deleted from db2. ## test_create_tasks_two_users @@ -223,9 +240,13 @@ the availability of the test servers. ### Steps -1. Reset SG and load `todo` dataset. -2. Reset local database, and load `todo` dataset into database `db1` and `db2`. -3. Start a replicator: +1. Setup test env and assign SG roles to users: + * Reset SG and load `todo` dataset. + * Reset local database, and load `todo` dataset into database `db1` and `db2`. + * Assign SG roles to users: + * user1 : `lists.user1.db1-list1.contributor` + * user2 : `lists.user1.db2-list1.contributor` +2. Start a replicator: * endpoint: `/todo` * database: `db1` * collections : `_default.lists`, `_default.tasks`, `_default.users` @@ -233,7 +254,7 @@ the availability of the test servers. * continuous: true * enableDocumentListener: true * credentials: user1/pass -4. Start a replicator: +3. Start a replicator: * endpoint: `/todo` * database: `db2` * collections : `_default.lists`, `_default.tasks`, `_default.users` @@ -241,25 +262,33 @@ the availability of the test servers. * continuous: true * enableDocumentListener: true * credentials: user2/pass -5. Create a list and a task in `db1`: - * Create SG role named `lists.user1.db1-list1.contributor`. +4. Create a list and a task in db1: * Create a list document in `_default.lists` as * { "_id": "db1-list1", "name": "db1 list1", "owner": "user1" } * Create a task document in `_default.tasks` as * { "_id": "db1-list1-task1", "name": "db1 list1 task1", "complete": false, "image": null, "taskList" : { "id" : "db1-list1", "owner" : "user1" } } * Set the `image` key with the `l1.jpg` blob. -6. Create a list and a task in `db2`: +7. Snapshot documents in db1: + * `_default.lists`.`db1-list1` + * `_default.tasks`.`db1-list1-task1` + * `_default.lists`.`db2-list1` + * `_default.tasks`.`db2-list1-task1` +8. Create a list and a task in db2: * Create SG role named `lists.user2.db2-list1.contributor`. * Create a list document in `_default.lists` as * { "_id": "db2-list1", "name": "db2 list1", "owner": "user2" } * Create a task document in `_default.tasks` as * { "_id": "db2-list1-task1", "name": "db2 list1 task1", "complete": true, "image": null, "taskList" : { "id" : "db2-list1", "owner" : "user2" } } * Set the `image` key with the `l2.jpg` blob. -7. Wait for 10 seconds which should be enough for the two replicators to finish replicating. -8. Check that no document replication events are in `db1`. -9. Check that no document replication events are in `db2`. -10. Check that `db1` has only `_default.lists`.`db1-list1` and `_default.tasks`.`db1-list1-task1`. -11. Check that `db2` has only `_default.lists`.`db2-list1` and `_default.tasks`.`db2-list1-task1`. +9. Snapshot documents in db2: + * `_default.lists`.`db1-list1` + * `_default.tasks`.`db1-list1-task1` + * `_default.lists`.`db2-list1` + * `_default.tasks`.`db2-list1-task1` +10. Verify that there are no document replication events in db1, for 10 seconds. +11. Verify that there are no document replication events in db2, for 10 seconds. +12. Verify that db1 has not changed. +13. Verify that db2 has not changed. ## test_share_list @@ -270,9 +299,12 @@ Test sharing a list and its tasks to a user. This test will use two databases either on the same test server or on two test servers depending on the availability of the test servers. -1. Reset SG and load `todo` dataset. -2. Reset local database, and load `todo` dataset into database `db1` and `db2`. -3. Start a replicator: +1. Setup test env and assign SG roles to users: + * Reset SG and load `todo` dataset. + * Reset local database, and load `todo` dataset into database `db1` and `db2`. + * Assign SG roles to users: + * user1 : `lists.user1.db1-list1.contributor` +2. Start a replicator: * endpoint: `/todo` * database: `db1` * collections : `_default.lists`, `_default.tasks`, `_default.users` @@ -280,7 +312,7 @@ the availability of the test servers. * continuous: true * enableDocumentListener: true * credentials: user1/pass -4. Start a replicator: +3. Start a replicator: * endpoint: `/todo` * database: `db2` * collections : `_default.lists`, `_default.tasks`, `_default.users` @@ -288,8 +320,11 @@ the availability of the test servers. * continuous: true * enableDocumentListener: true * credentials: user2/pass -5. Create a list and 2 tasks in `db1: - * Create SG role named `lists.user1.db1-list1.contributor`. +4. Snapshot documents in db2: + * `_default.lists`.`db1-list1` + * `_default.tasks`.`db1-list1-task1` + * `_default.tasks`.`db1-list1-task2` +5. Create a list and two tasks in db1: * Create a list document in `_default.lists` as * { "_id": "db1-list1", "name": "db1 list1", "owner": "user1" } * Create a task document in `_default.tasks` as @@ -297,31 +332,28 @@ the availability of the test servers. * Set the `image` key with the `l1.jpg` blob. * Create a task document in `_default.tasks` as * { "_id": "db1-list1-task2", "name": "db1 list1 task2", "complete": true, "image": null, "taskList" : { "id" : "db1-list1", "owner" : "user1" } } -6. Wait for 10 seconds which should be enough for the two replicators to finish replicating. -7. Check that no document replication events are in `db2`. -8. Snapshot documents in `db2`: - * `_default.lists`.`db1-list1` - * `_default.tasks`.`db1-list1-task1` - * `_default.tasks`.`db1-list1-task2` -9. Creating a user document in the `_default.users` to share the `_default.lists`.`db1-list1` list in `db1` as +6. Verify that there are no document replication events in db2, for 10 seconds. +7. Verify that there are no new documents in db2. +8. Create a user document to share the _default.lists.db1-list1 from db1: * { "_id": "db1-list1-user2", "username": "user2", "taskList" : { "id" : "db1-list1", "owner" : "user1" } } -10. Wait and check the pull document replication events in `db2`. +9. Wait for the the newly visible documents to be pulled to db2: * `_default.lists`.`db1-list1` * `_default.tasks`.`db1-list1-task1` * `_default.tasks`.`db1-list1-task2` -11. Verify snapshot from step 8 to check the content of the snapshot documents. -12. Check that no `_default.users`.`db1-list1-user2` document is in `db2`. +10. Verify snapshot that the new docs are in db2 and that _default.users.db1-list1-user2 is not. ## test_update_shared_tasks Test updating shared tasks. -This test will use two databases either on the same test server or on two test servers depending on -the availability of the test servers. +This test will use two databases either on the same test server or on two test servers depending on the availability of the test servers. -1. Reset SG and load `todo` dataset. -2. Reset local database, and load `todo` dataset into database `db1` and `db2`. -3. Start a replicator: +1. Setup test env and assign SG roles to users: + * Reset SG and load `todo` dataset. + * Reset local database, and load `todo` dataset into database `db1` and `db2`. + * Assign SG roles to users: + * user1 : `lists.user1.db1-list1.contributor` +2. Start a replicator: * endpoint: `/todo` * database: `db1` * collections : `_default.lists`, `_default.tasks`, `_default.users` @@ -329,7 +361,7 @@ the availability of the test servers. * continuous: true * enableDocumentListener: true * credentials: user1/pass -4. Start a replicator: +3. Start a replicator: * endpoint: `/todo` * database: `db2` * collections : `_default.lists`, `_default.tasks`, `_default.users` @@ -337,8 +369,12 @@ the availability of the test servers. * continuous: true * enableDocumentListener: true * credentials: user2/pass -5. Create a list and 2 tasks in `db1`: - * Create SG role named `lists.user1.db1-list1.contributor`. +4. Snapshot documents in db2: + * `_default.lists`.`db1-list1` + * `_default.tasks`.`db1-list1-task1` + * `_default.tasks`.`db1-list1-task2` + * `_default.users`.`db1-list1-user2` +5. Create a list and two tasks in db1: * Create a list document in `_default.lists` as * { "_id": "db1-list1", "name": "db1 list1", "owner": "user1" } * Create a task document in `_default.tasks` as @@ -347,32 +383,26 @@ the availability of the test servers. * Create a task document in `_default.tasks` as * { "_id": "db1-list1-task2", "name": "db1 list1 task2", "complete": false, "image": null, "taskList" : { "id" : "db1-list1", "owner" : "user1" } } * Set the `image` key with the `s1.jpg` blob. -6. Wait for 10 seconds which should be enough for the two replicators to finish replicating. -7. Check that no document replication events are in `db2`. -8. Snapshot documents in `db2`: - * `_default.lists`.`db1-list1` +6. Snapshot documents in db1: * `_default.tasks`.`db1-list1-task1` * `_default.tasks`.`db1-list1-task2` -9. Creating a user document in the `_default.users` to share the `_default.lists`.`db1-list1` list in `db1` as +7. Verify that there are no document replication events in db2, for 10 seconds. +8. Create a user document to share the _default.lists.db1-list1 from db1: * { "_id": "db1-list1-user2", "username": "user2", "taskList" : { "id" : "db1-list1", "owner" : "user1" } } -10. Wait and check the pull document replication events in `db2`. +9. Wait for the newly visible docs to be pulled to db2: * `_default.lists`.`db1-list1` * `_default.tasks`.`db1-list1-task1` * `_default.tasks`.`db1-list1-task2` -11. Verify snapshot from step 8 to check the content of the snapshot documents. -12. Check that no `_default.users`.`db1-list1-user2` document is in `db2` -13. Snapshot documents in `db1`: - * `_default.tasks`.`db1-list1-task1` - * `_default.tasks`.`db1-list1-task2` -14. Update `_default.tasks`.`db1-list1-task1` in `db2`: +10. Verify that the newly visible docs are in db2 and that _default.users.db1-list1-user2 is not. +11. Update _default.tasks.db1-list1-task1 and delete db1-list1-task2 in db2: * Set the `name` key with `Updated db1 list1 task1`. * Set the `complete` key with `true`. * Set the `image` key with the `s1.jpg` blob. -15. Delete `_default.tasks`.`db1-list1-task2` in `db2` -16. Wait and check the pull replication events in `db1`: + * Delete `_default.tasks`.`db1-list1-task2` in `db2` +12. Wait for the changes to be pulled to db1: * `_default.tasks`.`db1-list1-task1` * `_default.tasks`.`db1-list1-task2` (deleted) -17. Verify snapshot from step 12: +13. Verify that the new docs are in db1: * Check the updated content of `_default.tasks`.`db1-list1-task1` document * Check that the `_default.tasks`.`db1-list1-task2` document was deleted. @@ -383,9 +413,12 @@ Test unsharing a list. The shared list and tasks will be purged from the unshare This test will use two databases either on the same test server or on two test servers depending on the availability of the test servers. -1. Reset SG and load `todo` dataset. -2. Reset local database, and load `todo` dataset into database `db1` and `db2`. -3. Start a replicator: +1. Setup test env and assign SG roles to users: + * Reset SG and load `todo` dataset. + * Reset local database, and load `todo` dataset into database `db1` and `db2`. + * Assign SG roles to users: + * user1 : `lists.user1.db1-list1.contributor` +2. Start a replicator: * endpoint: `/todo` * database: `db1` * collections : `_default.lists`, `_default.tasks`, `_default.users` @@ -393,7 +426,7 @@ the availability of the test servers. * continuous: true * enableDocumentListener: true * credentials: user1/pass -4. Start a replicator: +3. Start a replicator: * endpoint: `/todo` * database: `db2` * collections : `_default.lists`, `_default.tasks`, `_default.users` @@ -401,8 +434,12 @@ the availability of the test servers. * continuous: true * enableDocumentListener: true * credentials: user2/pass -5. Create a list and 2 tasks in `db1` as separate steps. - * Create SG role named `lists.user1.db1-list1.contributor`. +4. Snapshot documents in db2: + * `_default.lists`.`db1-list1` + * `_default.tasks`.`db1-list1-task1` + * `_default.tasks`.`db1-list1-task2` + * `_default.users`.`db1-list1-user2` +5. Create a list and two tasks in db1: * Create a list document in `_default.lists` as * { "_id": "db1-list1", "name": "db1 list1", "owner": "user1" } * Create a task document in `_default.tasks` as @@ -411,25 +448,22 @@ the availability of the test servers. * Create a task document in `_default.tasks` as * { "_id": "db1-list1-task2", "name": "db1 list1 task2", "complete": true, "image": null, "taskList" : { "id" : "db1-list1", "owner" : "user1" } } * Set the `image` key with the `s1.jpg` blob. -6. Wait for 10 seconds which should be enough for the two replicators to finish replicating. -7. Check that no document replication events are in `db2`. -8. Snapshot documents in `db2`: - * `_default.lists`.`db1-list1` - * `_default.tasks`.`db1-list1-task1` - * `_default.tasks`.`db1-list1-task2` -9. Creating a user document in the `_default.users` to share the `_default.lists`.`db1-list1` list in `db1` as +6. Verify that there are no document replication events in db1, for 10 seconds. +7. Create a user document to share the _default.lists.db1-list1 from db1: * { "_id": "db1-list1-user2", "username": "user2", "taskList" : { "id" : "db1-list1", "owner" : "user1" } } -10. Wait and check the pull document replication events in `db2`. +8. Wait for the newly visible documents to be pulled to db2: * `_default.lists`.`db1-list1` * `_default.tasks`.`db1-list1-task1` * `_default.tasks`.`db1-list1-task2` -11. Verify snapshot from step 8 to check the content of the snapshot documents. -12. Check that no `_default.users`.`db1-list1-user2` document is in `db2` -13. Unshare the `db1-list1` list by deleting `_default.users`.`db1-list1-user2` from `db1`. -14. Wait and check the document replication events in `db1`. - * `_default.users`.`db1-list1-user2` (deleted, pushed) -15. Wait and check the document replication events in `db2`. +9. Verify that the newly visible docs are in db2 and that _default.users.db1-list1-user2 is not. +10. Unshare the db1-list1 list by deleting _default.users.db1-list1-user2 from db1. +11. Verify that the deletion is pushed from db1. +12. Wait for the newly invisible documents to be removed from db2: + * `_default.lists`.`db1-list1` (pull, acccessed_removed) + * `_default.tasks`.`db1-list1-task1` (pull, accessed_removed) + * `_default.tasks`.`db1-list1-task2` (pull, access_removed) +13. Verify that the shared list and its tasks do not exist in db2: * `_default.lists`.`db1-list1` (pull, purged) * `_default.tasks`.`db1-list1-task1` (pull, purged) * `_default.tasks`.`db1-list1-task2` (pull, purged) -16. Check that the shared list and its tasks do not exist in `db2`. + \ No newline at end of file diff --git a/tests/test_fest.py b/tests/test_fest.py index 1275751f..bf4b534d 100644 --- a/tests/test_fest.py +++ b/tests/test_fest.py @@ -13,23 +13,32 @@ class TestFest(CBLTestClass): - async def setup_test_fest_cloud(self, cblpytest: CBLPyTest, dataset_path: Path, - roles: Optional[List[str]] = None) -> CouchbaseCloud: + async def setup_test_fest_cloud(self, cblpytest: CBLPyTest, dataset_path: Path, + roles: Optional[Dict[str, List[str]]] = None) -> CouchbaseCloud: + """ + Sets up the test environment. + :param cblpytest: CBLPyTest object. + :param dataset_path: Path to the SG dataset. + :param roles: A dictionary defining user roles to be created and assigned to the user. + """ + self.mark_test_step("Reset SG and load todo dataset") cloud = CouchbaseCloud(cblpytest.sync_gateways[0], cblpytest.couchbase_servers[0]) await cloud.configure_dataset(dataset_path, "todo") - sg_roles = roles if roles is not None else ["lists.user1.db1-list1.contributor"] - self.mark_test_step(f"Create SG roles: {', '.join(sg_roles)}") - collection_access: Dict[str, Any] = { - "_default": { - "lists": {"admin_channels": []}, - "tasks": {"admin_channels": []}, - "users": {"admin_channels": []} + if roles is not None: + collection_access: Dict[str, Any] = { + "_default": { + "lists": {"admin_channels": []}, + "tasks": {"admin_channels": []}, + "users": {"admin_channels": []} + } } - } - for role in sg_roles: - await cloud.create_role("todo", role, collection_access) + for user, user_roles in roles.items(): + self.mark_test_step(f"Create and assign SG roles: {', '.join(user_roles)} for user: {user}") + for role in user_roles: + await cloud.create_role("todo", role, collection_access) + await cloud.assign_roles("todo", user, user_roles) return cloud @@ -79,8 +88,10 @@ async def setup_test_fest_repls(self, cblpytest: CBLPyTest, dbs: Tuple[Database, @pytest.mark.asyncio(loop_scope="session") async def test_create_tasks(self, cblpytest: CBLPyTest, dataset_path: Path) -> None: - await self.setup_test_fest_cloud(cblpytest, dataset_path, ["lists.user1.db1-list1.contributor", - "lists.user1.db2-list1.contributor"]) + self.mark_test_step("Setup test env and assign SG roles to users") + await self.setup_test_fest_cloud(cblpytest, dataset_path, { + "user1": ["lists.user1.db1-list1.contributor", "lists.user1.db2-list1.contributor"] + }) db1, db2 = await self.setup_test_fest_dbs(cblpytest) repl1, repl2 = await self.setup_test_fest_repls(cblpytest, (db1, db2)) @@ -154,7 +165,10 @@ async def test_create_tasks(self, cblpytest: CBLPyTest, dataset_path: Path) -> N @pytest.mark.asyncio(loop_scope="session") async def test_update_task(self, cblpytest: CBLPyTest, dataset_path: Path) -> None: - await self.setup_test_fest_cloud(cblpytest, dataset_path) + self.mark_test_step("Setup test env and assign SG roles to users") + await self.setup_test_fest_cloud(cblpytest, dataset_path, { + "user1": ["lists.user1.db1-list1.contributor"] + }) db1, db2 = await self.setup_test_fest_dbs(cblpytest) repl1, repl2 = await self.setup_test_fest_repls(cblpytest, (db1, db2)) @@ -164,6 +178,10 @@ async def test_update_task(self, cblpytest: CBLPyTest, dataset_path: Path) -> No DocumentEntry("_default.tasks", "db1-list1-task1") ]) + self.mark_test_step("Start the replicators") + await repl1.start() + await repl2.start() + self.mark_test_step("Create a list and a task in db1") async with db1.batch_updater() as b: b.upsert_document("_default.lists", "db1-list1", [{"name": "db1 list1"}, {"owner": "user1"}]) @@ -172,16 +190,6 @@ async def test_update_task(self, cblpytest: CBLPyTest, dataset_path: Path) -> No {"taskList": {"id": "db1-list1", "owner": "user1"}}], new_blobs={"image": "l5.jpg"}) - self.mark_test_step("Snapshot db1") - snap1 = await db1.create_snapshot([ - DocumentEntry("_default.lists", "db1-list1"), - DocumentEntry("_default.tasks", "db1-list1-task1") - ]) - - self.mark_test_step("Start the replicators") - await repl1.start() - await repl2.start() - self.mark_test_step("Wait for the new docs to be pulled to db2") await repl2.wait_for_all_doc_events({ WaitForDocumentEventEntry("_default.lists", "db1-list1", ReplicatorType.PULL, @@ -199,6 +207,12 @@ async def test_update_task(self, cblpytest: CBLPyTest, dataset_path: Path) -> No verify_result = await db2.verify_documents(snapshot_updater) assert verify_result.result is True, f"Unexpected docs in db2: {verify_result.description}" + self.mark_test_step("Snapshot db1") + snap1 = await db1.create_snapshot([ + DocumentEntry("_default.lists", "db1-list1"), + DocumentEntry("_default.tasks", "db1-list1-task1") + ]) + self.mark_test_step("Update the db1-list1-task1 task in db2") async with db2.batch_updater() as b: b.upsert_document("_default.tasks", "db1-list1-task1", @@ -223,10 +237,17 @@ async def test_update_task(self, cblpytest: CBLPyTest, dataset_path: Path) -> No @pytest.mark.asyncio(loop_scope="session") async def test_delete_task(self, cblpytest: CBLPyTest, dataset_path: Path) -> None: - await self.setup_test_fest_cloud(cblpytest, dataset_path) + self.mark_test_step("Setup test env and assign SG roles to users") + await self.setup_test_fest_cloud(cblpytest, dataset_path, { + "user1": ["lists.user1.db1-list1.contributor"] + }) db1, db2 = await self.setup_test_fest_dbs(cblpytest) repl1, repl2 = await self.setup_test_fest_repls(cblpytest, (db1, db2)) + self.mark_test_step("Start the replicators") + await repl1.start() + await repl2.start() + self.mark_test_step("Create a list and a task in db1") async with db1.batch_updater() as b: b.upsert_document("_default.lists", "db1-list1", [{"name": "db1 list1"}, {"owner": "user1"}]) @@ -235,16 +256,6 @@ async def test_delete_task(self, cblpytest: CBLPyTest, dataset_path: Path) -> No {"taskList": {"id": "db1-list1", "owner": "user1"}}], new_blobs={"image": "l1.jpg"}) - self.mark_test_step("Snapshot documents in db1") - snap1 = await db1.create_snapshot([ - DocumentEntry("_default.lists", "db1-list1"), - DocumentEntry("_default.tasks", "db1-list1-task1") - ]) - - self.mark_test_step("Start the replicators") - await repl1.start() - await repl2.start() - self.mark_test_step("Wait for the updated doc to be pulled to db2") await repl2.wait_for_all_doc_events({ WaitForDocumentEventEntry("_default.lists", "db1-list1", ReplicatorType.PULL, @@ -253,6 +264,12 @@ async def test_delete_task(self, cblpytest: CBLPyTest, dataset_path: Path) -> No ReplicatorDocumentFlags.NONE)}) repl2.clear_document_updates() + self.mark_test_step("Snapshot documents in db1") + snap1 = await db1.create_snapshot([ + DocumentEntry("_default.lists", "db1-list1"), + DocumentEntry("_default.tasks", "db1-list1-task1") + ]) + self.mark_test_step("Snapshot documents in db2") snap2 = await db2.create_snapshot([ DocumentEntry("_default.lists", "db1-list1"), @@ -284,10 +301,17 @@ async def test_delete_task(self, cblpytest: CBLPyTest, dataset_path: Path) -> No @pytest.mark.asyncio(loop_scope="session") async def test_delete_list(self, cblpytest: CBLPyTest, dataset_path: Path) -> None: - await self.setup_test_fest_cloud(cblpytest, dataset_path) + self.mark_test_step("Setup test env and assign SG roles to users") + await self.setup_test_fest_cloud(cblpytest, dataset_path, { + "user1": ["lists.user1.db1-list1.contributor"] + }) db1, db2 = await self.setup_test_fest_dbs(cblpytest) repl1, repl2 = await self.setup_test_fest_repls(cblpytest, (db1, db2)) + self.mark_test_step("Start the replicators") + await repl1.start() + await repl2.start() + self.mark_test_step("Create a list and two tasks in db1") async with db1.batch_updater() as b: b.upsert_document("_default.lists", "db1-list1", [{"name": "db1 list1"}, {"owner": "user1"}]) @@ -299,17 +323,6 @@ async def test_delete_list(self, cblpytest: CBLPyTest, dataset_path: Path) -> No new_properties=[{"name": "db1 list1 task2"}, {"complete": True}, {"taskList": {"id": "db1-list1", "owner": "user1"}}]) - self.mark_test_step("Snapshot documents in db1") - snap1 = await db1.create_snapshot([ - DocumentEntry("_default.lists", "db1-list1"), - DocumentEntry("_default.tasks", "db1-list1-task1"), - DocumentEntry("_default.tasks", "db1-list1-task2") - ]) - - self.mark_test_step("Start the replicators") - await repl1.start() - await repl2.start() - self.mark_test_step("Wait for the new docs to be pulled to db2") await repl2.wait_for_all_doc_events({ WaitForDocumentEventEntry("_default.lists", "db1-list1", ReplicatorType.PULL, @@ -320,6 +333,13 @@ async def test_delete_list(self, cblpytest: CBLPyTest, dataset_path: Path) -> No ReplicatorDocumentFlags.NONE)}) repl2.clear_document_updates() + self.mark_test_step("Snapshot documents in db1") + snap1 = await db1.create_snapshot([ + DocumentEntry("_default.lists", "db1-list1"), + DocumentEntry("_default.tasks", "db1-list1-task1"), + DocumentEntry("_default.tasks", "db1-list1-task2") + ]) + self.mark_test_step("Snapshot documents in db2") snap2 = await db2.create_snapshot([ DocumentEntry("_default.lists", "db1-list1"), @@ -327,7 +347,7 @@ async def test_delete_list(self, cblpytest: CBLPyTest, dataset_path: Path) -> No DocumentEntry("_default.tasks", "db1-list1-task2") ]) - self.mark_test_step("Delete the _default.tasks, db1-list1-task1 task in db1") + self.mark_test_step("Delete the the list1 and the two tasks in db1") async with db1.batch_updater() as b: b.delete_document("_default.lists", "db1-list1") b.delete_document("_default.tasks", "db1-list1-task1") @@ -342,7 +362,7 @@ async def test_delete_list(self, cblpytest: CBLPyTest, dataset_path: Path) -> No WaitForDocumentEventEntry("_default.tasks", "db1-list1-task2", ReplicatorType.PULL, ReplicatorDocumentFlags.DELETED)}) - self.mark_test_step("Verify that _default.tasks.db1-list1-task1 was deleted from db1") + self.mark_test_step("Verify that the list and two tasks were deleted from db1") snapshot_updater = SnapshotUpdater(snap1) snapshot_updater.delete_document("_default.lists", "db1-list1") snapshot_updater.delete_document("_default.tasks", "db1-list1-task1") @@ -350,7 +370,7 @@ async def test_delete_list(self, cblpytest: CBLPyTest, dataset_path: Path) -> No verify_result = await db1.verify_documents(snapshot_updater) assert verify_result.result is True, f"Unexpected docs in db1: {verify_result.description}" - self.mark_test_step("Verify that _default.tasks.db1-list1-task1 was deleted from db2") + self.mark_test_step("Verify that the list and two tasks were deleted from db2") snapshot_updater = SnapshotUpdater(snap2) snapshot_updater.delete_document("_default.lists", "db1-list1") snapshot_updater.delete_document("_default.tasks", "db1-list1-task1") @@ -361,12 +381,19 @@ async def test_delete_list(self, cblpytest: CBLPyTest, dataset_path: Path) -> No await cblpytest.test_servers[0].cleanup() @pytest.mark.asyncio(loop_scope="session") - async def _test_create_tasks_two_users(self, cblpytest: CBLPyTest, dataset_path: Path) -> None: - await self.setup_test_fest_cloud(cblpytest, dataset_path, ["lists.user1.db1-list1.contributor", - "lists.user2.db2-list1.contributor"]) + async def test_create_tasks_two_users(self, cblpytest: CBLPyTest, dataset_path: Path) -> None: + self.mark_test_step("Setup test env and assign SG roles to users") + await self.setup_test_fest_cloud(cblpytest, dataset_path, { + "user1": ["lists.user1.db1-list1.contributor"], + "user2": ["lists.user2.db2-list1.contributor"] + }) db1, db2 = await self.setup_test_fest_dbs(cblpytest) repl1, repl2 = await self.setup_test_fest_repls(cblpytest, (db1, db2), ("user1", "user2")) + self.mark_test_step("Start the replicators") + await repl1.start() + await repl2.start() + self.mark_test_step("Create a list and a task in db1") async with db1.batch_updater() as b: b.upsert_document("_default.lists", "db1-list1", [{"name": "db1 list1"}, {"owner": "user1"}]) @@ -399,10 +426,6 @@ async def _test_create_tasks_two_users(self, cblpytest: CBLPyTest, dataset_path: DocumentEntry("_default.tasks", "db2-list1-task1") ]) - self.mark_test_step("Start the replicators") - await repl1.start() - await repl2.start() - self.mark_test_step("Verify that there are no document replication events in db1, for 10 seconds") found = await repl1.wait_for_any_doc_event({ WaitForDocumentEventEntry("_default.lists", "db2-list1", ReplicatorType.PUSH_AND_PULL, None), @@ -431,10 +454,17 @@ async def _test_create_tasks_two_users(self, cblpytest: CBLPyTest, dataset_path: @pytest.mark.asyncio(loop_scope="session") async def test_share_list(self, cblpytest: CBLPyTest, dataset_path: Path) -> None: - await self.setup_test_fest_cloud(cblpytest, dataset_path, ["lists.user1.db1-list1.contributor"]) + self.mark_test_step("Setup test env and assign SG roles to users") + await self.setup_test_fest_cloud(cblpytest, dataset_path, { + "user1": ["lists.user1.db1-list1.contributor"] + }) db1, db2 = await self.setup_test_fest_dbs(cblpytest) repl1, repl2 = await self.setup_test_fest_repls(cblpytest, (db1, db2), ("user1", "user2")) + self.mark_test_step("Start the replicators") + await repl1.start() + await repl2.start() + self.mark_test_step("Snapshot documents in db2") snap = await db2.create_snapshot([ DocumentEntry("_default.lists", "db1-list1"), @@ -443,10 +473,6 @@ async def test_share_list(self, cblpytest: CBLPyTest, dataset_path: Path) -> Non DocumentEntry("_default.users", "db1-list1-user2") ]) - self.mark_test_step("Start the replicators") - await repl1.start() - await repl2.start() - self.mark_test_step("Create a list and two tasks in db1") async with db1.batch_updater() as b: b.upsert_document("_default.lists", "db1-list1", [{"name": "db1 list1"}, {"owner": "user1"}]) @@ -502,10 +528,17 @@ async def test_share_list(self, cblpytest: CBLPyTest, dataset_path: Path) -> Non @pytest.mark.asyncio(loop_scope="session") async def test_update_shared_tasks(self, cblpytest: CBLPyTest, dataset_path: Path) -> None: - await self.setup_test_fest_cloud(cblpytest, dataset_path, ["lists.user1.db1-list1.contributor"]) + self.mark_test_step("Setup test env and assign SG roles to users") + await self.setup_test_fest_cloud(cblpytest, dataset_path, { + "user1": ["lists.user1.db1-list1.contributor"] + }) db1, db2 = await self.setup_test_fest_dbs(cblpytest) repl1, repl2 = await self.setup_test_fest_repls(cblpytest, (db1, db2), ("user1", "user2")) + self.mark_test_step("Start the replicators") + await repl1.start() + await repl2.start() + self.mark_test_step("Snapshot documents in db2") snap2 = await db2.create_snapshot([ DocumentEntry("_default.lists", "db1-list1"), @@ -532,10 +565,6 @@ async def test_update_shared_tasks(self, cblpytest: CBLPyTest, dataset_path: Pat DocumentEntry("_default.tasks", "db1-list1-task2") ]) - self.mark_test_step("Start the replicators") - await repl1.start() - await repl2.start() - self.mark_test_step("Verify that there are no document replication events in db2, for 10 seconds") found = await repl2.wait_for_any_doc_event({ WaitForDocumentEventEntry("_default.lists", "db1-list1", ReplicatorType.PUSH_AND_PULL, None), @@ -600,10 +629,17 @@ async def test_update_shared_tasks(self, cblpytest: CBLPyTest, dataset_path: Pat @pytest.mark.asyncio(loop_scope="session") async def test_unshare_list(self, cblpytest: CBLPyTest, dataset_path: Path) -> None: - await self.setup_test_fest_cloud(cblpytest, dataset_path, ["lists.user1.db1-list1.contributor"]) + self.mark_test_step("Setup test env and assign SG roles to users") + await self.setup_test_fest_cloud(cblpytest, dataset_path, { + "user1": ["lists.user1.db1-list1.contributor"] + }) db1, db2 = await self.setup_test_fest_dbs(cblpytest) repl1, repl2 = await self.setup_test_fest_repls(cblpytest, (db1, db2), ("user1", "user2")) + self.mark_test_step("Start the replicators") + await repl1.start() + await repl2.start() + self.mark_test_step("Snapshot documents in db2") snap2 = await db2.create_snapshot([ DocumentEntry("_default.lists", "db1-list1"), @@ -612,10 +648,6 @@ async def test_unshare_list(self, cblpytest: CBLPyTest, dataset_path: Path) -> N DocumentEntry("_default.users", "db1-list1-user2") ]) - self.mark_test_step("Start the replicators") - await repl1.start() - await repl2.start() - self.mark_test_step("Create a list and two tasks in db1") async with db1.batch_updater() as b: b.upsert_document("_default.lists", "db1-list1", [{"name": "db1 list1"}, {"owner": "user1"}])