From ef80c72194dd8a0b8868ece15589e0a3cf04516f Mon Sep 17 00:00:00 2001 From: Alexander Kabui Date: Sat, 9 Oct 2021 21:55:39 +0300 Subject: unittest for processing wgcna output --- wqflask/tests/unit/wqflask/wgcna/__init__.py | 0 wqflask/tests/unit/wqflask/wgcna/test_wgcna.py | 50 ++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 wqflask/tests/unit/wqflask/wgcna/__init__.py create mode 100644 wqflask/tests/unit/wqflask/wgcna/test_wgcna.py (limited to 'wqflask/tests') diff --git a/wqflask/tests/unit/wqflask/wgcna/__init__.py b/wqflask/tests/unit/wqflask/wgcna/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/wqflask/tests/unit/wqflask/wgcna/test_wgcna.py b/wqflask/tests/unit/wqflask/wgcna/test_wgcna.py new file mode 100644 index 00000000..8e947e2f --- /dev/null +++ b/wqflask/tests/unit/wqflask/wgcna/test_wgcna.py @@ -0,0 +1,50 @@ + +"""module contains for processing gn3 wgcna data""" +from unittest import TestCase + +from wqflask.wgcna.gn3_wgcna import process_wgcna_data + + +class DataProcessingTests(TestCase): + """class contains data processing tests""" + + def test_data_processing(self): + """test for parsing data for datatable""" + output = { + "input": { + "sample_names": ["BXD1", "BXD2", "BXD3", "BXD4", "BXD5", "BXD6"], + + }, + "output": { + "ModEigens": { + "MEturquoise": [ + 0.0646677768085351, + 0.137200224277058, + 0.63451113720732, + -0.544002665501479, + -0.489487590361863, + 0.197111117570427 + ], + "MEgrey": [ + 0.213, + 0.214, + 0.3141, + -0.545, + -0.423, + 0.156, + ] + }}} + + row_data = [['BXD1', 0.065, 0.213], + ['BXD2', 0.137, 0.214], + ['BXD3', 0.635, 0.314], + ['BXD4', -0.544, -0.545], + ['BXD5', -0.489, -0.423], + ['BXD6', 0.197, 0.156]] + + expected_results = { + "col_names": ["sample_names", "MEturquoise", "MEgrey"], + "mod_dataset": row_data + } + + self.assertEqual(process_wgcna_data(output), expected_results) -- cgit v1.2.3 From 0c6ad845e6f461e880b423bd8f79156754658f8a Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Sat, 9 Oct 2021 09:43:40 +0300 Subject: Move markdown_routes to api/markdown All new API definitions should be migrated to "wqflask/ api" --- .../tests/unit/wqflask/api/test_markdown_routes.py | 54 ++++++ wqflask/tests/unit/wqflask/test_markdown_routes.py | 54 ------ wqflask/wqflask/__init__.py | 16 +- wqflask/wqflask/api/correlation.py | 6 - wqflask/wqflask/api/markdown.py | 186 +++++++++++++++++++++ wqflask/wqflask/markdown_routes.py | 186 --------------------- 6 files changed, 248 insertions(+), 254 deletions(-) create mode 100644 wqflask/tests/unit/wqflask/api/test_markdown_routes.py delete mode 100644 wqflask/tests/unit/wqflask/test_markdown_routes.py create mode 100644 wqflask/wqflask/api/markdown.py delete mode 100644 wqflask/wqflask/markdown_routes.py (limited to 'wqflask/tests') diff --git a/wqflask/tests/unit/wqflask/api/test_markdown_routes.py b/wqflask/tests/unit/wqflask/api/test_markdown_routes.py new file mode 100644 index 00000000..1c513ac5 --- /dev/null +++ b/wqflask/tests/unit/wqflask/api/test_markdown_routes.py @@ -0,0 +1,54 @@ +"""Test functions for wqflask/api/markdown.py""" + +import unittest +from unittest import mock + +from dataclasses import dataclass +from wqflask.api.markdown import render_markdown + + +@dataclass +class MockRequests404: + status_code: int = 404 + + +@dataclass +class MockRequests200: + status_code: int = 200 + content: str = b""" +# Glossary +This is some content + +## Sub-heading +This is another sub-heading""" + + +class TestMarkdownRoutesFunctions(unittest.TestCase): + """Test cases for functions in markdown""" + + @mock.patch('wqflask.api.markdown.requests.get') + def test_render_markdown_when_fetching_locally(self, requests_mock): + requests_mock.return_value = MockRequests404() + markdown_content = render_markdown("general/glossary/glossary.md") + requests_mock.assert_called_with( + "https://raw.githubusercontent.com" + "/genenetwork/gn-docs/" + "master/general/" + "glossary/glossary.md") + self.assertRegexpMatches(markdown_content, + "Content for general/glossary/glossary.md not available.") + + @mock.patch('wqflask.api.markdown.requests.get') + def test_render_markdown_when_fetching_remotely(self, requests_mock): + requests_mock.return_value = MockRequests200() + markdown_content = render_markdown("general/glossary/glossary.md") + requests_mock.assert_called_with( + "https://raw.githubusercontent.com" + "/genenetwork/gn-docs/" + "master/general/" + "glossary/glossary.md") + self.assertEqual("""

Glossary

+

This is some content

+

Sub-heading

+

This is another sub-heading

""", + markdown_content) diff --git a/wqflask/tests/unit/wqflask/test_markdown_routes.py b/wqflask/tests/unit/wqflask/test_markdown_routes.py deleted file mode 100644 index 90e0f17c..00000000 --- a/wqflask/tests/unit/wqflask/test_markdown_routes.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Test functions in markdown utils""" - -import unittest -from unittest import mock - -from dataclasses import dataclass -from wqflask.markdown_routes import render_markdown - - -@dataclass -class MockRequests404: - status_code: int = 404 - - -@dataclass -class MockRequests200: - status_code: int = 200 - content: str = b""" -# Glossary -This is some content - -## Sub-heading -This is another sub-heading""" - - -class TestMarkdownRoutesFunctions(unittest.TestCase): - """Test cases for functions in markdown_routes""" - - @mock.patch('wqflask.markdown_routes.requests.get') - def test_render_markdown_when_fetching_locally(self, requests_mock): - requests_mock.return_value = MockRequests404() - markdown_content = render_markdown("general/glossary/glossary.md") - requests_mock.assert_called_with( - "https://raw.githubusercontent.com" - "/genenetwork/gn-docs/" - "master/general/" - "glossary/glossary.md") - self.assertRegexpMatches(markdown_content, - "Content for general/glossary/glossary.md not available.") - - @mock.patch('wqflask.markdown_routes.requests.get') - def test_render_markdown_when_fetching_remotely(self, requests_mock): - requests_mock.return_value = MockRequests200() - markdown_content = render_markdown("general/glossary/glossary.md") - requests_mock.assert_called_with( - "https://raw.githubusercontent.com" - "/genenetwork/gn-docs/" - "master/general/" - "glossary/glossary.md") - self.assertEqual("""

Glossary

-

This is some content

-

Sub-heading

-

This is another sub-heading

""", - markdown_content) diff --git a/wqflask/wqflask/__init__.py b/wqflask/wqflask/__init__.py index 8b2055cd..5758b13e 100644 --- a/wqflask/wqflask/__init__.py +++ b/wqflask/wqflask/__init__.py @@ -8,14 +8,14 @@ from flask import Flask from typing import Tuple from urllib.parse import urlparse from utility import formatting -from wqflask.markdown_routes import glossary_blueprint -from wqflask.markdown_routes import references_blueprint -from wqflask.markdown_routes import links_blueprint -from wqflask.markdown_routes import policies_blueprint -from wqflask.markdown_routes import environments_blueprint -from wqflask.markdown_routes import facilities_blueprint -from wqflask.markdown_routes import blogs_blueprint -from wqflask.markdown_routes import news_blueprint +from wqflask.api.markdown import glossary_blueprint +from wqflask.api.markdown import references_blueprint +from wqflask.api.markdown import links_blueprint +from wqflask.api.markdown import policies_blueprint +from wqflask.api.markdown import environments_blueprint +from wqflask.api.markdown import facilities_blueprint +from wqflask.api.markdown import blogs_blueprint +from wqflask.api.markdown import news_blueprint app = Flask(__name__) diff --git a/wqflask/wqflask/api/correlation.py b/wqflask/wqflask/api/correlation.py index a135ecc8..9b875c99 100644 --- a/wqflask/wqflask/api/correlation.py +++ b/wqflask/wqflask/api/correlation.py @@ -9,7 +9,6 @@ from utility.db_tools import escape from wqflask.correlation import correlation_functions - def do_correlation(start_vars): assert('db' in start_vars) assert('target_db' in start_vars) @@ -26,7 +25,6 @@ def do_correlation(start_vars): corr_results = calculate_results( this_trait, this_dataset, target_dataset, corr_params) - #corr_results = collections.OrderedDict(sorted(corr_results.items(), key=lambda t: -abs(t[1][0]))) final_results = [] for _trait_counter, trait in enumerate(list(corr_results.keys())[:corr_params['return_count']]): @@ -54,11 +52,7 @@ def do_correlation(start_vars): "#_strains": num_overlap, "p_value": sample_p } - final_results.append(result_dict) - - # json_corr_results = generate_corr_json(final_corr_results, this_trait, this_dataset, target_dataset, for_api = True) - return final_results diff --git a/wqflask/wqflask/api/markdown.py b/wqflask/wqflask/api/markdown.py new file mode 100644 index 00000000..580b9ac0 --- /dev/null +++ b/wqflask/wqflask/api/markdown.py @@ -0,0 +1,186 @@ +"""Markdown routes + +Render pages from github, or if they are unavailable, look for it else where +""" + +import requests +import markdown +import os +import sys + +from bs4 import BeautifulSoup # type: ignore + +from flask import send_from_directory +from flask import Blueprint +from flask import render_template + +from typing import Dict +from typing import List + +glossary_blueprint = Blueprint('glossary_blueprint', __name__) +references_blueprint = Blueprint("references_blueprint", __name__) +environments_blueprint = Blueprint("environments_blueprint", __name__) +links_blueprint = Blueprint("links_blueprint", __name__) +policies_blueprint = Blueprint("policies_blueprint", __name__) +facilities_blueprint = Blueprint("facilities_blueprint", __name__) +news_blueprint = Blueprint("news_blueprint", __name__) + +blogs_blueprint = Blueprint("blogs_blueprint", __name__) + + +def render_markdown(file_name, is_remote_file=True): + """Try to fetch the file name from Github and if that fails, try to +look for it inside the file system """ + github_url = ("https://raw.githubusercontent.com/" + "genenetwork/gn-docs/master/") + + if not is_remote_file: + text = "" + with open(file_name, "r", encoding="utf-8") as input_file: + text = input_file.read() + return markdown.markdown(text, + extensions=['tables']) + + md_content = requests.get(f"{github_url}{file_name}") + + if md_content.status_code == 200: + return markdown.markdown(md_content.content.decode("utf-8"), + extensions=['tables']) + + return (f"\nContent for {file_name} not available. " + "Please check " + "(here to see where content exists)" + "[https://github.com/genenetwork/gn-docs]. " + "Please reach out to the gn2 team to have a look at this") + + +def get_file_from_python_search_path(pathname_suffix): + cands = [os.path.join(d, pathname_suffix) for d in sys.path] + try: + return list(filter(os.path.exists, cands))[0] + except IndexError: + return None + + +def get_blogs(user: str = "genenetwork", + repo_name: str = "gn-docs") -> dict: + + blogs: Dict[int, List] = {} + github_url = f"https://api.github.com/repos/{user}/{repo_name}/git/trees/master?recursive=1" + + repo_tree = requests.get(github_url).json()["tree"] + + for data in repo_tree: + path_name = data["path"] + if path_name.startswith("blog") and path_name.endswith(".md"): + split_path = path_name.split("/")[1:] + try: + year, title, file_name = split_path + except Exception as e: + year, file_name = split_path + title = "" + + subtitle = os.path.splitext(file_name)[0] + + blog = { + "title": title, + "subtitle": subtitle, + "full_path": path_name + } + + if year in blogs: + blogs[int(year)].append(blog) + else: + blogs[int(year)] = [blog] + + return dict(sorted(blogs.items(), key=lambda x: x[0], reverse=True)) + + +@glossary_blueprint.route('/') +def glossary(): + return render_template( + "glossary.html", + rendered_markdown=render_markdown("general/glossary/glossary.md")), 200 + + +@references_blueprint.route('/') +def references(): + return render_template( + "references.html", + rendered_markdown=render_markdown("general/references/references.md")), 200 + + +@news_blueprint.route('/') +def news(): + return render_template( + "news.html", + rendered_markdown=render_markdown("general/news/news.md")), 200 + + +@environments_blueprint.route("/") +def environments(): + + md_file = get_file_from_python_search_path("wqflask/DEPENDENCIES.md") + svg_file = get_file_from_python_search_path( + "wqflask/dependency-graph.html") + svg_data = None + if svg_file: + with open(svg_file, 'r') as f: + svg_data = "".join( + BeautifulSoup(f.read(), + 'lxml').body.script.contents) + + if md_file is not None: + return ( + render_template("environment.html", + svg_data=svg_data, + rendered_markdown=render_markdown( + md_file, + is_remote_file=False)), + 200 + ) + # Fallback: Fetch file from server + return (render_template( + "environment.html", + svg_data=None, + rendered_markdown=render_markdown( + "general/environments/environments.md")), + 200) + + +@environments_blueprint.route('/svg-dependency-graph') +def svg_graph(): + directory, file_name, _ = get_file_from_python_search_path( + "wqflask/dependency-graph.svg").partition("dependency-graph.svg") + return send_from_directory(directory, file_name) + + +@links_blueprint.route("/") +def links(): + return render_template( + "links.html", + rendered_markdown=render_markdown("general/links/links.md")), 200 + + +@policies_blueprint.route("/") +def policies(): + return render_template( + "policies.html", + rendered_markdown=render_markdown("general/policies/policies.md")), 200 + + +@facilities_blueprint.route("/") +def facilities(): + return render_template("facilities.html", rendered_markdown=render_markdown("general/help/facilities.md")), 200 + + +@blogs_blueprint.route("/") +def display_blog(blog_path): + return render_template("blogs.html", rendered_markdown=render_markdown(blog_path)) + + +@blogs_blueprint.route("/") +def blogs_list(): + blogs = get_blogs() + + return render_template("blogs_list.html", blogs=blogs) diff --git a/wqflask/wqflask/markdown_routes.py b/wqflask/wqflask/markdown_routes.py deleted file mode 100644 index 580b9ac0..00000000 --- a/wqflask/wqflask/markdown_routes.py +++ /dev/null @@ -1,186 +0,0 @@ -"""Markdown routes - -Render pages from github, or if they are unavailable, look for it else where -""" - -import requests -import markdown -import os -import sys - -from bs4 import BeautifulSoup # type: ignore - -from flask import send_from_directory -from flask import Blueprint -from flask import render_template - -from typing import Dict -from typing import List - -glossary_blueprint = Blueprint('glossary_blueprint', __name__) -references_blueprint = Blueprint("references_blueprint", __name__) -environments_blueprint = Blueprint("environments_blueprint", __name__) -links_blueprint = Blueprint("links_blueprint", __name__) -policies_blueprint = Blueprint("policies_blueprint", __name__) -facilities_blueprint = Blueprint("facilities_blueprint", __name__) -news_blueprint = Blueprint("news_blueprint", __name__) - -blogs_blueprint = Blueprint("blogs_blueprint", __name__) - - -def render_markdown(file_name, is_remote_file=True): - """Try to fetch the file name from Github and if that fails, try to -look for it inside the file system """ - github_url = ("https://raw.githubusercontent.com/" - "genenetwork/gn-docs/master/") - - if not is_remote_file: - text = "" - with open(file_name, "r", encoding="utf-8") as input_file: - text = input_file.read() - return markdown.markdown(text, - extensions=['tables']) - - md_content = requests.get(f"{github_url}{file_name}") - - if md_content.status_code == 200: - return markdown.markdown(md_content.content.decode("utf-8"), - extensions=['tables']) - - return (f"\nContent for {file_name} not available. " - "Please check " - "(here to see where content exists)" - "[https://github.com/genenetwork/gn-docs]. " - "Please reach out to the gn2 team to have a look at this") - - -def get_file_from_python_search_path(pathname_suffix): - cands = [os.path.join(d, pathname_suffix) for d in sys.path] - try: - return list(filter(os.path.exists, cands))[0] - except IndexError: - return None - - -def get_blogs(user: str = "genenetwork", - repo_name: str = "gn-docs") -> dict: - - blogs: Dict[int, List] = {} - github_url = f"https://api.github.com/repos/{user}/{repo_name}/git/trees/master?recursive=1" - - repo_tree = requests.get(github_url).json()["tree"] - - for data in repo_tree: - path_name = data["path"] - if path_name.startswith("blog") and path_name.endswith(".md"): - split_path = path_name.split("/")[1:] - try: - year, title, file_name = split_path - except Exception as e: - year, file_name = split_path - title = "" - - subtitle = os.path.splitext(file_name)[0] - - blog = { - "title": title, - "subtitle": subtitle, - "full_path": path_name - } - - if year in blogs: - blogs[int(year)].append(blog) - else: - blogs[int(year)] = [blog] - - return dict(sorted(blogs.items(), key=lambda x: x[0], reverse=True)) - - -@glossary_blueprint.route('/') -def glossary(): - return render_template( - "glossary.html", - rendered_markdown=render_markdown("general/glossary/glossary.md")), 200 - - -@references_blueprint.route('/') -def references(): - return render_template( - "references.html", - rendered_markdown=render_markdown("general/references/references.md")), 200 - - -@news_blueprint.route('/') -def news(): - return render_template( - "news.html", - rendered_markdown=render_markdown("general/news/news.md")), 200 - - -@environments_blueprint.route("/") -def environments(): - - md_file = get_file_from_python_search_path("wqflask/DEPENDENCIES.md") - svg_file = get_file_from_python_search_path( - "wqflask/dependency-graph.html") - svg_data = None - if svg_file: - with open(svg_file, 'r') as f: - svg_data = "".join( - BeautifulSoup(f.read(), - 'lxml').body.script.contents) - - if md_file is not None: - return ( - render_template("environment.html", - svg_data=svg_data, - rendered_markdown=render_markdown( - md_file, - is_remote_file=False)), - 200 - ) - # Fallback: Fetch file from server - return (render_template( - "environment.html", - svg_data=None, - rendered_markdown=render_markdown( - "general/environments/environments.md")), - 200) - - -@environments_blueprint.route('/svg-dependency-graph') -def svg_graph(): - directory, file_name, _ = get_file_from_python_search_path( - "wqflask/dependency-graph.svg").partition("dependency-graph.svg") - return send_from_directory(directory, file_name) - - -@links_blueprint.route("/") -def links(): - return render_template( - "links.html", - rendered_markdown=render_markdown("general/links/links.md")), 200 - - -@policies_blueprint.route("/") -def policies(): - return render_template( - "policies.html", - rendered_markdown=render_markdown("general/policies/policies.md")), 200 - - -@facilities_blueprint.route("/") -def facilities(): - return render_template("facilities.html", rendered_markdown=render_markdown("general/help/facilities.md")), 200 - - -@blogs_blueprint.route("/") -def display_blog(blog_path): - return render_template("blogs.html", rendered_markdown=render_markdown(blog_path)) - - -@blogs_blueprint.route("/") -def blogs_list(): - blogs = get_blogs() - - return render_template("blogs_list.html", blogs=blogs) -- cgit v1.2.3 From 212d19c29a42bd6966965b166cdbb4dd642e5eb4 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 13 Oct 2021 13:05:26 +0300 Subject: Add test-cases for `get_user_membership` --- .../tests/unit/wqflask/test_resource_manager.py | 51 ++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 wqflask/tests/unit/wqflask/test_resource_manager.py (limited to 'wqflask/tests') diff --git a/wqflask/tests/unit/wqflask/test_resource_manager.py b/wqflask/tests/unit/wqflask/test_resource_manager.py new file mode 100644 index 00000000..a27f40e1 --- /dev/null +++ b/wqflask/tests/unit/wqflask/test_resource_manager.py @@ -0,0 +1,51 @@ +"""Test cases for wqflask/resource_manager.py""" +import unittest + +from unittest import mock +from wqflask.resource_manager import get_user_membership + + +class TestGetUserMembership(unittest.TestCase): + """Test cases for `get_user_membership`""" + + def setUp(self): + conn = mock.MagicMock() + conn.hgetall.return_value = { + '7fa95d07-0e2d-4bc5-b47c-448fdc1260b2': ( + '{"name": "editors", ' + '"admins": ["8ad942fe-490d-453e-bd37-56f252e41604", "rand"], ' + '"members": ["8ad942fe-490d-453e-bd37-56f252e41603", ' + '"rand"], ' + '"changed_timestamp": "Oct 06 2021 06:39PM", ' + '"created_timestamp": "Oct 06 2021 06:39PM"}')} + self.conn = conn + + def test_user_is_group_member_only(self): + """Test that a user is only a group member""" + self.assertEqual( + get_user_membership( + conn=self.conn, + user_id="8ad942fe-490d-453e-bd37-56f252e41603", + group_id="7fa95d07-0e2d-4bc5-b47c-448fdc1260b2"), + {"member": True, + "admin": False}) + + def test_user_is_group_admin_only(self): + """Test that a user is a group admin only""" + self.assertEqual( + get_user_membership( + conn=self.conn, + user_id="8ad942fe-490d-453e-bd37-56f252e41604", + group_id="7fa95d07-0e2d-4bc5-b47c-448fdc1260b2"), + {"member": False, + "admin": True}) + + def test_user_is_both_group_member_and_admin(self): + """Test that a user is both an admin and member of a group""" + self.assertEqual( + get_user_membership( + conn=self.conn, + user_id="rand", + group_id="7fa95d07-0e2d-4bc5-b47c-448fdc1260b2"), + {"member": True, + "admin": True}) -- cgit v1.2.3 From 610335cb3c3030cf39e91ad3232d468b388fc340 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 15 Oct 2021 08:51:46 +0300 Subject: Get a users access mask If a user has several access roles, select the highest role. --- .../tests/unit/wqflask/test_resource_manager.py | 59 ++++++++++++++++++++++ wqflask/wqflask/resource_manager.py | 48 ++++++++++++++++++ 2 files changed, 107 insertions(+) (limited to 'wqflask/tests') diff --git a/wqflask/tests/unit/wqflask/test_resource_manager.py b/wqflask/tests/unit/wqflask/test_resource_manager.py index a27f40e1..b0b7e6a3 100644 --- a/wqflask/tests/unit/wqflask/test_resource_manager.py +++ b/wqflask/tests/unit/wqflask/test_resource_manager.py @@ -3,6 +3,9 @@ import unittest from unittest import mock from wqflask.resource_manager import get_user_membership +from wqflask.resource_manager import get_user_access_roles +from wqflask.resource_manager import DataRole +from wqflask.resource_manager import AdminRole class TestGetUserMembership(unittest.TestCase): @@ -49,3 +52,59 @@ class TestGetUserMembership(unittest.TestCase): group_id="7fa95d07-0e2d-4bc5-b47c-448fdc1260b2"), {"member": True, "admin": True}) + + +class TestCheckUserAccessRole(unittest.TestCase): + """Test cases for `get_user_access_roles`""" + + def setUp(self): + conn = mock.MagicMock() + conn.hget.return_value = ( + '{"owner_id": "8ad942fe-490d-453e-bd37", ' + '"default_mask": {"data": "no-access", ' + '"metadata": "no-access", ' + '"admin": "not-admin"}, ' + '"group_masks": ' + '{"7fa95d07-0e2d-4bc5-b47c-448fdc1260b2": ' + '{"metadata": "edit", "data": "edit"}}, ' + '"name": "_14329", "' + 'data": {"dataset": 1, "trait": 14329}, ' + '"type": "dataset-publish"}') + + conn.hgetall.return_value = { + '7fa95d07-0e2d-4bc5-b47c-448fdc1260b2': ( + '{"name": "editors", ' + '"admins": ["8ad942fe-490d-453e-bd37-56f252e41604", "rand"], ' + '"members": ["8ad942fe-490d-453e-bd37-56f252e41603", ' + '"rand"], ' + '"changed_timestamp": "Oct 06 2021 06:39PM", ' + '"created_timestamp": "Oct 06 2021 06:39PM"}')} + self.conn = conn + + def test_get_user_access_when_owner(self): + """Test that the right access roles are set""" + self.assertEqual(get_user_access_roles( + conn=self.conn, + resource_id="", # Can be anything + user_id="8ad942fe-490d-453e-bd37"), + {"data": DataRole.EDIT, + "metadata": DataRole.EDIT, + "admin": AdminRole.EDIT_ACCESS}) + + def test_get_user_access_default_mask(self): + self.assertEqual(get_user_access_roles( + conn=self.conn, + resource_id="", # Can be anything + user_id=""), + {"data": DataRole.NO_ACCESS, + "metadata": DataRole.NO_ACCESS, + "admin": AdminRole.NOT_ADMIN}) + + def test_get_user_access_group_mask(self): + self.assertEqual(get_user_access_roles( + conn=self.conn, + resource_id="", # Can be anything + user_id="8ad942fe-490d-453e-bd37-56f252e41603"), + {"data": DataRole.EDIT, + "metadata": DataRole.EDIT, + "admin": AdminRole.NOT_ADMIN}) diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index 7f4718f0..a3a94f9e 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -63,4 +63,52 @@ def get_user_membership(conn: redis.Redis, user_id: str, results["member"] = True break return results + + +def get_user_access_roles(conn: redis.Redis, + resource_id: str, + user_id: str) -> Dict: + """Get the highest access roles for a given user + + Args: + - conn: A redis connection with `decoded_responses == True`. + - resource_id: The unique id of a given resource. + + Returns: + A dict indicating the highest access role the user has. + """ + # This is the default access role + access_role = { + "data": [DataRole.NO_ACCESS], + "metadata": [DataRole.NO_ACCESS], + "admin": [AdminRole.NOT_ADMIN], + } + resource_info = json.loads(conn.hget('resources', resource_id)) + + # Check the resource's default mask + if default_mask := resource_info.get("default_mask"): + access_role["data"].append(DataRole(default_mask.get("data"))) + access_role["metadata"].append(DataRole(default_mask.get("metadata"))) + access_role["admin"].append(AdminRole(default_mask.get("admin"))) + + # Then check if the user is the owner Check with Zach and Rob if + # the owner, be default should, as the lowest access_roles, edit + # access + if resource_info.get("owner_id") == user_id: + access_role["data"].append(DataRole.EDIT) + access_role["metadata"].append(DataRole.EDIT) + access_role["admin"].append(AdminRole.EDIT_ACCESS) + + # Check the group mask. If the user is in that group mask, use the + # access roles for that group + if group_masks := resource_info.get("group_masks"): + for group_id, roles in group_masks.items(): + user_membership = get_user_membership(conn=conn, + user_id=user_id, + group_id=group_id) + if any(user_membership.values()): + access_role["data"].append(DataRole(roles.get("data"))) + access_role["metadata"].append( + DataRole(roles.get("metadata"))) + return {k: max(v) for k, v in access_role.items()} admin_status = check_owner_or_admin(resource_id=resource_id) -- cgit v1.2.3 From da86bc79798c05ec469d76f375741f306213e4d0 Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Fri, 15 Oct 2021 13:01:38 +0300 Subject: Replace "resource_id" with "resource_info" dict This avoids calling Redis twice when fetching metadata about the resource. --- .../tests/unit/wqflask/test_resource_manager.py | 36 +++++++++++++--------- wqflask/wqflask/resource_manager.py | 10 +++--- 2 files changed, 28 insertions(+), 18 deletions(-) (limited to 'wqflask/tests') diff --git a/wqflask/tests/unit/wqflask/test_resource_manager.py b/wqflask/tests/unit/wqflask/test_resource_manager.py index b0b7e6a3..9d5aaf0b 100644 --- a/wqflask/tests/unit/wqflask/test_resource_manager.py +++ b/wqflask/tests/unit/wqflask/test_resource_manager.py @@ -58,18 +58,26 @@ class TestCheckUserAccessRole(unittest.TestCase): """Test cases for `get_user_access_roles`""" def setUp(self): + self.resource_info = { + "owner_id": "8ad942fe-490d-453e-bd37", + "default_mask": { + "data": "no-access", + "metadata": "no-access", + "admin": "not-admin", + }, + "group_masks": { + "7fa95d07-0e2d-4bc5-b47c-448fdc1260b2": { + "metadata": "edit", + "data": "edit", + }}, + "name": "_14329", + "data": { + "dataset": 1, + "trait": 14329, + }, + "type": "dataset-publish", + } conn = mock.MagicMock() - conn.hget.return_value = ( - '{"owner_id": "8ad942fe-490d-453e-bd37", ' - '"default_mask": {"data": "no-access", ' - '"metadata": "no-access", ' - '"admin": "not-admin"}, ' - '"group_masks": ' - '{"7fa95d07-0e2d-4bc5-b47c-448fdc1260b2": ' - '{"metadata": "edit", "data": "edit"}}, ' - '"name": "_14329", "' - 'data": {"dataset": 1, "trait": 14329}, ' - '"type": "dataset-publish"}') conn.hgetall.return_value = { '7fa95d07-0e2d-4bc5-b47c-448fdc1260b2': ( @@ -85,7 +93,7 @@ class TestCheckUserAccessRole(unittest.TestCase): """Test that the right access roles are set""" self.assertEqual(get_user_access_roles( conn=self.conn, - resource_id="", # Can be anything + resource_info=self.resource_info, user_id="8ad942fe-490d-453e-bd37"), {"data": DataRole.EDIT, "metadata": DataRole.EDIT, @@ -94,7 +102,7 @@ class TestCheckUserAccessRole(unittest.TestCase): def test_get_user_access_default_mask(self): self.assertEqual(get_user_access_roles( conn=self.conn, - resource_id="", # Can be anything + resource_info=self.resource_info, user_id=""), {"data": DataRole.NO_ACCESS, "metadata": DataRole.NO_ACCESS, @@ -103,7 +111,7 @@ class TestCheckUserAccessRole(unittest.TestCase): def test_get_user_access_group_mask(self): self.assertEqual(get_user_access_roles( conn=self.conn, - resource_id="", # Can be anything + resource_info=self.resource_info, user_id="8ad942fe-490d-453e-bd37-56f252e41603"), {"data": DataRole.EDIT, "metadata": DataRole.EDIT, diff --git a/wqflask/wqflask/resource_manager.py b/wqflask/wqflask/resource_manager.py index a3a94f9e..57b99296 100644 --- a/wqflask/wqflask/resource_manager.py +++ b/wqflask/wqflask/resource_manager.py @@ -66,16 +66,19 @@ def get_user_membership(conn: redis.Redis, user_id: str, def get_user_access_roles(conn: redis.Redis, - resource_id: str, + resource_info: Dict, user_id: str) -> Dict: """Get the highest access roles for a given user Args: - - conn: A redis connection with `decoded_responses == True`. - - resource_id: The unique id of a given resource. + - conn: A redis connection with the responses decoded. + - resource_info: A dict containing details(metadata) about a + given resource. + - user_id: The unique id of a given user. Returns: A dict indicating the highest access role the user has. + """ # This is the default access role access_role = { @@ -83,7 +86,6 @@ def get_user_access_roles(conn: redis.Redis, "metadata": [DataRole.NO_ACCESS], "admin": [AdminRole.NOT_ADMIN], } - resource_info = json.loads(conn.hget('resources', resource_id)) # Check the resource's default mask if default_mask := resource_info.get("default_mask"): -- cgit v1.2.3 From fbffb2e4f7538cc34332f37064d4b67cd52588fb Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Wed, 20 Oct 2021 13:43:57 +0300 Subject: Update failing tests when testing `get_user_access_roles` --- .../tests/unit/wqflask/test_resource_manager.py | 82 ++++++++-------------- 1 file changed, 29 insertions(+), 53 deletions(-) (limited to 'wqflask/tests') diff --git a/wqflask/tests/unit/wqflask/test_resource_manager.py b/wqflask/tests/unit/wqflask/test_resource_manager.py index 9d5aaf0b..01fb2021 100644 --- a/wqflask/tests/unit/wqflask/test_resource_manager.py +++ b/wqflask/tests/unit/wqflask/test_resource_manager.py @@ -1,4 +1,5 @@ """Test cases for wqflask/resource_manager.py""" +import json import unittest from unittest import mock @@ -57,62 +58,37 @@ class TestGetUserMembership(unittest.TestCase): class TestCheckUserAccessRole(unittest.TestCase): """Test cases for `get_user_access_roles`""" - def setUp(self): - self.resource_info = { - "owner_id": "8ad942fe-490d-453e-bd37", - "default_mask": { - "data": "no-access", - "metadata": "no-access", - "admin": "not-admin", - }, - "group_masks": { - "7fa95d07-0e2d-4bc5-b47c-448fdc1260b2": { - "metadata": "edit", - "data": "edit", - }}, - "name": "_14329", - "data": { - "dataset": 1, - "trait": 14329, - }, - "type": "dataset-publish", - } - conn = mock.MagicMock() - - conn.hgetall.return_value = { - '7fa95d07-0e2d-4bc5-b47c-448fdc1260b2': ( - '{"name": "editors", ' - '"admins": ["8ad942fe-490d-453e-bd37-56f252e41604", "rand"], ' - '"members": ["8ad942fe-490d-453e-bd37-56f252e41603", ' - '"rand"], ' - '"changed_timestamp": "Oct 06 2021 06:39PM", ' - '"created_timestamp": "Oct 06 2021 06:39PM"}')} - self.conn = conn - - def test_get_user_access_when_owner(self): + @mock.patch("wqflask.resource_manager.requests.get") + def test_edit_access(self, requests_mock): """Test that the right access roles are set""" + response = mock.PropertyMock(return_value=json.dumps( + { + 'data': ['no-access', 'view', 'edit', ], + 'metadata': ['no-access', 'view', 'edit', ], + 'admin': ['not-admin', 'edit-access', ], + } + )) + type(requests_mock.return_value).content = response self.assertEqual(get_user_access_roles( - conn=self.conn, - resource_info=self.resource_info, + resource_id="0196d92e1665091f202f", user_id="8ad942fe-490d-453e-bd37"), - {"data": DataRole.EDIT, - "metadata": DataRole.EDIT, - "admin": AdminRole.EDIT_ACCESS}) + {"data": DataRole.EDIT, + "metadata": DataRole.EDIT, + "admin": AdminRole.EDIT_ACCESS}) - def test_get_user_access_default_mask(self): + @mock.patch("wqflask.resource_manager.requests.get") + def test_no_access(self, requests_mock): + response = mock.PropertyMock(return_value=json.dumps( + { + 'data': ['no-access', ], + 'metadata': ['no-access', ], + 'admin': ['not-admin', ], + } + )) + type(requests_mock.return_value).content = response self.assertEqual(get_user_access_roles( - conn=self.conn, - resource_info=self.resource_info, + resource_id="0196d92e1665091f202f", user_id=""), - {"data": DataRole.NO_ACCESS, - "metadata": DataRole.NO_ACCESS, - "admin": AdminRole.NOT_ADMIN}) - - def test_get_user_access_group_mask(self): - self.assertEqual(get_user_access_roles( - conn=self.conn, - resource_info=self.resource_info, - user_id="8ad942fe-490d-453e-bd37-56f252e41603"), - {"data": DataRole.EDIT, - "metadata": DataRole.EDIT, - "admin": AdminRole.NOT_ADMIN}) + {"data": DataRole.NO_ACCESS, + "metadata": DataRole.NO_ACCESS, + "admin": AdminRole.NOT_ADMIN}) -- cgit v1.2.3 From b3a9b90343f70584b82df00a42cbcc5b97dbfc7f Mon Sep 17 00:00:00 2001 From: BonfaceKilz Date: Mon, 1 Nov 2021 09:22:23 +0300 Subject: use gn3 imports --- wqflask/tests/unit/wqflask/test_resource_manager.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'wqflask/tests') diff --git a/wqflask/tests/unit/wqflask/test_resource_manager.py b/wqflask/tests/unit/wqflask/test_resource_manager.py index 01fb2021..f4335425 100644 --- a/wqflask/tests/unit/wqflask/test_resource_manager.py +++ b/wqflask/tests/unit/wqflask/test_resource_manager.py @@ -3,10 +3,10 @@ import json import unittest from unittest import mock -from wqflask.resource_manager import get_user_membership -from wqflask.resource_manager import get_user_access_roles -from wqflask.resource_manager import DataRole -from wqflask.resource_manager import AdminRole +from gn3.authentication import get_user_membership +from gn3.authentication import get_highest_user_access_role +from gn3.authentication import DataRole +from gn3.authentication import AdminRole class TestGetUserMembership(unittest.TestCase): @@ -56,7 +56,7 @@ class TestGetUserMembership(unittest.TestCase): class TestCheckUserAccessRole(unittest.TestCase): - """Test cases for `get_user_access_roles`""" + """Test cases for `get_highest_user_access_role`""" @mock.patch("wqflask.resource_manager.requests.get") def test_edit_access(self, requests_mock): @@ -69,7 +69,7 @@ class TestCheckUserAccessRole(unittest.TestCase): } )) type(requests_mock.return_value).content = response - self.assertEqual(get_user_access_roles( + self.assertEqual(get_highest_user_access_role( resource_id="0196d92e1665091f202f", user_id="8ad942fe-490d-453e-bd37"), {"data": DataRole.EDIT, @@ -86,7 +86,7 @@ class TestCheckUserAccessRole(unittest.TestCase): } )) type(requests_mock.return_value).content = response - self.assertEqual(get_user_access_roles( + self.assertEqual(get_highest_user_access_role( resource_id="0196d92e1665091f202f", user_id=""), {"data": DataRole.NO_ACCESS, -- cgit v1.2.3