Ejemplo n.º 1
0
    def test_caching_general_clearing(self, client: FlaskClient) -> None:

        if Env.get_bool("AUTH_ENABLE"):
            headers, _ = self.do_login(client, None, None)
        else:
            headers = None

        # get method is cached for 200 seconds

        # First response is not cached
        r = client.get(f"{API_URI}/tests/cache/long")
        assert r.status_code == 200
        counter1 = self.get_content(r)

        # Second response is cached
        r = client.get(f"{API_URI}/tests/cache/long")
        assert r.status_code == 200
        assert self.get_content(r) == counter1

        # Empty all the cache
        Cache.clear()

        # Third response is no longer cached
        r = client.get(f"{API_URI}/tests/cache/long")
        assert r.status_code == 200
        counter2 = self.get_content(r)
        assert counter2 != counter1

        # Response is still cached
        r = client.get(f"{API_URI}/tests/cache/long")
        assert r.status_code == 200
        assert self.get_content(r) == counter2

        # Empty the endpoint cache
        client.delete(f"{API_URI}/tests/cache/long")

        # Second response is no longer cached
        r = client.get(f"{API_URI}/tests/cache/long")
        assert r.status_code == 200
        counter3 = self.get_content(r)
        assert counter3 != counter2

        # Response is still cached
        r = client.get(f"{API_URI}/tests/cache/long")
        assert r.status_code == 200
        assert self.get_content(r) == counter3

        # Endpoint is unauthenticated, headers are ignored when building the cache key
        r = client.get(f"{API_URI}/tests/cache/long", headers=headers)
        assert r.status_code == 200
        assert self.get_content(r) == counter3

        # Tokens are ignored even if invalid
        r = client.get(f"{API_URI}/tests/cache/long",
                       headers={"Authorization": "Bearer invalid"})
        assert r.status_code == 200
        assert self.get_content(r) == counter3
Ejemplo n.º 2
0
def delete_test_env(
    client: FlaskClient,
    user_A1_headers: Tuple[Optional[Dict[str, str]], str],
    user_B1_headers: Tuple[Optional[Dict[str, str]], str],
    user_B1_uuid: str,
    user_B2_uuid: str,
    user_A1_uuid: str,
    uuid_group_A: str,
    uuid_group_B: str,
    study1_uuid: Optional[str] = None,
    study2_uuid: Optional[str] = None,
) -> None:

    admin_headers, _ = BaseTests.do_login(client, None, None)
    # delete all the elements used by the test
    if study1_uuid:
        r = client.delete(f"{API_URI}/study/{study1_uuid}",
                          headers=user_B1_headers)
        assert r.status_code == 204
    if study2_uuid:
        r = client.delete(f"{API_URI}/study/{study2_uuid}",
                          headers=user_A1_headers)
        assert r.status_code == 204
    # first user
    r = client.delete(f"{API_URI}/admin/users/{user_B1_uuid}",
                      headers=admin_headers)
    assert r.status_code == 204
    # second user
    r = client.delete(f"{API_URI}/admin/users/{user_B2_uuid}",
                      headers=admin_headers)
    assert r.status_code == 204
    # other user
    r = client.delete(f"{API_URI}/admin/users/{user_A1_uuid}",
                      headers=admin_headers)
    assert r.status_code == 204

    # group A directory
    INPUT_ROOT_path = INPUT_ROOT.joinpath(uuid_group_A)
    shutil.rmtree(INPUT_ROOT_path)
    # group A
    r = client.delete(f"{API_URI}/admin/groups/{uuid_group_A}",
                      headers=admin_headers)
    assert r.status_code == 204
    # group B directory
    INPUT_ROOT_path = INPUT_ROOT.joinpath(uuid_group_B)
    shutil.rmtree(INPUT_ROOT_path)
    # group B
    r = client.delete(f"{API_URI}/admin/groups/{uuid_group_B}",
                      headers=admin_headers)
    assert r.status_code == 204
Ejemplo n.º 3
0
    def check_endpoint(
        self,
        client: FlaskClient,
        method: str,
        endpoint: str,
        headers: Optional[Dict[str, str]],
        expected_authorized: bool,
        paths: List[str],
    ) -> List[str]:

        assert method in (
            "GET",
            "POST",
            "PUT",
            "PATCH",
            "DELETE",
        )

        path = self.get_path(method, endpoint)

        assert path in paths

        # SERVER_URI because api and auth are already included in endpoint
        full_endpoint = f"{SERVER_URI}/{endpoint}"

        if method == "GET":
            r = client.get(full_endpoint, headers=headers)
        elif method == "POST":
            r = client.post(full_endpoint, headers=headers)
        elif method == "PUT":
            r = client.put(full_endpoint, headers=headers)
        elif method == "PATCH":
            r = client.patch(full_endpoint, headers=headers)
        elif method == "DELETE":
            r = client.delete(full_endpoint, headers=headers)
        else:  # pragma: no cover
            pytest.fail("Unknown method")

        if expected_authorized:
            assert r.status_code != 401
        else:
            assert r.status_code != 400

        paths.remove(path)
        return paths
Ejemplo n.º 4
0
    def test_staff_restrictions(self, client: FlaskClient,
                                faker: Faker) -> None:

        if not Env.get_bool("MAIN_LOGIN_ENABLE") or not Env.get_bool(
                "AUTH_ENABLE"):
            log.warning("Skipping admin/users tests")
            return

        auth = Connector.get_authentication_instance()
        staff_role_enabled = Role.STAFF.value in [
            r.name for r in auth.get_roles()
        ]

        if not staff_role_enabled:  # pragma: no cover
            log.warning(
                "Skipping tests of admin/users restrictions, role Staff not enabled"
            )
            return

        staff_uuid, staff_data = self.create_user(client, roles=[Role.STAFF])
        staff_email = staff_data.get("email")
        staff_password = staff_data.get("password")
        staff_headers, _ = self.do_login(client, staff_email, staff_password)

        user_uuid, _ = self.create_user(client, roles=[Role.USER])

        admin_headers, _ = self.do_login(client, None, None)

        r = client.get(f"{AUTH_URI}/profile", headers=admin_headers)
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, dict)
        admin_uuid = content.get("uuid")

        # Staff users are not allowed to retrieve Admins' data
        r = client.get(f"{API_URI}/admin/users/{user_uuid}",
                       headers=admin_headers)
        assert r.status_code == 200

        r = client.get(f"{API_URI}/admin/users/{staff_uuid}",
                       headers=admin_headers)
        assert r.status_code == 200

        r = client.get(f"{API_URI}/admin/users/{admin_uuid}",
                       headers=admin_headers)
        assert r.status_code == 200

        r = client.get(f"{API_URI}/admin/users/{user_uuid}",
                       headers=staff_headers)
        assert r.status_code == 200

        r = client.get(f"{API_URI}/admin/users/{staff_uuid}",
                       headers=staff_headers)
        assert r.status_code == 200

        r = client.get(f"{API_URI}/admin/users/{admin_uuid}",
                       headers=staff_headers)
        assert r.status_code == 404
        content = self.get_content(r)
        assert content == "This user cannot be found or you are not authorized"

        # Staff users are not allowed to edit Admins
        r = client.put(
            f"{API_URI}/admin/users/{admin_uuid}",
            json={
                "name": faker.name(),
                "roles": orjson.dumps([Role.STAFF]).decode("UTF8"),
            },
            headers=staff_headers,
        )
        assert r.status_code == 404
        content = self.get_content(r)
        assert content == "This user cannot be found or you are not authorized"

        r = client.put(
            f"{API_URI}/admin/users/{staff_uuid}",
            json={
                "name": faker.name(),
                "roles": orjson.dumps([Role.STAFF]).decode("UTF8"),
            },
            headers=staff_headers,
        )
        assert r.status_code == 204

        r = client.put(
            f"{API_URI}/admin/users/{user_uuid}",
            json={
                "name": faker.name(),
                "roles": orjson.dumps([Role.USER]).decode("UTF8"),
            },
            headers=staff_headers,
        )
        assert r.status_code == 204

        # Admin role is not allowed for Staff users
        tmp_schema = self.get_dynamic_input_schema(client, "admin/users",
                                                   admin_headers)
        post_schema = {s["key"]: s for s in tmp_schema}
        assert "roles" in post_schema
        assert "options" in post_schema["roles"]
        assert "normal_user" in post_schema["roles"]["options"]
        assert "admin_root" in post_schema["roles"]["options"]

        tmp_schema = self.get_dynamic_input_schema(client,
                                                   f"admin/users/{user_uuid}",
                                                   admin_headers,
                                                   method="put")
        put_schema = {s["key"]: s for s in tmp_schema}

        assert "roles" in put_schema
        assert "options" in post_schema["roles"]
        assert "normal_user" in post_schema["roles"]["options"]
        assert "admin_root" in post_schema["roles"]["options"]

        tmp_schema = self.get_dynamic_input_schema(client, "admin/users",
                                                   staff_headers)
        post_schema = {s["key"]: s for s in tmp_schema}
        assert "roles" in post_schema
        assert "options" in post_schema["roles"]
        assert "normal_user" in post_schema["roles"]["options"]
        assert "admin_root" not in post_schema["roles"]["options"]

        tmp_schema = self.get_dynamic_input_schema(client,
                                                   f"admin/users/{user_uuid}",
                                                   staff_headers,
                                                   method="put")
        put_schema = {s["key"]: s for s in tmp_schema}

        assert "roles" in put_schema
        assert "options" in post_schema["roles"]
        assert "normal_user" in post_schema["roles"]["options"]
        assert "admin_root" not in post_schema["roles"]["options"]

        # Staff can't send role admin on put
        r = client.put(
            f"{API_URI}/admin/users/{user_uuid}",
            json={
                "name": faker.name(),
                "roles": orjson.dumps([Role.ADMIN]).decode("UTF8"),
            },
            headers=staff_headers,
        )
        assert r.status_code == 400

        # Staff can't send role admin on post
        schema = self.get_dynamic_input_schema(client, "admin/users",
                                               staff_headers)
        data = self.buildData(schema)

        data["email_notification"] = True
        data["is_active"] = True
        data["expiration"] = None
        data["roles"] = orjson.dumps([Role.ADMIN]).decode("UTF8")

        r = client.post(f"{API_URI}/admin/users",
                        json=data,
                        headers=staff_headers)
        assert r.status_code == 400

        # Admin users are filtered out when asked from a Staff user
        r = client.get(f"{API_URI}/admin/users", headers=admin_headers)
        assert r.status_code == 200
        users_list = self.get_content(r)
        assert isinstance(users_list, list)
        assert len(users_list) > 0
        email_list = [u.get("email") for u in users_list]
        assert staff_email in email_list
        assert BaseAuthentication.default_user in email_list

        r = client.get(f"{API_URI}/admin/users", headers=staff_headers)
        assert r.status_code == 200
        users_list = self.get_content(r)
        assert isinstance(users_list, list)
        assert len(users_list) > 0
        email_list = [u.get("email") for u in users_list]
        assert staff_email in email_list
        assert BaseAuthentication.default_user not in email_list

        # Staff users are not allowed to delete Admins
        r = client.delete(f"{API_URI}/admin/users/{admin_uuid}",
                          headers=staff_headers)
        assert r.status_code == 404
        content = self.get_content(r)
        assert content == "This user cannot be found or you are not authorized"

        r = client.delete(f"{API_URI}/admin/users/{user_uuid}",
                          headers=staff_headers)
        assert r.status_code == 204
Ejemplo n.º 5
0
    def test_admin_users(self, client: FlaskClient, faker: Faker) -> None:

        if not Env.get_bool("MAIN_LOGIN_ENABLE") or not Env.get_bool(
                "AUTH_ENABLE"):
            log.warning("Skipping admin/users tests")
            return

        project_tile = get_project_configuration("project.title",
                                                 default="YourProject")

        auth = Connector.get_authentication_instance()
        staff_role_enabled = Role.STAFF.value in [
            r.name for r in auth.get_roles()
        ]

        for role in (
                Role.ADMIN,
                Role.STAFF,
        ):

            if not staff_role_enabled:  # pragma: no cover
                log.warning(
                    "Skipping tests of admin/users endpoints, role Staff not enabled"
                )
                continue
            else:
                log.warning("Testing admin/users endpoints as {}", role)

            if role == Role.ADMIN:
                user_email = BaseAuthentication.default_user
                user_password = BaseAuthentication.default_password
            elif role == Role.STAFF:
                _, user_data = self.create_user(client, roles=[Role.STAFF])
                user_email = user_data.get("email")
                user_password = user_data.get("password")

            headers, _ = self.do_login(client, user_email, user_password)
            r = client.get(f"{API_URI}/admin/users", headers=headers)
            assert r.status_code == 200

            schema = self.get_dynamic_input_schema(client, "admin/users",
                                                   headers)
            data = self.buildData(schema)

            data["email_notification"] = True
            data["is_active"] = True
            data["expiration"] = None

            # Event 1: create
            r = client.post(f"{API_URI}/admin/users",
                            json=data,
                            headers=headers)
            assert r.status_code == 200
            uuid = self.get_content(r)
            assert isinstance(uuid, str)

            # A new User is created
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.create.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].url == "/api/admin/users"
            assert "name" in events[0].payload
            assert "surname" in events[0].payload
            assert "email" in events[0].payload

            # Save it for the following tests
            event_target_id1 = events[0].target_id

            mail = self.read_mock_email()
            body = mail.get("body", "")

            # Subject: is a key in the MIMEText
            assert body is not None
            assert mail.get("headers") is not None
            assert f"Subject: {project_tile}: New credentials" in mail.get(
                "headers", "")
            assert data.get("email", "MISSING").lower() in body
            assert (data.get("password", "MISSING") in body
                    or escape(str(data.get("password"))) in body)

            # Test the differences between post and put schema
            post_schema = {s["key"]: s for s in schema}

            tmp_schema = self.get_dynamic_input_schema(client,
                                                       f"admin/users/{uuid}",
                                                       headers,
                                                       method="put")
            put_schema = {s["key"]: s for s in tmp_schema}

            assert "email" in post_schema
            assert post_schema["email"]["required"]
            assert "email" not in put_schema

            assert "name" in post_schema
            assert post_schema["name"]["required"]
            assert "name" in put_schema
            assert not put_schema["name"]["required"]

            assert "surname" in post_schema
            assert post_schema["surname"]["required"]
            assert "surname" in put_schema
            assert not put_schema["surname"]["required"]

            assert "password" in post_schema
            assert post_schema["password"]["required"]
            assert "password" in put_schema
            assert not put_schema["password"]["required"]

            assert "group" in post_schema
            assert post_schema["group"]["required"]
            assert "group" in put_schema
            assert not put_schema["group"]["required"]

            # Event 2: read
            r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers)
            assert r.status_code == 200
            users_list = self.get_content(r)
            assert isinstance(users_list, dict)
            assert len(users_list) > 0
            # email is saved lowercase
            assert users_list.get("email") == data.get("email",
                                                       "MISSING").lower()

            # Access to the user
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.access.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].target_id == event_target_id1
            assert events[0].url == f"/api/admin/users/{event_target_id1}"
            assert len(events[0].payload) == 0

            # Check duplicates
            r = client.post(f"{API_URI}/admin/users",
                            json=data,
                            headers=headers)
            assert r.status_code == 409
            assert (self.get_content(r) ==
                    f"A User already exists with email: {data['email']}")

            data["email"] = BaseAuthentication.default_user
            r = client.post(f"{API_URI}/admin/users",
                            json=data,
                            headers=headers)
            assert r.status_code == 409
            assert (
                self.get_content(r) ==
                f"A User already exists with email: {BaseAuthentication.default_user}"
            )

            # Create another user
            data2 = self.buildData(schema)
            data2["email_notification"] = True
            data2["is_active"] = True
            data2["expiration"] = None

            # Event 3: create
            r = client.post(f"{API_URI}/admin/users",
                            json=data2,
                            headers=headers)
            assert r.status_code == 200
            uuid2 = self.get_content(r)
            assert isinstance(uuid2, str)

            # Another User is created
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.create.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].target_id != event_target_id1
            assert events[0].url == "/api/admin/users"
            assert "name" in events[0].payload
            assert "surname" in events[0].payload
            assert "email" in events[0].payload

            # Save it for the following tests
            event_target_id2 = events[0].target_id

            mail = self.read_mock_email()
            body = mail.get("body", "")
            # Subject: is a key in the MIMEText
            assert body is not None
            assert mail.get("headers") is not None
            assert f"Subject: {project_tile}: New credentials" in mail.get(
                "headers", "")
            assert data2.get("email", "MISSING").lower() in body
            pwd = data2.get("password", "MISSING")
            assert pwd in body or escape(str(pwd)) in body

            # send and invalid user_id
            r = client.put(
                f"{API_URI}/admin/users/invalid",
                json={"name": faker.name()},
                headers=headers,
            )
            assert r.status_code == 404

            # Event 4: modify
            r = client.put(
                f"{API_URI}/admin/users/{uuid}",
                json={"name": faker.name()},
                headers=headers,
            )
            assert r.status_code == 204

            # User 1 modified (same target_id as above)
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.modify.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].target_id == event_target_id1
            assert events[0].url == f"/api/admin/users/{event_target_id1}"
            assert "name" in events[0].payload
            assert "surname" not in events[0].payload
            assert "email" not in events[0].payload
            assert "password" not in events[0].payload

            # email cannot be modified
            new_data = {"email": data.get("email")}
            r = client.put(f"{API_URI}/admin/users/{uuid2}",
                           json=new_data,
                           headers=headers)
            # from webargs >= 6 this endpoint no longer return a 204 but a 400
            # because email is an unknown field
            # assert r.status_code == 204
            assert r.status_code == 400

            # Event 5: read
            r = client.get(f"{API_URI}/admin/users/{uuid2}", headers=headers)
            assert r.status_code == 200
            users_list = self.get_content(r)
            assert isinstance(users_list, dict)
            assert len(users_list) > 0
            # email is not modified -> still equal to data2, not data1
            assert users_list.get("email") != data.get("email",
                                                       "MISSING").lower()
            assert users_list.get("email") == data2.get("email",
                                                        "MISSING").lower()

            # Access to user 2
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.access.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].target_id == event_target_id2
            assert events[0].url == f"/api/admin/users/{event_target_id2}"
            assert len(events[0].payload) == 0

            r = client.delete(f"{API_URI}/admin/users/invalid",
                              headers=headers)
            assert r.status_code == 404

            # Event 6: delete
            r = client.delete(f"{API_URI}/admin/users/{uuid}", headers=headers)
            assert r.status_code == 204

            # User 1 is deleted (same target_id as above)
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.delete.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].target_id == event_target_id1
            assert events[0].url == f"/api/admin/users/{event_target_id1}"
            assert len(events[0].payload) == 0

            r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers)
            assert r.status_code == 404

            # change password of user2
            # Event 7: modify
            newpwd = faker.password(strong=True)
            data = {"password": newpwd, "email_notification": True}
            r = client.put(f"{API_URI}/admin/users/{uuid2}",
                           json=data,
                           headers=headers)
            assert r.status_code == 204

            # User 2 modified (same target_id as above)
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.modify.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].target_id == event_target_id2
            assert events[0].url == f"/api/admin/users/{event_target_id2}"
            assert "name" not in events[0].payload
            assert "surname" not in events[0].payload
            assert "email" not in events[0].payload
            assert "password" in events[0].payload
            assert "email_notification" in events[0].payload
            # Verify that the password is obfuscated in the log:
            assert events[0].payload["password"] == OBSCURE_VALUE

            mail = self.read_mock_email()
            # Subject: is a key in the MIMEText
            assert mail.get("body", "") is not None
            assert mail.get("headers", "") is not None
            assert f"Subject: {project_tile}: Password changed" in mail.get(
                "headers", "")
            assert data2.get("email",
                             "MISSING").lower() in mail.get("body", "")
            assert newpwd in mail.get(
                "body", "") or escape(newpwd) in mail.get("body", "")

            # login with a newly created user
            headers2, _ = self.do_login(client, data2.get("email"), newpwd)

            # normal users cannot access to this endpoint
            r = client.get(f"{API_URI}/admin/users", headers=headers2)
            assert r.status_code == 401

            r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers2)
            assert r.status_code == 401

            r = client.post(f"{API_URI}/admin/users",
                            json=data,
                            headers=headers2)
            assert r.status_code == 401

            r = client.put(
                f"{API_URI}/admin/users/{uuid}",
                json={"name": faker.name()},
                headers=headers2,
            )
            assert r.status_code == 401

            r = client.delete(f"{API_URI}/admin/users/{uuid}",
                              headers=headers2)
            assert r.status_code == 401

            # Users are not authorized to /admin/tokens
            # These two tests should be moved in test_endpoints_tokens.py
            r = client.get(f"{API_URI}/admin/tokens", headers=headers2)
            assert r.status_code == 401
            r = client.delete(f"{API_URI}/admin/tokens/xyz", headers=headers2)
            assert r.status_code == 401

            # let's delete the second user
            # Event 8: delete
            r = client.delete(f"{API_URI}/admin/users/{uuid2}",
                              headers=headers)
            assert r.status_code == 204

            # User 2 is deleted (same target_id as above)
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.delete.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].target_id == event_target_id2
            assert events[0].url == f"/api/admin/users/{event_target_id2}"
            assert len(events[0].payload) == 0

            # Restore the default password (changed due to FORCE_FIRST_PASSWORD_CHANGE)
            # or MAX_PASSWORD_VALIDITY errors
            r = client.get(f"{AUTH_URI}/profile", headers=headers)
            assert r.status_code == 200
            content = self.get_content(r)
            assert isinstance(content, dict)
            uuid = str(content.get("uuid"))

            data = {
                "password": user_password,
                # very important, otherwise the default user will lose its role
                "roles": orjson.dumps([role]).decode("UTF8"),
            }
            # Event 9: modify
            r = client.put(f"{API_URI}/admin/users/{uuid}",
                           json=data,
                           headers=headers)
            assert r.status_code == 204

            # Default user is modified
            events = self.get_last_events(1, filters={"target_type": "User"})
            assert events[0].event == Events.modify.value
            assert events[0].user == user_email
            assert events[0].target_type == "User"
            assert events[0].target_id != event_target_id1
            assert events[0].target_id != event_target_id2
            assert events[0].url != f"/api/admin/users/{event_target_id1}"
            assert events[0].url != f"/api/admin/users/{event_target_id2}"
            assert "name" not in events[0].payload
            assert "surname" not in events[0].payload
            assert "email" not in events[0].payload
            assert "password" in events[0].payload
            assert "roles" in events[0].payload
            assert "email_notification" not in events[0].payload
            # Verify that the password is obfuscated in the log:
            assert events[0].payload["password"] == OBSCURE_VALUE

            r = client.get(f"{AUTH_URI}/logout", headers=headers)
            assert r.status_code == 204
Ejemplo n.º 6
0
    def test_sendmail(self, client: FlaskClient, faker: Faker) -> None:

        headers, _ = self.do_login(client, None, None)

        r = client.get(f"{API_URI}/admin/mail", headers=headers)
        assert r.status_code == 405

        r = client.put(f"{API_URI}/admin/mail", headers=headers)
        assert r.status_code == 405

        r = client.patch(f"{API_URI}/admin/mail", headers=headers)
        assert r.status_code == 405

        r = client.delete(f"{API_URI}/admin/mail", headers=headers)
        assert r.status_code == 405

        data: Dict[str, Any] = {"dry_run": False}
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 400

        data["subject"] = faker.pystr()
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 400

        data["body"] = faker.text()
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 400

        data["to"] = faker.pystr()
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 400

        data["to"] = faker.ascii_email()
        data["body"] = "TEST EMAIL BODY"
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 204

        mail = self.read_mock_email()
        body = mail.get("body", "")
        assert "TEST EMAIL BODY" in body

        data["dry_run"] = True
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 200

        response = self.get_content(r)
        assert isinstance(response, dict)
        assert "html_body" in response
        assert "plain_body" in response
        assert "subject" in response
        assert "to" in response
        assert "cc" in response
        assert "bcc" in response

        data["dry_run"] = False

        data["body"] = "TEST EMAIL <b>HTML</b> BODY"
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 204
        mail = self.read_mock_email()
        body = mail.get("body", "")
        assert "TEST EMAIL <b>HTML</b> BODY" in body

        data["dry_run"] = True
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 200

        response = self.get_content(r)
        assert isinstance(response, dict)
        assert "html_body" in response
        assert "plain_body" in response
        assert "subject" in response
        assert "to" in response
        assert "cc" in response
        assert "bcc" in response

        data["dry_run"] = False

        data["body"] = faker.text()
        data["cc"] = faker.pystr()
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 400

        data["cc"] = faker.ascii_email()
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 204

        data["cc"] = f"{faker.ascii_email()},{faker.pystr()}"
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 400

        data["cc"] = f"{faker.ascii_email()},{faker.ascii_email()}"
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 204

        data["bcc"] = faker.pystr()
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 400

        data["bcc"] = f"{faker.ascii_email()},{faker.pystr()}"
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 400

        data["bcc"] = f"{faker.ascii_email()},{faker.ascii_email()}"
        r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers)
        assert r.status_code == 204

        mail = self.read_mock_email()

        body = mail.get("body", "")
        email_headers = mail.get("headers", "")
        assert body is not None
        assert email_headers is not None
        # Subject: is a key in the MIMEText
        assert f"Subject: {data['subject']}" in email_headers
        ccs = mail.get("cc", [])
        assert ccs[0] == data["to"]
        assert ccs[1] == data["cc"].split(",")
        assert ccs[2] == data["bcc"].split(",")
Ejemplo n.º 7
0
    def test_api_phenotype(self, client: FlaskClient, faker: Faker) -> None:
        # setup the test env
        (
            admin_headers,
            uuid_group_A,
            user_A1_uuid,
            user_A1_headers,
            uuid_group_B,
            user_B1_uuid,
            user_B1_headers,
            user_B2_uuid,
            user_B2_headers,
            study1_uuid,
            study2_uuid,
        ) = create_test_env(client, faker, study=True)

        # create a new phenotype with wrong age
        phenotype1 = {
            "name": faker.pystr(),
            "age": -2,
            "sex": "male",
        }
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/phenotypes",
            headers=user_B1_headers,
            data=phenotype1,
        )
        assert r.status_code == 400

        # create a new phenotype
        phenotype1["age"] = faker.pyint(0, 100)
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/phenotypes",
            headers=user_B1_headers,
            data=phenotype1,
        )
        assert r.status_code == 200
        phenotype1_uuid = self.get_content(r)
        assert isinstance(phenotype1_uuid, str)

        # create a new phenotype in a study of an other group
        r = client.post(
            f"{API_URI}/study/{study2_uuid}/phenotypes",
            headers=user_B1_headers,
            data=phenotype1,
        )
        assert r.status_code == 404

        # create a new phenotype as admin not belonging to study group
        phenotype2 = {
            "name": faker.pystr(),
            "age": faker.pyint(0, 100),
            "sex": "female",
        }
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/phenotypes",
            headers=admin_headers,
            data=phenotype2,
        )
        assert r.status_code == 404

        # create a phenotype with a geodata and a list of hpo
        graph = neo4j.get_instance()
        geodata_nodes = graph.GeoData.nodes
        geodata_uuid = geodata_nodes[0].uuid
        hpo_nodes = graph.HPO.nodes
        hpo1_id = hpo_nodes[0].hpo_id
        hpo2_id = hpo_nodes[1].hpo_id
        phenotype2["birth_place"] = geodata_uuid
        phenotype2["hpo"] = [hpo1_id, hpo2_id]
        phenotype2["hpo"] = json.dumps(phenotype2["hpo"])

        r = client.post(
            f"{API_URI}/study/{study1_uuid}/phenotypes",
            headers=user_B1_headers,
            data=phenotype2,
        )
        assert r.status_code == 200
        phenotype2_uuid = self.get_content(r)
        assert isinstance(phenotype2_uuid, str)

        # test phenotype access
        # test phenotype list response
        r = client.get(f"{API_URI}/study/{study1_uuid}/phenotypes",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        assert len(response) == 2

        # test phenotype list response for a study you don't have access
        r = client.get(f"{API_URI}/study/{study2_uuid}/phenotypes",
                       headers=user_B1_headers)
        assert r.status_code == 404

        # test phenotype list response for admin
        r = client.get(f"{API_URI}/study/{study1_uuid}/phenotypes",
                       headers=admin_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        assert len(response) == 2

        # test empty list of phenotypes in a study
        r = client.get(f"{API_URI}/study/{study2_uuid}/phenotypes",
                       headers=user_A1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        assert not response

        # study owner
        r = client.get(f"{API_URI}/phenotype/{phenotype1_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 200
        # same group of the study owner
        r = client.get(f"{API_URI}/phenotype/{phenotype2_uuid}",
                       headers=user_B2_headers)
        assert r.status_code == 200
        # check hpo and geodata were correctly linked
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert response["birth_place"]["uuid"] == geodata_uuid
        assert len(response["hpo"]) == 2
        hpo_list = []
        for el in response["hpo"]:
            hpo_list.append(el["hpo_id"])
        assert hpo1_id in hpo_list
        assert hpo2_id in hpo_list

        # phenotype owned by an other group
        r = client.get(f"{API_URI}/phenotype/{phenotype1_uuid}",
                       headers=user_A1_headers)
        assert r.status_code == 404
        not_authorized_message = self.get_content(r)
        assert isinstance(not_authorized_message, str)

        # admin access
        r = client.get(f"{API_URI}/phenotype/{phenotype1_uuid}",
                       headers=admin_headers)
        assert r.status_code == 200

        # test phenotype modification

        # modify a non existent phenotype
        random_phenotype = faker.pystr()
        r = client.put(
            f"{API_URI}/phenotype/{random_phenotype}",
            headers=user_A1_headers,
            data={
                "name": faker.pystr(),
                "sex": "female"
            },
        )
        assert r.status_code == 404
        # modify a phenotype you do not own
        r = client.put(
            f"{API_URI}/phenotype/{phenotype1_uuid}",
            headers=user_A1_headers,
            data={
                "name": faker.pystr(),
                "sex": "female"
            },
        )
        assert r.status_code == 404

        # modify a phenotype using a wrong age
        phenotype1["age"] = -8
        r = client.put(
            f"{API_URI}/phenotype/{phenotype1_uuid}",
            headers=user_B1_headers,
            data=phenotype1,
        )
        assert r.status_code == 400

        # modify a phenotype you own
        phenotype1["age"] = faker.pyint(0, 100)
        r = client.put(
            f"{API_URI}/phenotype/{phenotype1_uuid}",
            headers=user_B1_headers,
            data=phenotype1,
        )
        assert r.status_code == 204

        # admin modify a phenotype of a group he don't belongs
        r = client.put(
            f"{API_URI}/phenotype/{phenotype1_uuid}",
            headers=admin_headers,
            data={
                "name": faker.pystr(),
                "sex": "female"
            },
        )
        assert r.status_code == 404

        # add a new hpo and change the previous geodata
        hpo3_id = hpo_nodes[2].hpo_id
        geodata2_uuid = geodata_nodes[1].uuid
        phenotype2["name"] = faker.pystr()
        phenotype2["sex"] = "male"
        phenotype2["birth_place"] = geodata2_uuid
        phenotype2["hpo"] = [hpo1_id, hpo2_id, hpo3_id]
        phenotype2["hpo"] = json.dumps(phenotype2["hpo"])
        r = client.put(
            f"{API_URI}/phenotype/{phenotype2_uuid}",
            headers=user_B1_headers,
            data=phenotype2,
        )
        assert r.status_code == 204
        r = client.get(f"{API_URI}/phenotype/{phenotype2_uuid}",
                       headers=user_B2_headers)
        res = self.get_content(r)
        assert isinstance(res, dict)
        assert res["birth_place"]["uuid"] == geodata2_uuid
        assert len(res["hpo"]) == 3

        # delete all hpo and geodata
        data: Dict[str, Any] = {**phenotype2}
        data.pop("birth_place", None)
        data.pop("hpo", None)
        r = client.put(f"{API_URI}/phenotype/{phenotype2_uuid}",
                       headers=user_B1_headers,
                       data=data)
        assert r.status_code == 204
        r = client.get(f"{API_URI}/phenotype/{phenotype2_uuid}",
                       headers=user_B2_headers)
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert "birth_place" not in response
        assert not response["hpo"]

        # add a no existing geodata
        phenotype2["birth_place"] = faker.pystr()
        r = client.put(
            f"{API_URI}/phenotype/{phenotype2_uuid}",
            headers=user_B1_headers,
            data=phenotype2,
        )
        assert r.status_code == 400

        # delete a phenotype
        # delete a phenotype that does not exists
        r = client.delete(f"{API_URI}/phenotype/{random_phenotype}",
                          headers=user_A1_headers)
        assert r.status_code == 404
        # delete a phenotype in a study you do not own
        r = client.delete(f"{API_URI}/phenotype/{phenotype1_uuid}",
                          headers=user_A1_headers)
        assert r.status_code == 404
        # admin delete a phenotype of a group he don't belong
        r = client.delete(f"{API_URI}/phenotype/{phenotype1_uuid}",
                          headers=admin_headers)
        assert r.status_code == 404
        # delete a phenotype in a study you own
        r = client.delete(f"{API_URI}/phenotype/{phenotype1_uuid}",
                          headers=user_B1_headers)
        assert r.status_code == 204
        # delete a phenotype in a study own by your group
        r = client.delete(f"{API_URI}/phenotype/{phenotype2_uuid}",
                          headers=user_B2_headers)
        assert r.status_code == 204
        # check phenotype deletion
        r = client.get(f"{API_URI}/phenotype/{phenotype1_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 404
        not_existent_message = self.get_content(r)
        assert isinstance(not_existent_message, str)
        assert not_existent_message == not_authorized_message

        # delete all the elements used by the test
        delete_test_env(
            client,
            user_A1_headers,
            user_B1_headers,
            user_B1_uuid,
            user_B2_uuid,
            user_A1_uuid,
            uuid_group_A,
            uuid_group_B,
            study1_uuid=study1_uuid,
            study2_uuid=study2_uuid,
        )
Ejemplo n.º 8
0
    def test_admin_users(self, client: FlaskClient, faker: Faker) -> None:

        if not Env.get_bool("MAIN_LOGIN_ENABLE"):  # pragma: no cover
            log.warning("Skipping admin/users tests")
            return

        project_tile = get_project_configuration("project.title",
                                                 default="YourProject")

        headers, _ = self.do_login(client, None, None)
        r = client.get(f"{API_URI}/admin/users", headers=headers)
        assert r.status_code == 200

        schema = self.getDynamicInputSchema(client, "admin/users", headers)
        data = self.buildData(schema)
        data["email_notification"] = True
        data["is_active"] = True
        data["expiration"] = None

        # Event 1: create
        r = client.post(f"{API_URI}/admin/users", data=data, headers=headers)
        assert r.status_code == 200
        uuid = self.get_content(r)

        mail = self.read_mock_email()
        # Subject: is a key in the MIMEText
        assert mail.get("body") is not None
        assert mail.get("headers") is not None
        assert f"Subject: {project_tile}: new credentials" in mail.get(
            "headers")
        assert f"Username: {data.get('email', 'MISSING').lower()}" in mail.get(
            "body")
        assert f"Password: {data.get('password')}" in mail.get("body")

        # Event 2: read
        r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers)
        assert r.status_code == 200
        users_list = self.get_content(r)
        assert len(users_list) > 0
        # email is saved lowercase
        assert users_list[0].get("email") == data.get("email",
                                                      "MISSING").lower()

        # Check duplicates
        r = client.post(f"{API_URI}/admin/users", data=data, headers=headers)
        assert r.status_code == 409

        # Create another user
        data2 = self.buildData(schema)
        data2["email_notification"] = True
        data2["is_active"] = True
        data2["expiration"] = None

        # Event 3: create
        r = client.post(f"{API_URI}/admin/users", data=data2, headers=headers)
        assert r.status_code == 200
        uuid2 = self.get_content(r)

        mail = self.read_mock_email()
        # Subject: is a key in the MIMEText
        assert mail.get("body") is not None
        assert mail.get("headers") is not None
        assert f"Subject: {project_tile}: new credentials" in mail.get(
            "headers")
        assert f"Username: {data2.get('email', 'MISSING').lower()}" in mail.get(
            "body")
        assert f"Password: {data2.get('password')}" in mail.get("body")

        # send and invalid user_id
        r = client.put(
            f"{API_URI}/admin/users/invalid",
            data={"name": faker.name()},
            headers=headers,
        )
        assert r.status_code == 404

        # Event 4: modify
        r = client.put(
            f"{API_URI}/admin/users/{uuid}",
            data={"name": faker.name()},
            headers=headers,
        )
        assert r.status_code == 204

        # email cannot be modified
        new_data = {"email": data.get("email")}
        r = client.put(f"{API_URI}/admin/users/{uuid2}",
                       data=new_data,
                       headers=headers)
        # from webargs >= 6 this endpoint no longer return a 204 but a 400
        # because email is an unknown field
        # assert r.status_code == 204
        assert r.status_code == 400

        # Event 5: read
        r = client.get(f"{API_URI}/admin/users/{uuid2}", headers=headers)
        assert r.status_code == 200
        users_list = self.get_content(r)
        assert len(users_list) > 0
        # email is not modified -> still equal to data2, not data1
        assert users_list[0].get("email") != data.get("email",
                                                      "MISSING").lower()
        assert users_list[0].get("email") == data2.get("email",
                                                       "MISSING").lower()

        r = client.delete(f"{API_URI}/admin/users/invalid", headers=headers)
        assert r.status_code == 404

        # Event 6: delete
        r = client.delete(f"{API_URI}/admin/users/{uuid}", headers=headers)
        assert r.status_code == 204

        r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers)
        assert r.status_code == 404

        # change password of user2
        # Event 7: modify
        newpwd = faker.password(strong=True)
        data = {"password": newpwd, "email_notification": True}
        r = client.put(f"{API_URI}/admin/users/{uuid2}",
                       data=data,
                       headers=headers)
        assert r.status_code == 204

        mail = self.read_mock_email()
        # Subject: is a key in the MIMEText
        assert mail.get("body") is not None
        assert mail.get("headers") is not None
        assert f"Subject: {project_tile}: password changed" in mail.get(
            "headers")
        assert f"Username: {data2.get('email', 'MISSING').lower()}" in mail.get(
            "body")
        assert f"Password: {newpwd}" in mail.get("body")

        # login with a newly created user
        headers2, _ = self.do_login(client, data2.get("email"), newpwd)

        # normal users cannot access to this endpoint
        r = client.get(f"{API_URI}/admin/users", headers=headers2)
        assert r.status_code == 401

        r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers2)
        assert r.status_code == 401

        r = client.post(f"{API_URI}/admin/users", data=data, headers=headers2)
        assert r.status_code == 401

        r = client.put(
            f"{API_URI}/admin/users/{uuid}",
            data={"name": faker.name()},
            headers=headers2,
        )
        assert r.status_code == 401

        r = client.delete(f"{API_URI}/admin/users/{uuid}", headers=headers2)
        assert r.status_code == 401

        # Users are not authorized to /admin/tokens
        # These two tests should be moved in test_endpoints_tokens.py
        r = client.get(f"{API_URI}/admin/tokens", headers=headers2)
        assert r.status_code == 401
        r = client.delete(f"{API_URI}/admin/tokens/xyz", headers=headers2)
        assert r.status_code == 401

        # let's delete the second user
        # Event 8: delete
        r = client.delete(f"{API_URI}/admin/users/{uuid2}", headers=headers)
        assert r.status_code == 204

        # Restore the default password, if it changed due to FORCE_FIRST_PASSWORD_CHANGE
        # or MAX_PASSWORD_VALIDITY errors
        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 200
        uuid = self.get_content(r).get("uuid")

        data = {
            "password": BaseAuthentication.default_password,
            # very important, otherwise the default user will lose its admin role
            "roles": json.dumps(["admin_root"]),
        }
        # Event 9: modify
        r = client.put(f"{API_URI}/admin/users/{uuid}",
                       data=data,
                       headers=headers)
        assert r.status_code == 204

        r = client.get(f"{AUTH_URI}/logout", headers=headers)
        assert r.status_code == 204
Ejemplo n.º 9
0
    def test_tokens(self, client: FlaskClient, faker: Faker) -> None:

        if not Env.get_bool("AUTH_ENABLE"):
            log.warning("Skipping tokens tests")
            return

        last_token = None
        last_tokens_header = None
        token_id = None

        for _ in range(3):
            header, token = self.do_login(client, None, None)
            last_tokens_header = header
            last_token = token

        # TEST GET ALL TOKENS
        r = client.get(f"{AUTH_URI}/tokens", headers=last_tokens_header)
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)

        # Probably due to password expiration:
        # change password invalidated tokens created before
        # => create tokens again
        if len(content) < 3:  # pragma: no cover

            for _ in range(3):
                header, token = self.do_login(client, None, None)
                last_tokens_header = header
                last_token = token

            # TEST GET ALL TOKENS
            r = client.get(f"{AUTH_URI}/tokens", headers=last_tokens_header)
            assert r.status_code == 200
            content = self.get_content(r)
            assert isinstance(content, list)
            assert len(content) >= 3

        # save a token to be used for further tests
        for c in content:
            if c["token"] == last_token:
                continue
            token_id = c["id"]

        # SINGLE TOKEN IS NOT ALLOWED
        r = client.get(f"{AUTH_URI}/tokens/{token_id}", headers=last_tokens_header)
        assert r.status_code == 405

        # TEST GET ALL TOKENS
        r = client.get(f"{API_URI}/admin/tokens")
        assert r.status_code == 401

        # TEST GET ALL TOKENS
        r = client.get(
            f"{API_URI}/admin/tokens",
            query_string={"get_total": True},
            headers=last_tokens_header,
        )
        assert r.status_code == 206
        content = self.get_content(r)
        assert isinstance(content, dict)
        assert "total" in content
        assert content["total"] > 0

        r = client.get(
            f"{API_URI}/admin/tokens",
            query_string={"get_total": True, "input_filter": "1"},
            headers=last_tokens_header,
        )
        assert r.status_code == 206
        content = self.get_content(r)
        assert isinstance(content, dict)
        assert "total" in content
        assert content["total"] > 0

        r = client.get(
            f"{API_URI}/admin/tokens",
            query_string={"get_total": True, "input_filter": faker.pystr()},
            headers=last_tokens_header,
        )
        assert r.status_code == 206
        content = self.get_content(r)
        assert isinstance(content, dict)
        assert "total" in content
        assert content["total"] == 0

        r = client.get(
            f"{API_URI}/admin/tokens",
            query_string={"get_total": True, "page": 1, "size": 20},
            headers=last_tokens_header,
        )
        assert r.status_code == 206
        content = self.get_content(r)
        assert isinstance(content, dict)
        assert "total" in content
        assert content["total"] > 0

        r = client.get(
            f"{API_URI}/admin/tokens",
            query_string={"page": 0, "size": 20},
            headers=last_tokens_header,
        )
        assert r.status_code == 400

        r = client.get(
            f"{API_URI}/admin/tokens",
            query_string={"page": 1, "size": 0},
            headers=last_tokens_header,
        )
        assert r.status_code == 400

        r = client.get(
            f"{API_URI}/admin/tokens",
            query_string={"page": 1, "size": 101},
            headers=last_tokens_header,
        )
        assert r.status_code == 400

        r = client.get(
            f"{API_URI}/admin/tokens",
            query_string={"page": 99999, "size": 20},
            headers=last_tokens_header,
        )
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) == 0

        r = client.get(
            f"{API_URI}/admin/tokens",
            query_string={"page": 1, "size": 2},
            headers=last_tokens_header,
        )
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) <= 2

        r = client.get(
            f"{API_URI}/admin/tokens",
            query_string={"page": 1, "size": 20, "input_filter": "1"},
            headers=last_tokens_header,
        )
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) >= 1

        r = client.get(
            f"{API_URI}/admin/tokens",
            query_string={"page": 1, "size": 20, "input_filter": faker.pystr()},
            headers=last_tokens_header,
        )
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) == 0

        r = client.get(
            f"{API_URI}/admin/tokens",
            query_string={
                "page": 1,
                "size": 20,
                "input_filter": "1",
                # Sort_by emitted or other date cannot be done, because mysql truncate
                # the date, so that sort can't be predicted [several dates are reported]
                # as the same. Let's use a certainly unique field like uuid
                "sort_by": "uuid",
            },
            headers=last_tokens_header,
        )
        assert r.status_code == 200
        default_sort = self.get_content(r)
        assert isinstance(default_sort, list)
        assert len(default_sort) >= 2

        r = client.get(
            f"{API_URI}/admin/tokens",
            query_string={
                "page": 1,
                "size": 20,
                "input_filter": "1",
                # Sort_by emitted or other date cannot be done, because mysql truncate
                # the date, so that sort can't be predicted [several dates are reported]
                # as the same. Let's use a certainly unique field like uuid
                "sort_by": "uuid",
                "sort_order": "asc",
            },
            headers=last_tokens_header,
        )
        assert r.status_code == 200
        asc_sort = self.get_content(r)
        assert isinstance(asc_sort, list)
        assert len(asc_sort) >= 2
        assert default_sort[0]["token"] == asc_sort[0]["token"]
        assert default_sort[-1]["token"] == asc_sort[-1]["token"]

        r = client.get(
            f"{API_URI}/admin/tokens",
            query_string={
                "page": 1,
                "size": 20,
                "input_filter": "1",
                # Sort_by emitted or other date cannot be done, because mysql truncate
                # the date, so that sort can't be predicted [several dates are reported]
                # as the same. Let's use a certainly unique field like uuid
                "sort_by": "uuid",
                "sort_order": "desc",
            },
            headers=last_tokens_header,
        )
        assert r.status_code == 200
        desc_sort = self.get_content(r)
        assert isinstance(desc_sort, list)
        # Results of desc_sort can't be compared with previous contents
        # It may only be done if we were able to retrieve all tokens, in this case the
        # first desc will be the last asc... But we cannot ensure to be able to always
        # retrieve all tokens.
        assert len(desc_sort) >= 2
        # At least they should be different
        # assert asc_sort[0] != desc_sort[0]
        # assert asc_sort[-1] != desc_sort[-1]

        # TEST GET ALL TOKENS
        r = client.get(f"{API_URI}/admin/tokens", headers=last_tokens_header)
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)
        assert len(content) >= 3

        # DELETE INVALID TOKEN
        r = client.delete(f"{API_URI}/admin/tokens/xyz", headers=last_tokens_header)
        assert r.status_code == 404

        # TEST DELETE OF A SINGLE TOKEN
        r = client.delete(f"{AUTH_URI}/tokens/{token_id}", headers=last_tokens_header)
        assert r.status_code == 204

        events = self.get_last_events(1)
        assert events[0].event == Events.delete.value
        assert events[0].target_type == "Token"
        # Tokens does not have a uuid...
        # assert events[0].target_id == token_id
        assert events[0].user == "-"
        assert events[0].url == f"/auth/tokens/{token_id}"

        # TEST AN ALREADY DELETED TOKEN
        r = client.delete(f"{AUTH_URI}/tokens/{token_id}", headers=last_tokens_header)
        assert r.status_code == 403

        # TEST INVALID DELETE OF A SINGLE TOKEN
        r = client.delete(f"{AUTH_URI}/tokens/0", headers=last_tokens_header)
        assert r.status_code == 403

        # TEST TOKEN IS STILL VALID
        r = client.get(f"{AUTH_URI}/tokens", headers=last_tokens_header)
        assert r.status_code == 200

        # user_header will be used as target for deletion
        # Always enabled in core tests
        if not Env.get_bool("MAIN_LOGIN_ENABLE"):  # pragma: no cover
            uuid = None
            user_header, token = self.do_login(client, None, None)
        else:
            uuid, data = self.create_user(client)
            user_header, token = self.do_login(client, data["email"], data["password"])

        r = client.get(f"{AUTH_URI}/status", headers=user_header)
        assert r.status_code == 200

        # TEST GET ALL TOKENS
        r = client.get(f"{AUTH_URI}/tokens", headers=user_header)
        assert r.status_code == 200
        content = self.get_content(r)
        assert isinstance(content, list)

        token_id = None
        for c in content:
            if c["token"] == token:
                token_id = c["id"]
                break

        assert token_id is not None

        last_tokens_header, _ = self.do_login(client, None, None)
        r = client.delete(
            f"{API_URI}/admin/tokens/{token_id}", headers=last_tokens_header
        )
        assert r.status_code == 204

        events = self.get_last_events(1)
        assert events[0].event == Events.delete.value
        assert events[0].target_type == "Token"
        # Tokens does not have a uuid...
        # assert events[0].target_id == token_id
        assert events[0].user == "-"
        assert events[0].url == f"/api/admin/tokens/{token_id}"

        r = client.delete(
            f"{API_URI}/admin/tokens/{token_id}", headers=last_tokens_header
        )
        assert r.status_code == 404

        r = client.get(f"{AUTH_URI}/status", headers=user_header)
        assert r.status_code == 401

        # Goodbye temporary user (if previously created)
        if uuid:
            self.delete_user(client, uuid)
Ejemplo n.º 10
0
    def test_api_family(self, client: FlaskClient, faker: Faker) -> None:
        # setup the test env
        (
            admin_headers,
            uuid_group_A,
            user_A1_uuid,
            user_A1_headers,
            uuid_group_B,
            user_B1_uuid,
            user_B1_headers,
            user_B2_uuid,
            user_B2_headers,
            study1_uuid,
            study2_uuid,
        ) = create_test_env(client, faker, study=True)

        # create new phenotypes
        phenotype_father = {
            "name": faker.pystr(),
            "age": faker.pyint(0, 100),
            "sex": "male",
        }
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/phenotypes",
            headers=user_B1_headers,
            data=phenotype_father,
        )
        assert r.status_code == 200
        phenotype_father_uuid = self.get_content(r)
        assert isinstance(phenotype_father_uuid, str)

        phenotype_mother = {
            "name": faker.pystr(),
            "age": faker.pyint(0, 100),
            "sex": "female",
        }
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/phenotypes",
            headers=user_B1_headers,
            data=phenotype_mother,
        )
        assert r.status_code == 200
        phenotype_mother_uuid = self.get_content(r)
        assert isinstance(phenotype_mother_uuid, str)

        phenotype_son_B = {
            "name": faker.pystr(),
            "age": faker.pyint(0, 100),
            "sex": "female",
        }
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/phenotypes",
            headers=user_B1_headers,
            data=phenotype_son_B,
        )
        assert r.status_code == 200
        phenotype_son_B_uuid = self.get_content(r)
        assert isinstance(phenotype_son_B_uuid, str)

        phenotype_son_A = {
            "name": faker.pystr(),
            "age": faker.pyint(0, 100),
            "sex": "female",
        }
        r = client.post(
            f"{API_URI}/study/{study2_uuid}/phenotypes",
            headers=user_A1_headers,
            data=phenotype_son_A,
        )
        assert r.status_code == 200
        phenotype_son_A_uuid = self.get_content(r)
        assert isinstance(phenotype_son_A_uuid, str)

        # create a relationship
        # father case
        r = client.post(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_father_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 200
        graph = neo4j.get_instance()
        phenotype_father_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_father_uuid)
        phenotype_son_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_son_B_uuid)
        assert phenotype_father_node.son.is_connected(phenotype_son_node)
        assert phenotype_son_node.father.is_connected(phenotype_father_node)

        # test relationships in get phenotype list response
        r = client.get(f"{API_URI}/study/{study1_uuid}/phenotypes",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        for el in response:
            if el["uuid"] == phenotype_son_B_uuid:
                assert el["relationships"]["father"][
                    "uuid"] == phenotype_father_uuid
            if el["uuid"] == phenotype_father_uuid:
                assert el["relationships"]["sons"][0][
                    "uuid"] == phenotype_son_B_uuid

        # test relationships in get single phenotype response
        r = client.get(f"{API_URI}/phenotype/{phenotype_son_B_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert response["relationships"]["father"][
            "uuid"] == phenotype_father_uuid

        r = client.get(f"{API_URI}/phenotype/{phenotype_father_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert response["relationships"]["sons"][0][
            "uuid"] == phenotype_son_B_uuid

        # create a relationship for two phenotypes in an other study
        r = client.post(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}",
            headers=user_A1_headers,
        )
        assert r.status_code == 404

        # admin creates a relationship
        r = client.post(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}",
            headers=admin_headers,
        )
        assert r.status_code == 404

        # a user of the same group of the owner create a relationship
        # mother case
        r = client.post(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}",
            headers=user_B2_headers,
        )
        assert r.status_code == 200
        graph = neo4j.get_instance()
        phenotype_mother_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_mother_uuid)
        phenotype_son_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_son_B_uuid)
        assert phenotype_mother_node.son.is_connected(phenotype_son_node)
        assert phenotype_son_node.mother.is_connected(phenotype_mother_node)

        # test relationships in get phenotype list response
        r = client.get(f"{API_URI}/study/{study1_uuid}/phenotypes",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        for el in response:
            if el["uuid"] == phenotype_son_B_uuid:
                assert el["relationships"]["mother"][
                    "uuid"] == phenotype_mother_uuid
            if el["uuid"] == phenotype_mother_uuid:
                assert el["relationships"]["sons"][0][
                    "uuid"] == phenotype_son_B_uuid

        # test relationships in get single phenotype response
        r = client.get(f"{API_URI}/phenotype/{phenotype_son_B_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert response["relationships"]["mother"][
            "uuid"] == phenotype_mother_uuid

        # relationship between phenotype from different studies
        r = client.post(
            f"{API_URI}/phenotype/{phenotype_son_A_uuid}/relationships/{phenotype_father_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 404

        # relationship with a random phenotype as son
        random_phenotype_uuid = faker.pystr()
        r = client.post(
            f"{API_URI}/phenotype/{random_phenotype_uuid}/relationships/{phenotype_father_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 404

        # relationship with a random phenotype as father
        r = client.post(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{random_phenotype_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 404

        # relationship with itself
        r = client.post(
            f"{API_URI}/phenotype/{phenotype_father_uuid}/relationships/{phenotype_father_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 400

        # delete a relationship
        # father case
        r = client.delete(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_father_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 204
        graph = neo4j.get_instance()
        phenotype_father_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_father_uuid)
        phenotype_son_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_son_B_uuid)
        assert not phenotype_father_node.son.single()
        assert not phenotype_son_node.father.single()

        # delete a relationship for two phenotypes in an other study
        r = client.delete(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}",
            headers=user_A1_headers,
        )
        assert r.status_code == 404

        # admin delete a relationship
        r = client.delete(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}",
            headers=admin_headers,
        )
        assert r.status_code == 404

        # a user of the same group of the owner delete a relationship
        # mother case
        r = client.delete(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}",
            headers=user_B2_headers,
        )
        assert r.status_code == 204
        graph = neo4j.get_instance()
        phenotype_mother_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_mother_uuid)
        phenotype_son_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_son_B_uuid)
        assert not phenotype_mother_node.son.single()
        assert not phenotype_son_node.mother.single()

        # delete relationship between phenotype from different studies
        r = client.delete(
            f"{API_URI}/phenotype/{phenotype_son_A_uuid}/relationships/{phenotype_father_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 404

        #  delete relationship with a random phenotype as son
        r = client.delete(
            f"{API_URI}/phenotype/{random_phenotype_uuid}/relationships/{phenotype_father_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 404

        # delete relationship with a random phenotype as father
        r = client.delete(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{random_phenotype_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 404

        r = client.post(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_father_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 200

        r = client.post(
            f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 200

        # delete a son relationship
        r = client.delete(
            f"{API_URI}/phenotype/{phenotype_mother_uuid}/relationships/{phenotype_son_B_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 204
        graph = neo4j.get_instance()
        phenotype_mother_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_mother_uuid)
        phenotype_son_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_son_B_uuid)
        assert not phenotype_mother_node.son.single()
        assert not phenotype_son_node.mother.single()

        r = client.delete(
            f"{API_URI}/phenotype/{phenotype_father_uuid}/relationships/{phenotype_son_B_uuid}",
            headers=user_B1_headers,
        )
        assert r.status_code == 204
        graph = neo4j.get_instance()
        phenotype_father_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_father_uuid)
        phenotype_son_node = graph.Phenotype.nodes.get_or_none(
            uuid=phenotype_son_B_uuid)
        assert not phenotype_father_node.son.single()
        assert not phenotype_son_node.father.single()

        # delete all the elements used by the test
        delete_test_env(
            client,
            user_A1_headers,
            user_B1_headers,
            user_B1_uuid,
            user_B2_uuid,
            user_A1_uuid,
            uuid_group_A,
            uuid_group_B,
            study1_uuid=study1_uuid,
            study2_uuid=study2_uuid,
        )
Ejemplo n.º 11
0
    def test_api_study(self, client: FlaskClient, faker: Faker) -> None:
        # setup the test env
        (
            admin_headers,
            uuid_group_A,
            user_A1_uuid,
            user_A1_headers,
            uuid_group_B,
            user_B1_uuid,
            user_B1_headers,
            user_B2_uuid,
            user_B2_headers,
            study1_uuid,
            study2_uuid,
        ) = create_test_env(client, faker, study=False)

        # create a new study for the group B
        random_name = faker.pystr()
        study1 = {"name": random_name, "description": faker.pystr()}
        r = client.post(f"{API_URI}/study",
                        headers=user_B1_headers,
                        data=study1)
        assert r.status_code == 200
        study1_uuid = self.get_content(r)
        assert isinstance(study1_uuid, str)

        # create a new study for the group A
        random_name2 = faker.pystr()
        study2 = {"name": random_name2, "description": faker.pystr()}
        r = client.post(f"{API_URI}/study",
                        headers=user_A1_headers,
                        data=study2)
        assert r.status_code == 200
        study2_uuid = self.get_content(r)
        assert isinstance(study2_uuid, str)

        # check the directory was created
        dir_path = INPUT_ROOT.joinpath(uuid_group_A, study2_uuid)
        assert dir_path.is_dir()

        # test study access
        # test study list response
        r = client.get(f"{API_URI}/study", headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        assert len(response) == 1

        # test admin access
        r = client.get(f"{API_URI}/study/{study1_uuid}", headers=admin_headers)
        assert r.status_code == 200

        # study owner
        r = client.get(f"{API_URI}/study/{study1_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 200
        # other component of the group
        r = client.get(f"{API_URI}/study/{study1_uuid}",
                       headers=user_B2_headers)
        assert r.status_code == 200
        # study own by an other group
        r = client.get(f"{API_URI}/study/{study1_uuid}",
                       headers=user_A1_headers)
        assert r.status_code == 404
        not_authorized_message = self.get_content(r)
        assert isinstance(not_authorized_message, str)

        # test study modification
        # modify a study you do not own
        r = client.put(
            f"{API_URI}/study/{study1_uuid}",
            headers=user_A1_headers,
            data={"description": faker.pystr()},
        )
        assert r.status_code == 404
        # modify a study you own
        r = client.put(
            f"{API_URI}/study/{study1_uuid}",
            headers=user_B1_headers,
            data={"description": faker.pystr()},
        )
        assert r.status_code == 204

        # delete a study
        # delete a study you do not own
        r = client.delete(f"{API_URI}/study/{study1_uuid}",
                          headers=user_A1_headers)
        assert r.status_code == 404
        # delete a study you own
        # create a new dataset to test if it's deleted with the study
        dataset = {"name": faker.pystr(), "description": faker.pystr()}
        r = client.post(
            f"{API_URI}/study/{study2_uuid}/datasets",
            headers=user_A1_headers,
            data=dataset,
        )
        assert r.status_code == 200
        dataset_uuid = self.get_content(r)
        assert isinstance(dataset_uuid, str)
        dataset_path = dir_path.joinpath(dataset_uuid)
        assert dataset_path.is_dir()
        # create a new file to test if it's deleted with the study
        filename = f"{faker.pystr()}_R1"
        file_data = {
            "name": f"{filename}.fastq.gz",
            "mimeType": "application/gzip",
            "size": faker.pyint(),
            "lastModified": faker.pyint(),
        }
        r = client.post(
            f"{API_URI}/dataset/{dataset_uuid}/files/upload",
            headers=user_A1_headers,
            data=file_data,
        )
        assert r.status_code == 201
        # get the file uuid
        r = client.get(
            f"{API_URI}/dataset/{dataset_uuid}/files",
            headers=user_A1_headers,
        )
        assert r.status_code == 200
        file_list = self.get_content(r)
        assert isinstance(file_list, list)
        file_uuid = file_list[0]["uuid"]

        # create a new technical to test if it's deleted with the study
        techmeta = {"name": faker.pystr()}
        r = client.post(
            f"{API_URI}/study/{study2_uuid}/technicals",
            headers=user_A1_headers,
            data=techmeta,
        )
        assert r.status_code == 200
        techmeta_uuid = self.get_content(r)
        assert isinstance(techmeta_uuid, str)
        # create a new phenotype to test if it's deleted with the study
        phenotype = {"name": faker.pystr(), "sex": "male"}
        r = client.post(
            f"{API_URI}/study/{study2_uuid}/phenotypes",
            headers=user_A1_headers,
            data=phenotype,
        )
        assert r.status_code == 200
        phenotype_uuid = self.get_content(r)
        assert isinstance(phenotype_uuid, str)
        # simulate the study has an output directory
        # create the output directory in the same way is created in launch pipeline task
        output_path = OUTPUT_ROOT.joinpath(
            dataset_path.relative_to(INPUT_ROOT))
        output_path.mkdir(parents=True)
        assert output_path.is_dir()

        # delete the study
        r = client.delete(f"{API_URI}/study/{study2_uuid}",
                          headers=user_A1_headers)
        assert r.status_code == 204
        assert not dir_path.is_dir()
        assert not dataset_path.is_dir()
        # check the dataset was deleted
        r = client.get(f"{API_URI}/dataset/{dataset_uuid}",
                       headers=user_A1_headers)
        assert r.status_code == 404
        # check the file was deleted
        r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_A1_headers)
        assert r.status_code == 404
        # check the technical was deleted
        r = client.get(f"{API_URI}/technical/{techmeta_uuid}",
                       headers=user_A1_headers)
        assert r.status_code == 404
        # check the phenotype was deleted
        r = client.get(f"{API_URI}/phenotype/{phenotype_uuid}",
                       headers=user_A1_headers)
        assert r.status_code == 404
        # check the output dir was deleted
        assert not output_path.is_dir()

        # delete a study own by your group
        r = client.delete(f"{API_URI}/study/{study1_uuid}",
                          headers=user_B2_headers)
        assert r.status_code == 204
        # check study deletion
        r = client.get(f"{API_URI}/study/{study1_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 404
        not_existent_message = self.get_content(r)
        assert isinstance(not_existent_message, str)
        assert not_existent_message == not_authorized_message

        # delete all the elements used by the test
        delete_test_env(
            client,
            user_A1_headers,
            user_B1_headers,
            user_B1_uuid,
            user_B2_uuid,
            user_A1_uuid,
            uuid_group_A,
            uuid_group_B,
        )
Ejemplo n.º 12
0
    def test_api_techmeta(self, client: FlaskClient, faker: Faker) -> None:
        # setup the test env
        (
            admin_headers,
            uuid_group_A,
            user_A1_uuid,
            user_A1_headers,
            uuid_group_B,
            user_B1_uuid,
            user_B1_headers,
            user_B2_uuid,
            user_B2_headers,
            study1_uuid,
            study2_uuid,
        ) = create_test_env(client, faker, study=True)

        # create a new techmeta
        techmeta1 = {
            "name": faker.pystr(),
            "sequencing_date": faker.date(),
            "platform": "Other",
        }
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/technicals",
            headers=user_B1_headers,
            data=techmeta1,
        )
        assert r.status_code == 200
        techmeta1_uuid = self.get_content(r)
        assert isinstance(techmeta1_uuid, str)

        # create a new techmeta in a study of an other group
        r = client.post(
            f"{API_URI}/study/{study2_uuid}/technicals",
            headers=user_B1_headers,
            data=techmeta1,
        )
        assert r.status_code == 404

        # create a new technical as admin not belonging to study group
        techmeta2 = {
            "name": faker.pystr(),
            "sequencing_date": faker.date(),
            "platform": "Other",
        }
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/technicals",
            headers=admin_headers,
            data=techmeta2,
        )
        assert r.status_code == 404

        r = client.post(
            f"{API_URI}/study/{study1_uuid}/technicals",
            headers=user_B1_headers,
            data=techmeta2,
        )
        assert r.status_code == 200
        techmeta2_uuid = self.get_content(r)
        assert isinstance(techmeta2_uuid, str)

        # test technical access
        # test technical list response
        r = client.get(f"{API_URI}/study/{study1_uuid}/technicals",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        assert len(response) == 2

        # test technical list response for a study you don't have access
        r = client.get(f"{API_URI}/study/{study2_uuid}/technicals",
                       headers=user_B1_headers)
        assert r.status_code == 404

        # test technical list response for admin
        r = client.get(f"{API_URI}/study/{study1_uuid}/technicals",
                       headers=admin_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        assert len(response) == 2

        # test empty list of technicals in a study
        r = client.get(f"{API_URI}/study/{study2_uuid}/technicals",
                       headers=user_A1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        assert not response

        # study owner
        r = client.get(f"{API_URI}/technical/{techmeta1_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 200
        # same group of the study owner
        r = client.get(f"{API_URI}/technical/{techmeta1_uuid}",
                       headers=user_B2_headers)
        assert r.status_code == 200
        # technical owned by an other group
        r = client.get(f"{API_URI}/technical/{techmeta1_uuid}",
                       headers=user_A1_headers)
        assert r.status_code == 404
        not_authorized_message = self.get_content(r)
        assert isinstance(not_authorized_message, str)

        # admin access
        r = client.get(f"{API_URI}/technical/{techmeta1_uuid}",
                       headers=admin_headers)
        assert r.status_code == 200

        # test technical modification

        # modify a non existent technical
        random_technical = faker.pystr()
        r = client.put(
            f"{API_URI}/technical/{random_technical}",
            headers=user_A1_headers,
            data={"name": faker.pystr()},
        )
        assert r.status_code == 404
        # modify a technical you do not own
        r = client.put(
            f"{API_URI}/technical/{techmeta1_uuid}",
            headers=user_A1_headers,
            data={"name": faker.pystr()},
        )
        assert r.status_code == 404
        # modify a technical you own
        r = client.put(
            f"{API_URI}/technical/{techmeta1_uuid}",
            headers=user_B1_headers,
            data={
                "name": faker.pystr(),
                "sequencing_date": faker.date()
            },
        )
        assert r.status_code == 204

        # admin modify a technical of a group he don't belongs
        r = client.put(
            f"{API_URI}/technical/{techmeta1_uuid}",
            headers=admin_headers,
            data={"name": faker.pystr()},
        )
        assert r.status_code == 404

        # delete a technical
        # delete a technical that does not exists
        r = client.delete(f"{API_URI}/technical/{random_technical}",
                          headers=user_A1_headers)
        assert r.status_code == 404
        # delete a technical in a study you do not own
        r = client.delete(f"{API_URI}/technical/{techmeta1_uuid}",
                          headers=user_A1_headers)
        assert r.status_code == 404
        # admin delete a technical of a group he don't belong
        r = client.delete(f"{API_URI}/technical/{techmeta1_uuid}",
                          headers=admin_headers)
        assert r.status_code == 404
        # delete a technical in a study you own
        r = client.delete(f"{API_URI}/technical/{techmeta1_uuid}",
                          headers=user_B1_headers)
        assert r.status_code == 204
        # delete a technical in a study own by your group
        r = client.delete(f"{API_URI}/technical/{techmeta2_uuid}",
                          headers=user_B2_headers)
        assert r.status_code == 204
        # check technical deletion
        r = client.get(f"{API_URI}/technical/{techmeta1_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 404
        not_existent_message = self.get_content(r)
        assert isinstance(not_existent_message, str)
        assert not_existent_message == not_authorized_message

        # delete all the elements used by the test
        delete_test_env(
            client,
            user_A1_headers,
            user_B1_headers,
            user_B1_uuid,
            user_B2_uuid,
            user_A1_uuid,
            uuid_group_A,
            uuid_group_B,
            study1_uuid=study1_uuid,
            study2_uuid=study2_uuid,
        )
Ejemplo n.º 13
0
    def test_api_file(self, client: FlaskClient, faker: Faker) -> None:
        # setup the test env
        (
            admin_headers,
            uuid_group_A,
            user_A1_uuid,
            user_A1_headers,
            uuid_group_B,
            user_B1_uuid,
            user_B1_headers,
            user_B2_uuid,
            user_B2_headers,
            study1_uuid,
            study2_uuid,
        ) = create_test_env(client, faker, study=True)
        # create a new dataset
        dataset_B = {"name": faker.pystr(), "description": faker.pystr()}
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/datasets",
            headers=user_B1_headers,
            data=dataset_B,
        )
        assert r.status_code == 200
        dataset_B_uuid = self.get_content(r)
        assert isinstance(dataset_B_uuid, str)

        # check accesses for post request
        # upload a new file in a dataset of an other group
        fake_file = {
            "name": f"{faker.pystr()}_R1.fastq.gz",
            "mimeType": "application/gzip",
            "size": faker.pyint(),
            "lastModified": faker.pyint(),
        }
        r = client.post(
            f"{API_URI}/dataset/{dataset_B_uuid}/files/upload",
            headers=user_A1_headers,
            data=fake_file,
        )
        assert r.status_code == 404

        # upload a new file as admin not belonging to study group
        r = client.post(
            f"{API_URI}/dataset/{dataset_B_uuid}/files/upload",
            headers=admin_headers,
            data=fake_file,
        )
        assert r.status_code == 404

        # try to upload a file with a non allowed format
        fake_format = {
            "name": f"{faker.pystr()}_R1.txt",
            "mimeType": "text/plain",
            "size": faker.pyint(),
            "lastModified": faker.pyint(),
        }
        r = client.post(
            f"{API_URI}/dataset/{dataset_B_uuid}/files/upload",
            headers=user_B1_headers,
            data=fake_format,
        )
        assert r.status_code == 400

        # try to upload a file with a wrong nomenclature
        fake_nomencl_file = {
            "name":
            f"{faker.pystr()}.{faker.pystr()}_R1.fastq.gz.{faker.pystr()}",
            "mimeType": "text/plain",
            "size": faker.pyint(),
            "lastModified": faker.pyint(),
        }
        r = client.post(
            f"{API_URI}/dataset/{dataset_B_uuid}/files/upload",
            headers=user_B1_headers,
            data=fake_nomencl_file,
        )
        assert r.status_code == 400

        fake_nomencl_file2 = {
            "name": f"{faker.pystr()}.{faker.pystr()}_R1.fastq.gz",
            "mimeType": "text/plain",
            "size": faker.pyint(),
            "lastModified": faker.pyint(),
        }
        r = client.post(
            f"{API_URI}/dataset/{dataset_B_uuid}/files/upload",
            headers=user_B1_headers,
            data=fake_nomencl_file2,
        )
        assert r.status_code == 400

        valid_fcontent = f"@SEQ_ID\n{faker.pystr(max_chars=12)}\n+{faker.pystr()}\n{faker.pystr(max_chars=12)}"
        # invalid header, @ is missing
        invalid_header = f"SEQ_ID\n{faker.pystr(max_chars=12)}\n+{faker.pystr()}\n{faker.pystr(max_chars=12)}"
        # CASE invalid separator
        invalid_separator = f"@SEQ_ID\n{faker.pystr(max_chars=12)}\n{faker.pystr()}\n{faker.pystr(max_chars=12)}"
        # len of sequence != len of quality
        invalid_sequence = f"@SEQ_ID\n{faker.pystr(max_chars=12)}\n+{faker.pystr()}\n{faker.pystr(max_chars=8)}"
        # invalid second header
        invalid_header2 = f"@SEQ_ID\n{faker.pystr(max_chars=12)}\n+{faker.pystr()}\n{faker.pystr(max_chars=12)}\n{faker.pystr()}"

        # create a file to upload
        fastq = self.create_fastq_gz(faker, valid_fcontent)

        # upload a file
        response = self.upload_file(client,
                                    user_B1_headers,
                                    fastq,
                                    dataset_B_uuid,
                                    stream=True)
        assert response.status_code == 200
        # check the file exists and have the expected size
        filename = fastq.name
        filesize = fastq.stat().st_size
        filepath = INPUT_ROOT.joinpath(uuid_group_B, study1_uuid,
                                       dataset_B_uuid, filename)
        assert filepath.is_file()
        assert filepath.stat().st_size == filesize

        # upload the same file twice
        response = self.upload_file(client,
                                    user_B2_headers,
                                    fastq,
                                    dataset_B_uuid,
                                    stream=True)
        assert response.status_code == 409

        # upload the same file in a different dataset
        # create a new dataset
        dataset_B2 = {"name": faker.pystr(), "description": faker.pystr()}
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/datasets",
            headers=user_B1_headers,
            data=dataset_B2,
        )
        assert r.status_code == 200
        dataset_B2_uuid = self.get_content(r)
        assert isinstance(dataset_B2_uuid, str)
        response = self.upload_file(client,
                                    user_B2_headers,
                                    fastq,
                                    dataset_B2_uuid,
                                    stream=True)
        assert response.status_code == 200

        # check error if final file size is different from the expected
        # rename the file to upload
        fastq2 = fastq.parent.joinpath(f"{faker.pystr()}_R1.fastq.gz")
        fastq.rename(fastq2)
        # upload without streaming
        response = self.upload_file(
            client,
            user_B1_headers,
            fastq2,
            dataset_B_uuid,
            stream=False,
        )
        assert response.status_code == 500
        error_message = self.get_content(response)
        assert isinstance(error_message, str)
        assert (
            error_message ==
            "File has not been uploaded correctly: final size does not correspond to total size. Please try a new upload"
        )
        # check uncomplete file has been removed
        check_filepath = INPUT_ROOT.joinpath(
            uuid_group_B,
            study1_uuid,
            dataset_B_uuid,
            fastq2.name,
        )
        assert not check_filepath.is_file()

        # check file validation
        # upload an empty file
        empty_file = self.create_fastq_gz(faker, "")
        response = self.upload_file(
            client,
            user_B1_headers,
            empty_file,
            dataset_B_uuid,
            stream=True,
        )
        assert response.status_code == 400
        # check the empty file has been removed
        check_filepath = INPUT_ROOT.joinpath(
            uuid_group_B,
            study1_uuid,
            dataset_B_uuid,
            empty_file.name,
        )
        assert not check_filepath.is_file()
        empty_file.unlink()

        # upload a file with not valid content
        # CASE wrong gzip file
        wrong_gzip = Path(tempfile.gettempdir(),
                          f"{faker.pystr()}_R1.fastq.gz")

        # Directly write the gz => it is an ascii file and not a valid gz
        with open(wrong_gzip, "w") as f:
            f.write(valid_fcontent)

        response = self.upload_file(
            client,
            user_B1_headers,
            wrong_gzip,
            dataset_B_uuid,
            stream=True,
        )
        assert response.status_code == 400
        error_message = self.get_content(response)
        assert isinstance(error_message, str)
        assert "gzipped" in error_message

        # check the empty file has been removed
        check_filepath = INPUT_ROOT.joinpath(
            uuid_group_B,
            study1_uuid,
            dataset_B_uuid,
            wrong_gzip.name,
        )
        assert not check_filepath.is_file()
        wrong_gzip.unlink()

        # CASE binary file instead of a text file
        binary_file = self.create_fastq_gz(faker, faker.binary(), mode="wb")
        response = self.upload_file(
            client,
            user_B1_headers,
            binary_file,
            dataset_B_uuid,
            stream=True,
        )
        assert response.status_code == 400
        error_message = self.get_content(response)
        assert isinstance(error_message, str)
        assert "binary" in error_message

        check_filepath = INPUT_ROOT.joinpath(
            uuid_group_B,
            study1_uuid,
            dataset_B_uuid,
            binary_file.name,
        )
        assert not check_filepath.is_file()
        binary_file.unlink()

        # CASE invalid header
        invalid_fastq = self.create_fastq_gz(faker, invalid_header)
        response = self.upload_file(
            client,
            user_B1_headers,
            invalid_fastq,
            dataset_B_uuid,
            stream=True,
        )
        assert response.status_code == 400
        error_message = self.get_content(response)
        assert isinstance(error_message, str)
        assert "header" in error_message

        check_filepath = INPUT_ROOT.joinpath(
            uuid_group_B,
            study1_uuid,
            dataset_B_uuid,
            invalid_fastq.name,
        )
        assert not check_filepath.is_file()
        invalid_fastq.unlink()

        # CASE invalid separator
        invalid_fastq = self.create_fastq_gz(faker, invalid_separator)
        response = self.upload_file(
            client,
            user_B1_headers,
            invalid_fastq,
            dataset_B_uuid,
            stream=True,
        )
        assert response.status_code == 400
        error_message = self.get_content(response)
        assert isinstance(error_message, str)
        assert "separator" in error_message

        check_filepath = INPUT_ROOT.joinpath(
            uuid_group_B,
            study1_uuid,
            dataset_B_uuid,
            invalid_fastq.name,
        )
        assert not check_filepath.is_file()
        invalid_fastq.unlink()

        # CASE invalid sequence line
        invalid_fastq = self.create_fastq_gz(faker, invalid_sequence)
        response = self.upload_file(
            client,
            user_B1_headers,
            invalid_fastq,
            dataset_B_uuid,
            stream=True,
        )
        assert response.status_code == 400
        error_message = self.get_content(response)
        assert isinstance(error_message, str)
        assert "lines lengths differ" in error_message

        check_filepath = INPUT_ROOT.joinpath(
            uuid_group_B,
            study1_uuid,
            dataset_B_uuid,
            invalid_fastq.name,
        )
        assert not check_filepath.is_file()
        invalid_fastq.unlink()

        # CASE invalid header for the second read
        invalid_fastq = self.create_fastq_gz(faker, invalid_header2)
        response = self.upload_file(
            client,
            user_B1_headers,
            invalid_fastq,
            dataset_B_uuid,
            stream=True,
        )
        assert response.status_code == 400
        error_message = self.get_content(response)
        assert isinstance(error_message, str)
        assert "header" in error_message

        check_filepath = INPUT_ROOT.joinpath(
            uuid_group_B,
            study1_uuid,
            dataset_B_uuid,
            invalid_fastq.name,
        )
        assert not check_filepath.is_file()
        invalid_fastq.unlink()

        # check accesses on put endpoint
        # put on a file in a dataset of an other group
        r = client.put(
            f"{API_URI}/dataset/{dataset_B_uuid}/files/upload/{filename}",
            headers=user_A1_headers,
        )
        assert r.status_code == 404
        # put a file as admin not belonging to study group
        r = client.put(
            f"{API_URI}/dataset/{dataset_B_uuid}/files/upload/{filename}",
            headers=admin_headers,
        )
        assert r.status_code == 404

        # put of a ton existent file
        r = client.put(
            f"{API_URI}/dataset/{dataset_B_uuid}/files/upload/{fastq2}.txt.gz",
            headers=user_B1_headers,
        )
        assert r.status_code == 404

        # test file access
        # test file list response
        r = client.get(f"{API_URI}/dataset/{dataset_B_uuid}/files",
                       headers=user_B1_headers)
        assert r.status_code == 200
        file_list = self.get_content(r)
        assert isinstance(file_list, list)
        assert len(file_list) == 1
        file_uuid = file_list[0]["uuid"]

        # test file list response for a dataset you don't have access
        r = client.get(f"{API_URI}/dataset/{dataset_B_uuid}/files",
                       headers=user_A1_headers)
        assert r.status_code == 404

        # test file list response for admin
        r = client.get(f"{API_URI}/dataset/{dataset_B_uuid}/files",
                       headers=admin_headers)
        assert r.status_code == 200
        file_list = self.get_content(r)
        assert isinstance(file_list, list)
        assert len(file_list) == 1

        # check use case of file not in the folder
        # rename the file in the folder as it will not be found
        temporary_filepath = filepath.with_suffix(".fastq.tmp")
        filepath.rename(temporary_filepath)
        r = client.get(f"{API_URI}/dataset/{dataset_B_uuid}/files",
                       headers=user_B1_headers)
        assert r.status_code == 200
        file_list = self.get_content(r)
        assert isinstance(file_list, list)
        assert file_list[0]["status"] == "unknown"
        # create an empty file with the original name
        # test status from unknown to importing
        filepath.touch()

        r = client.get(f"{API_URI}/dataset/{dataset_B_uuid}/files",
                       headers=user_B1_headers)
        assert r.status_code == 200
        file_list = self.get_content(r)
        assert isinstance(file_list, list)
        assert file_list[0]["status"] == "importing"

        # restore the original file
        filepath.unlink()
        r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_B1_headers)
        assert r.status_code == 200
        file_response = self.get_content(r)
        assert isinstance(file_response, dict)
        assert file_response["status"] == "unknown"
        temporary_filepath.rename(filepath)

        r = client.get(f"{API_URI}/dataset/{dataset_B_uuid}/files",
                       headers=user_B1_headers)
        assert r.status_code == 200
        file_list = self.get_content(r)
        assert isinstance(file_list, list)
        assert file_list[0]["status"] == "uploaded"

        # dataset owner
        r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_B1_headers)
        assert r.status_code == 200
        # same group of the dataset owner
        r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_B2_headers)
        assert r.status_code == 200
        # file owned by an other group
        r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_A1_headers)
        assert r.status_code == 404
        not_authorized_message = self.get_content(r)
        assert isinstance(not_authorized_message, str)

        # admin access
        r = client.get(f"{API_URI}/file/{file_uuid}", headers=admin_headers)
        assert r.status_code == 200

        # check use case of file not in the folder
        # rename the file in the folder as it will not be found
        filepath.rename(temporary_filepath)
        r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_B1_headers)
        assert r.status_code == 200

        # create an empty file with the original name
        # test status from unknown to importing
        filepath.touch()
        r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_B1_headers)
        assert r.status_code == 200
        file_res = self.get_content(r)
        assert isinstance(file_res, dict)
        assert file_res["status"] == "importing"

        # restore the original file
        filepath.unlink()
        r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_B1_headers)
        assert r.status_code == 200

        temporary_filepath.rename(filepath)

        r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_B1_headers)
        assert r.status_code == 200
        file_res = self.get_content(r)
        assert isinstance(file_res, dict)
        assert file_res["status"] == "uploaded"

        # delete a file
        # delete a file that does not exists
        fake_filename = f"{faker.pystr()}_R1"
        r = client.delete(f"{API_URI}/file/{fake_filename}",
                          headers=user_A1_headers)
        assert r.status_code == 404
        # delete a file in a dataset you do not own
        r = client.delete(f"{API_URI}/file/{file_uuid}",
                          headers=user_A1_headers)
        assert r.status_code == 404
        # admin delete a file of a dataset he don't belong
        r = client.delete(f"{API_URI}/file/{file_uuid}", headers=admin_headers)
        assert r.status_code == 404
        # delete a file in a dataset you own
        r = client.delete(f"{API_URI}/file/{file_uuid}",
                          headers=user_B1_headers)
        assert r.status_code == 204
        # delete a file in a dataset own by your group
        r = client.get(f"{API_URI}/dataset/{dataset_B2_uuid}/files",
                       headers=user_B2_headers)
        file_list = self.get_content(r)
        assert isinstance(file_list, list)
        file2_uuid = file_list[0]["uuid"]

        r = client.delete(f"{API_URI}/file/{file2_uuid}",
                          headers=user_B2_headers)
        assert r.status_code == 204
        # check file deletion
        r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_B1_headers)
        assert r.status_code == 404
        not_existent_message = self.get_content(r)
        assert isinstance(not_existent_message, str)
        assert not_existent_message == not_authorized_message
        # check physical deletion from the folder
        assert not filepath.is_file()

        if fastq.exists():
            fastq.unlink()

        if fastq2.exists():
            fastq2.unlink()

        # delete all the elements used by the test
        delete_test_env(
            client,
            user_A1_headers,
            user_B1_headers,
            user_B1_uuid,
            user_B2_uuid,
            user_A1_uuid,
            uuid_group_A,
            uuid_group_B,
            study1_uuid=study1_uuid,
            study2_uuid=study2_uuid,
        )
Ejemplo n.º 14
0
    def test_group_users(self, client: FlaskClient, faker: Faker) -> None:

        if not Env.get_bool("MAIN_LOGIN_ENABLE") or not Env.get_bool(
                "AUTH_ENABLE"):
            log.warning("Skipping group/users tests")
            return

        # Create group 1 with 1 Coordinator and 1 User
        group1_uuid, _ = self.create_group(client)
        _, user1_data = self.create_user(client,
                                         roles=[Role.COORDINATOR],
                                         data={"group": group1_uuid})
        _, user2_data = self.create_user(client,
                                         roles=[Role.USER],
                                         data={"group": group1_uuid})

        # Create group 2 with only 1 Coordinator
        group2_uuid, _ = self.create_group(client)

        _, user3_data = self.create_user(client,
                                         roles=[Role.COORDINATOR],
                                         data={"group": group2_uuid})

        # Verify POST / PUT and DELETE are not enabled
        headers, _ = self.do_login(client, user1_data["email"],
                                   user1_data["password"])

        r = client.post(f"{API_URI}/group/users", headers=headers)
        assert r.status_code == 405

        r = client.put(f"{API_URI}/group/users", headers=headers)
        assert r.status_code == 405

        r = client.delete(f"{API_URI}/group/users", headers=headers)
        assert r.status_code == 405

        r = client.put(f"{API_URI}/group/users/{group1_uuid}", headers=headers)
        assert r.status_code == 404

        r = client.delete(f"{API_URI}/group/users/{group1_uuid}",
                          headers=headers)
        assert r.status_code == 404

        # Verify GET response

        r = client.get(f"{API_URI}/group/users", headers=headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)

        assert response is not None
        assert len(response) == 2
        assert "email" in response[0]
        assert "name" in response[0]
        assert "surname" in response[0]
        assert "roles" in response[0]
        assert "password" not in response[0]
        assert "uuid" not in response[0]
        assert "group" not in response[0]
        assert "belongs_to" not in response[0]
        assert "first_login" not in response[0]
        assert "last_login" not in response[0]
        assert "last_password_change" not in response[0]
        assert "is_active" not in response[0]
        assert "privacy_accepted" not in response[0]
        assert "expiration" not in response[0]

        email1 = response[0]["email"]
        email2 = response[1]["email"]

        assert email1 == user1_data["email"] or email2 == user1_data["email"]
        assert email1 == user2_data["email"] or email2 == user2_data["email"]
        assert email1 != user3_data["email"] and email2 != user3_data["email"]

        # Verify GET response with the other group

        headers, _ = self.do_login(client, user3_data["email"],
                                   user3_data["password"])

        r = client.get(f"{API_URI}/group/users", headers=headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)

        assert response is not None
        assert len(response) == 1
        assert "email" in response[0]
        assert "name" in response[0]
        assert "surname" in response[0]
        assert "roles" in response[0]
        assert "password" not in response[0]
        assert "uuid" not in response[0]
        assert "group" not in response[0]
        assert "belongs_to" not in response[0]
        assert "first_login" not in response[0]
        assert "last_login" not in response[0]
        assert "last_password_change" not in response[0]
        assert "is_active" not in response[0]
        assert "privacy_accepted" not in response[0]
        assert "expiration" not in response[0]

        assert response[0]["email"] == user3_data["email"]
        assert response[0]["email"] != user1_data["email"]
        assert response[0]["email"] != user2_data["email"]

        # Add an admin to group1
        _, user4_data = self.create_user(client,
                                         roles=[Role.ADMIN, Role.COORDINATOR],
                                         data={"group": group1_uuid})

        # Verify as Admin AND Coordinator (Expected: all members, including admins)
        headers, _ = self.do_login(client, user4_data["email"],
                                   user4_data["password"])

        r = client.get(f"{API_URI}/group/users", headers=headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        members = {r["email"] for r in response}
        assert len(members) == 3

        assert user1_data["email"] in members
        assert user2_data["email"] in members
        assert user3_data["email"] not in members
        assert user4_data["email"] in members

        # Verify as Coordinator only (Expected: admins to be filtered out)
        headers, _ = self.do_login(client, user1_data["email"],
                                   user1_data["password"])

        r = client.get(f"{API_URI}/group/users", headers=headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        members = {r["email"] for r in response}

        assert len(members) == 2

        assert user1_data["email"] in members
        assert user2_data["email"] in members
        assert user3_data["email"] not in members
        assert user4_data["email"] not in members
Ejemplo n.º 15
0
    def test_api_dataset(self, client: FlaskClient, faker: Faker) -> None:
        # setup the test env
        (
            admin_headers,
            uuid_group_A,
            user_A1_uuid,
            user_A1_headers,
            uuid_group_B,
            user_B1_uuid,
            user_B1_headers,
            user_B2_uuid,
            user_B2_headers,
            study1_uuid,
            study2_uuid,
        ) = create_test_env(client, faker, study=True)

        # create a new dataset
        dataset1 = {"name": faker.pystr(), "description": faker.pystr()}
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/datasets",
            headers=user_B1_headers,
            data=dataset1,
        )
        assert r.status_code == 200
        dataset1_uuid = self.get_content(r)
        assert isinstance(dataset1_uuid, str)
        # check the directory exists
        dir_path = INPUT_ROOT.joinpath(uuid_group_B, study1_uuid,
                                       dataset1_uuid)
        assert dir_path.is_dir()

        # create a new dataset in a study of an other group
        r = client.post(
            f"{API_URI}/study/{study2_uuid}/datasets",
            headers=user_B1_headers,
            data=dataset1,
        )
        assert r.status_code == 404

        # create a technical
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/technicals",
            headers=user_B1_headers,
            data={"name": faker.pystr()},
        )
        assert r.status_code == 200
        technical_uuid = self.get_content(r)
        assert isinstance(technical_uuid, str)
        # create a phenotype
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/phenotypes",
            headers=user_B1_headers,
            data={
                "name": faker.pystr(),
                "sex": "male"
            },
        )
        assert r.status_code == 200
        phenotype_uuid = self.get_content(r)
        assert isinstance(phenotype_uuid, str)

        # create a new dataset as admin not belonging to study group
        dataset2 = {
            "name": faker.pystr(),
            "description": faker.pystr(),
            "phenotype": phenotype_uuid,
            "technical": technical_uuid,
        }
        r = client.post(
            f"{API_URI}/study/{study1_uuid}/datasets",
            headers=admin_headers,
            data=dataset2,
        )
        assert r.status_code == 404

        r = client.post(
            f"{API_URI}/study/{study1_uuid}/datasets",
            headers=user_B1_headers,
            data=dataset2,
        )
        assert r.status_code == 200
        dataset2_uuid = self.get_content(r)
        assert isinstance(dataset2_uuid, str)

        # test dataset access
        # test dataset list response
        r = client.get(f"{API_URI}/study/{study1_uuid}/datasets",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        assert len(response) == 2

        # test dataset list response for a study you don't have access
        r = client.get(f"{API_URI}/study/{study2_uuid}/datasets",
                       headers=user_B1_headers)
        assert r.status_code == 404

        # test dataset list response for admin
        r = client.get(f"{API_URI}/study/{study1_uuid}/datasets",
                       headers=admin_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        assert len(response) == 2

        # test empty list of datasets in a study
        r = client.get(f"{API_URI}/study/{study2_uuid}/datasets",
                       headers=user_A1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, list)
        assert not response

        # dataset owner
        r = client.get(f"{API_URI}/dataset/{dataset1_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 200
        # same group of the owner
        r = client.get(f"{API_URI}/dataset/{dataset1_uuid}",
                       headers=user_B2_headers)
        assert r.status_code == 200
        # dataset owned by an other group
        r = client.get(f"{API_URI}/dataset/{dataset1_uuid}",
                       headers=user_A1_headers)
        assert r.status_code == 404
        not_authorized_message = self.get_content(r)
        assert isinstance(not_authorized_message, str)

        # admin access
        r = client.get(f"{API_URI}/dataset/{dataset1_uuid}",
                       headers=admin_headers)
        assert r.status_code == 200

        # test technical and phenoype assignation when a new dataset is created
        r = client.get(f"{API_URI}/dataset/{dataset2_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert "technical" in response
        assert "phenotype" in response
        assert response["technical"]["uuid"] == technical_uuid
        # check phenotype was correctly assigned
        assert response["phenotype"]["uuid"] == phenotype_uuid

        # test dataset changing status
        r = client.patch(
            f"{API_URI}/dataset/{dataset1_uuid}",
            headers=user_B1_headers,
            data={"status": "UPLOAD COMPLETED"},
        )
        assert r.status_code == 204
        # check new status in get response
        r = client.get(f"{API_URI}/dataset/{dataset1_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert "status" in response

        # delete status
        r = client.patch(
            f"{API_URI}/dataset/{dataset1_uuid}",
            headers=user_B1_headers,
            data={"status": "-1"},
        )
        assert r.status_code == 204
        # check status has been removed
        r = client.get(f"{API_URI}/dataset/{dataset1_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert not response["status"]

        # try to modify a status when the dataset is running
        graph = neo4j.get_instance()
        dataset = graph.Dataset.nodes.get_or_none(uuid=dataset1_uuid)
        dataset.status = "RUNNING"
        dataset.save()
        r = client.patch(
            f"{API_URI}/dataset/{dataset1_uuid}",
            headers=user_B1_headers,
            data={"status": "UPLOAD COMPLETED"},
        )
        assert r.status_code == 400

        # admin tries to modify a status when the dataset is running
        r = client.patch(
            f"{API_URI}/dataset/{dataset1_uuid}",
            headers=admin_headers,
            data={"status": "UPLOAD COMPLETED"},
        )
        assert r.status_code == 204

        # test dataset modification

        # modify a dataset you do not own
        r = client.put(
            f"{API_URI}/dataset/{dataset1_uuid}",
            headers=user_A1_headers,
            data={"description": faker.pystr()},
        )
        assert r.status_code == 404
        # modify a dataset you own
        r = client.put(
            f"{API_URI}/dataset/{dataset1_uuid}",
            headers=user_B1_headers,
            data={"description": faker.pystr()},
        )
        assert r.status_code == 204
        # modify a dataset of your group assigning a technical and a phenotype
        r = client.put(
            f"{API_URI}/dataset/{dataset1_uuid}",
            headers=user_B2_headers,
            data={
                "name": faker.pystr(),
                "technical": technical_uuid,
                "phenotype": phenotype_uuid,
            },
        )
        assert r.status_code == 204
        # check technical was correctly assigned
        r = client.get(f"{API_URI}/dataset/{dataset1_uuid}",
                       headers=user_B2_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert "technical" in response
        assert "phenotype" in response
        assert response["technical"]["uuid"] == technical_uuid
        # check phenotype was correctly assigned
        assert response["phenotype"]["uuid"] == phenotype_uuid

        # modify a dataset of your group removing a technical and a phenotype
        r = client.put(
            f"{API_URI}/dataset/{dataset1_uuid}",
            headers=user_B2_headers,
            data={
                "technical": "-1",
                "phenotype": "-1"
            },
        )
        assert r.status_code == 204
        # check technical was correctly removed
        r = client.get(f"{API_URI}/dataset/{dataset1_uuid}",
                       headers=user_B2_headers)
        assert r.status_code == 200
        response = self.get_content(r)
        assert isinstance(response, dict)
        assert response["technical"] is None
        # check phenotype was correctly removed
        assert response["phenotype"] is None

        # admin modify a dataset of a group he don't belongs
        r = client.put(
            f"{API_URI}/dataset/{dataset1_uuid}",
            headers=admin_headers,
            data={"description": faker.pystr()},
        )
        assert r.status_code == 404
        # simulate the dataset has an output directory
        # create the output directory in the same way is created in launch pipeline task
        output_path = OUTPUT_ROOT.joinpath(dir_path.relative_to(INPUT_ROOT))
        output_path.mkdir(parents=True)
        assert output_path.is_dir()

        # delete a dataset
        # delete a dataset you do not own
        r = client.delete(f"{API_URI}/dataset/{dataset1_uuid}",
                          headers=user_A1_headers)
        assert r.status_code == 404
        # admin delete a dataset of a group he don't belong
        r = client.delete(f"{API_URI}/dataset/{dataset1_uuid}",
                          headers=admin_headers)
        assert r.status_code == 404
        # delete a dataset you own
        r = client.delete(f"{API_URI}/dataset/{dataset1_uuid}",
                          headers=user_B1_headers)
        assert r.status_code == 204
        assert not dir_path.is_dir()
        # delete a study own by your group
        r = client.delete(f"{API_URI}/dataset/{dataset2_uuid}",
                          headers=user_B2_headers)
        assert r.status_code == 204
        # check dataset deletion
        r = client.get(f"{API_URI}/dataset/{dataset1_uuid}",
                       headers=user_B1_headers)
        assert r.status_code == 404
        not_existent_message = self.get_content(r)
        assert isinstance(not_existent_message, str)
        assert not_existent_message == not_authorized_message
        # check directory deletion
        assert not dir_path.is_dir()
        assert not output_path.is_dir()

        # delete all the elements used by the test
        delete_test_env(
            client,
            user_A1_headers,
            user_B1_headers,
            user_B1_uuid,
            user_B2_uuid,
            user_A1_uuid,
            uuid_group_A,
            uuid_group_B,
            study1_uuid=study1_uuid,
            study2_uuid=study2_uuid,
        )
Ejemplo n.º 16
0
    def test_admin_groups(self, client: FlaskClient, faker: Faker) -> None:

        if not Env.get_bool("MAIN_LOGIN_ENABLE"):  # pragma: no cover
            log.warning("Skipping admin/users tests")
            return

        headers, _ = self.do_login(client, None, None)

        r = client.get(f"{API_URI}/admin/groups", headers=headers)
        assert r.status_code == 200

        schema = self.getDynamicInputSchema(client, "admin/groups", headers)
        data = self.buildData(schema)
        # Event 1: create
        r = client.post(f"{API_URI}/admin/groups", data=data, headers=headers)
        assert r.status_code == 200
        uuid = self.get_content(r)

        r = client.get(f"{API_URI}/admin/groups", headers=headers)
        assert r.status_code == 200
        groups = self.get_content(r)
        assert groups
        assert len(groups) > 0

        fullname = None
        for g in groups:
            if g.get("uuid") == uuid:

                fullname = g.get("fullname")
                break
        else:  # pragma: no cover
            pytest.fail("Group not found")

        assert fullname is not None

        newdata = {
            "shortname": faker.company(),
            "fullname": faker.company(),
        }
        # Event 2: modify
        r = client.put(f"{API_URI}/admin/groups/{uuid}",
                       data=newdata,
                       headers=headers)
        assert r.status_code == 204

        r = client.get(f"{API_URI}/admin/groups", headers=headers)
        assert r.status_code == 200
        groups = self.get_content(r)
        for g in groups:
            if g.get("uuid") == uuid:

                assert g.get("fullname") == newdata.get("fullname")
                assert g.get("fullname") != data.get("fullname")
                assert g.get("fullname") != fullname

        r = client.put(f"{API_URI}/admin/groups/xyz",
                       data=data,
                       headers=headers)
        assert r.status_code == 404

        # Event 3: delete
        r = client.delete(f"{API_URI}/admin/groups/{uuid}", headers=headers)
        assert r.status_code == 204

        r = client.get(f"{API_URI}/admin/groups", headers=headers)
        assert r.status_code == 200
        groups = self.get_content(r)
        for g in groups:
            if g.get("uuid") == uuid:  # pragma: no cover
                pytest.fail("Group not deleted!")

        r = client.delete(f"{API_URI}/admin/groups/xyz", headers=headers)
        assert r.status_code == 404

        # Create a group and assign it to the main user
        # Profile and AdminUsers will react to this change
        # Very important: admin_groups must be tested before admin_users and profile

        r = client.get(f"{AUTH_URI}/profile", headers=headers)
        assert r.status_code == 200
        user_uuid = self.get_content(r).get("uuid")

        data = {
            "fullname": "Default group",
            "shortname": faker.company(),
        }

        # Event 4: create
        uuid, _ = self.create_group(client, data=data)

        data = {
            "group": uuid,
            # very important, otherwise the default user will lose its admin role
            "roles": json.dumps(["admin_root"]),
        }
        headers, _ = self.do_login(client, None, None)
        # Event 5: modify
        r = client.put(f"{API_URI}/admin/users/{user_uuid}",
                       data=data,
                       headers=headers)
        assert r.status_code == 204