From d39cded0d0fb4eab9e0774d8ff7a943ba010bb12 Mon Sep 17 00:00:00 2001 From: Guillaume Charest <1690085+gcharest@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:45:35 -0400 Subject: [PATCH] Feat/migrate role google integration (#637) * fix: revert pythonPath setting * fix: filename typo * feat: improve type hints * fix: unit test to match updated execute api call method * fix: add scopes and delegated user to hoisted methods * feat: refactor using Google workspace integration * feat: delete unused module --- .devcontainer/devcontainer.json | 2 +- app/integrations/google_drive.py | 268 ------------------ .../google_workspace/google_drive.py | 61 +++- .../google_workspace/google_service.py | 47 +-- .../incident/{__init.py => __init__.py} | 0 app/modules/role/role.py | 35 ++- .../google_workspace/test_google_drive.py | 17 +- .../google_workspace/test_google_service.py | 2 +- .../intergrations/test_google_drive_module.py | 178 ------------ app/tests/modules/role/test_role.py | 36 ++- 10 files changed, 153 insertions(+), 493 deletions(-) delete mode 100644 app/integrations/google_drive.py rename app/modules/incident/{__init.py => __init__.py} (100%) delete mode 100644 app/tests/intergrations/test_google_drive_module.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fb7dc612..c92bdf44 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -21,7 +21,7 @@ "ms-python.black-formatter" ], "settings": { - // "python.pythonPath": "/usr/local/bin/python", + "python.pythonPath": "/usr/local/bin/python", "python.languageServer": "Pylance", "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", "python.formatting.blackPath": "/usr/local/py-utils/bin/black", diff --git a/app/integrations/google_drive.py b/app/integrations/google_drive.py deleted file mode 100644 index a1e34780..00000000 --- a/app/integrations/google_drive.py +++ /dev/null @@ -1,268 +0,0 @@ -import os -import pickle -import base64 -import logging -import datetime - -from dotenv import load_dotenv -from googleapiclient.discovery import build - -load_dotenv() - -SRE_DRIVE_ID = os.environ.get("SRE_DRIVE_ID") -SRE_INCIDENT_FOLDER = os.environ.get("SRE_INCIDENT_FOLDER") -INCIDENT_TEMPLATE = os.environ.get("INCIDENT_TEMPLATE") -INCIDENT_LIST = os.environ.get("INCIDENT_LIST") -START_HEADING = "DO NOT REMOVE this line as the SRE bot needs it as a placeholder." -END_HEADING = "Trigger" - -PICKLE_STRING = os.environ.get("PICKLE_STRING", False) - -SCOPES = [ - "https://www.googleapis.com/auth/drive.file", - "https://www.googleapis.com/auth/docs", - "https://www.googleapis.com/auth/spreadsheets", - "https://www.googleapis.com/auth/calendar", -] - - -def get_google_service(service, version): - creds = None - - if PICKLE_STRING is False: - raise Exception("Pickle string not set") - - try: - pickle_string = base64.b64decode(PICKLE_STRING) - # ignore Bandit complaint about insecure pickle - creds = pickle.loads(pickle_string) # nosec - except Exception as pickle_read_exception: - logging.error( - "Error while loading pickle string: {}".format(pickle_read_exception) - ) - raise Exception("Invalid pickle string") - - return build(service, version, credentials=creds) - - -def create_new_folder(name, parent_folder): - # Creates a new folder in the parent_folder directory - service = get_google_service("drive", "v3") - results = ( - service.files() - .create( - body={ - "name": name, - "mimeType": "application/vnd.google-apps.folder", - "parents": [parent_folder], - }, - supportsAllDrives=True, - fields="id", - ) - .execute() - ) - return results["id"] - - -def create_new_incident(name, folder): - service = get_google_service("drive", "v3") - result = ( - service.files() - .copy( - fileId=INCIDENT_TEMPLATE, - body={"name": name, "parents": [folder]}, - supportsAllDrives=True, - ) - .execute() - ) - return result["id"] - - -def copy_file_to_folder(file_id, name, parent_folder_id, destination_folder_id): - # Copies a file from the parent_folder to the destination_folder - # create the copy - service = get_google_service("drive", "v3") - copied_file = ( - service.files() - .copy( - fileId=file_id, - body={"name": name, "parents": [parent_folder_id]}, - supportsAllDrives=True, - fields="id", - ) - .execute() - ) - # move the copy to the new folder - updated_file = ( - service.files() - .update( - fileId=copied_file["id"], - addParents=destination_folder_id, - removeParents=parent_folder_id, - supportsAllDrives=True, - fields="id", - ) - .execute() - ) - return updated_file["id"] - - -def create_new_docs_file(name, parent_folder_id): - # Creates a new google docs file in the parent_folder directory - service = get_google_service("drive", "v3") - results = ( - service.files() - .create( - body={ - "name": name, - "mimeType": "application/vnd.google-apps.document", - "parents": [parent_folder_id], - }, - supportsAllDrives=True, - fields="id", - ) - .execute() - ) - return results["id"] - - -def create_new_sheets_file(name, parent_folder_id): - # Creates a new google sheets file in the parent_folder directory - service = get_google_service("drive", "v3") - results = ( - service.files() - .create( - body={ - "name": name, - "mimeType": "application/vnd.google-apps.spreadsheet", - "parents": [parent_folder_id], - }, - supportsAllDrives=True, - fields="id", - ) - .execute() - ) - return results["id"] - - -def list_folders(): - service = get_google_service("drive", "v3") - results = ( - service.files() - .list( - pageSize=25, - supportsAllDrives=True, - includeItemsFromAllDrives=True, - corpora="drive", - q="parents in '{}' and mimeType = 'application/vnd.google-apps.folder' and trashed=false and not name contains '{}'".format( - SRE_INCIDENT_FOLDER, "Templates" - ), - driveId=SRE_DRIVE_ID, - fields="nextPageToken, files(id, name)", - ) - .execute() - ) - return results.get("files", []) - - -def list_metadata(file_id): - service = get_google_service("drive", "v3") - result = ( - service.files() - .get(supportsAllDrives=True, fileId=file_id, fields="id, name, appProperties") - .execute() - ) - return result - - -def merge_data(file_id, name, product, slack_channel, on_call_names): - changes = { - "requests": [ - { - "replaceAllText": { - "containsText": {"text": "{{date}}", "matchCase": "true"}, - "replaceText": datetime.datetime.now().strftime("%Y-%m-%d"), - } - }, - { - "replaceAllText": { - "containsText": {"text": "{{name}}", "matchCase": "true"}, - "replaceText": str(name), - } - }, - { - "replaceAllText": { - "containsText": {"text": "{{on-call-names}}", "matchCase": "true"}, - "replaceText": str(on_call_names), - } - }, - { - "replaceAllText": { - "containsText": {"text": "{{team}}", "matchCase": "true"}, - "replaceText": str(product), - } - }, - { - "replaceAllText": { - "containsText": {"text": "{{slack-channel}}", "matchCase": "true"}, - "replaceText": str(slack_channel), - } - }, - { - "replaceAllText": { - "containsText": {"text": "{{status}}", "matchCase": "true"}, - "replaceText": "In Progress", - } - }, - ] - } - service = get_google_service("docs", "v1") - result = ( - service.documents() - .batchUpdate( - documentId=file_id, - body=changes, - ) - .execute() - ) - return result - - -def update_incident_list(document_link, name, slug, product, channel_url): - service = get_google_service("sheets", "v4") - list = [ - [ - datetime.datetime.now().strftime("%Y-%m-%d"), - f'=HYPERLINK("{document_link}", "{name}")', - product, - "In Progress", - f'=HYPERLINK("{channel_url}", "#{slug}")', - ] - ] - resource = {"majorDimension": "ROWS", "values": list} - range = "Sheet1!A:A" - result = ( - service.spreadsheets() - .values() - .append( - spreadsheetId=INCIDENT_LIST, - range=range, - body=resource, - valueInputOption="USER_ENTERED", - ) - .execute() - ) - - return result - - -def healthcheck(): - """Check if the bot can interact with Google Drive.""" - healthy = False - try: - metadata = list_metadata(INCIDENT_TEMPLATE) - healthy = "id" in metadata - logging.info(f"Google Drive healthcheck result: {metadata}") - except Exception as error: - logging.error(f"Google Drive healthcheck failed: {error}") - return healthy diff --git a/app/integrations/google_workspace/google_drive.py b/app/integrations/google_workspace/google_drive.py index 3822dbc8..cf40e982 100644 --- a/app/integrations/google_workspace/google_drive.py +++ b/app/integrations/google_workspace/google_drive.py @@ -14,7 +14,9 @@ @handle_google_api_errors -def add_metadata(file_id, key, value, delegated_user_email=None): +def add_metadata( + file_id: str, key: str, value: str, delegated_user_email: str | None = None +): """Add metadata to a file in Google Drive. Args: @@ -31,6 +33,7 @@ def add_metadata(file_id, key, value, delegated_user_email=None): "v3", "files", "update", + scopes=["https://www.googleapis.com/auth/drive"], delegated_user_email=delegated_user_email, fileId=file_id, body={"appProperties": {key: value}}, @@ -65,7 +68,7 @@ def delete_metadata(file_id, key, delegated_user_email=None): @handle_google_api_errors -def list_metadata(file_id): +def list_metadata(file_id: str): """List metadata of a file in Google Drive. Args: @@ -86,7 +89,13 @@ def list_metadata(file_id): @handle_google_api_errors -def create_folder(name, parent_folder, fields=None): +def create_folder( + name: str, + parent_folder: str, + fields: str | None = None, + scopes: list[str] | None = None, + delegated_user_email: str | None = None, +): """Create a new folder in Google Drive. Args: @@ -103,10 +112,12 @@ def create_folder(name, parent_folder, fields=None): "v3", "files", "create", + scopes=scopes, + delegated_user_email=delegated_user_email, body={ "name": name, - "mimeType": "application/vnd.google-apps.folder", "parents": [parent_folder], + "mimeType": "application/vnd.google-apps.folder", }, supportsAllDrives=True, fields=fields, @@ -114,7 +125,14 @@ def create_folder(name, parent_folder, fields=None): @handle_google_api_errors -def create_file_from_template(name, folder, template, fields=None): +def create_file_from_template( + name: str, + folder: str, + template: str, + fields: str | None = None, + scopes: list[str] | None = None, + delegated_user_email: str | None = None, +): """Create a new file in Google Drive from a template (Docs, Sheets, Slides, Forms, or Sites.) @@ -131,6 +149,8 @@ def create_file_from_template(name, folder, template, fields=None): "v3", "files", "copy", + scopes=scopes, + delegated_user_email=delegated_user_email, fileId=template, body={"name": name, "parents": [folder]}, supportsAllDrives=True, @@ -247,7 +267,11 @@ def list_folders_in_folder(folder, query=None): @handle_google_api_errors -def list_files_in_folder(folder): +def list_files_in_folder( + folder: str, + scopes: list[str] | None = None, + delegated_user_email: str | None = None, +): """List all files in a folder in Google Drive. Args: @@ -261,6 +285,8 @@ def list_files_in_folder(folder): "v3", "files", "list", + scopes=scopes, + delegated_user_email=delegated_user_email, paginate=True, pageSize=25, supportsAllDrives=True, @@ -272,7 +298,14 @@ def list_files_in_folder(folder): @handle_google_api_errors -def copy_file_to_folder(file_id, name, parent_folder_id, destination_folder_id): +def copy_file_to_folder( + file_id: str, + name: str, + parent_folder_id: str, + destination_folder_id: str, + scopes: list[str] | None = None, + delegated_user_email: str | None = None, +) -> str: """Copy a file to a new folder in Google Drive. Args: @@ -289,11 +322,14 @@ def copy_file_to_folder(file_id, name, parent_folder_id, destination_folder_id): "v3", "files", "copy", + scopes=scopes, + delegated_user_email=delegated_user_email, fileId=file_id, body={"name": name, "parents": [parent_folder_id]}, supportsAllDrives=True, fields="id", - ) + )[0]["id"] + print(f"Copied file: {copied_file}") # move the copy to the new folder updated_file = execute_google_api_call( @@ -301,14 +337,17 @@ def copy_file_to_folder(file_id, name, parent_folder_id, destination_folder_id): "v3", "files", "update", - fileId=copied_file["id"], + scopes=scopes, + delegated_user_email=delegated_user_email, + fileId=copied_file, addParents=destination_folder_id, removeParents=parent_folder_id, supportsAllDrives=True, fields="id", - ) + )[0]["id"] + print(f"Updated file: {updated_file}") - return updated_file["id"] + return updated_file @handle_google_api_errors diff --git a/app/integrations/google_workspace/google_service.py b/app/integrations/google_workspace/google_service.py index e4c0d8bb..18e9f5b1 100644 --- a/app/integrations/google_workspace/google_service.py +++ b/app/integrations/google_workspace/google_service.py @@ -19,8 +19,9 @@ from json import JSONDecodeError from dotenv import load_dotenv from functools import wraps +from typing import Any, Callable from google.oauth2 import service_account # type: ignore -from googleapiclient.discovery import build # type: ignore +from googleapiclient.discovery import build, Resource # type: ignore from googleapiclient.errors import HttpError, Error # type: ignore from google.auth.exceptions import RefreshError # type: ignore from integrations.utils.api import convert_kwargs_to_camel_case @@ -32,7 +33,12 @@ load_dotenv() -def get_google_service(service, version, delegated_user_email=None, scopes=None): +def get_google_service( + service: str, + version: str, + scopes: list[str] | None = None, + delegated_user_email: str | None = None, +) -> Resource: """ Get an authenticated Google service. @@ -43,12 +49,12 @@ def get_google_service(service, version, delegated_user_email=None, scopes=None) scopes (list): The list of scopes to request. Returns: - The authenticated Google service resource. + Resource: The authenticated Google service resource. """ - creds_json = os.environ.get("GCP_SRE_SERVICE_ACCOUNT_KEY_FILE", False) + creds_json = os.environ.get("GCP_SRE_SERVICE_ACCOUNT_KEY_FILE", "") - if creds_json is False: + if not creds_json: raise ValueError("Credentials JSON not set") try: @@ -66,7 +72,7 @@ def get_google_service(service, version, delegated_user_email=None, scopes=None) return build(service, version, credentials=creds, cache_discovery=False) -def handle_google_api_errors(func): +def handle_google_api_errors(func: Callable[..., Any]) -> Callable[..., Any]: """Decorator to handle Google API errors. Args: @@ -77,7 +83,7 @@ def handle_google_api_errors(func): """ @wraps(func) - def wrapper(*args, **kwargs): + def wrapper(*args: Any, **kwargs: Any) -> Any: try: result = func(*args, **kwargs) # Check if the result is a tuple and has two elements (for backward compatibility) @@ -106,15 +112,15 @@ def wrapper(*args, **kwargs): def execute_google_api_call( - service_name, - version, - resource_path, - method, - scopes=None, - delegated_user_email=None, - paginate=False, - **kwargs, -): + service_name: str, + version: str, + resource_path: str, + method: str, + scopes: list[str] | None = None, + delegated_user_email: str | None = None, + paginate: bool = False, + **kwargs: Any, +) -> Any: """Execute a Google API call on a resource. Args: @@ -128,9 +134,14 @@ def execute_google_api_call( **kwargs: Additional keyword arguments for the API call. Returns: - dict or list: The result of the API call. If paginate is True, returns a list of all results. + Any: The result of the API call. If paginate is True, returns a list of all results. """ - service = get_google_service(service_name, version, delegated_user_email, scopes) + service = get_google_service( + service_name, + version, + scopes, + delegated_user_email, + ) resource_obj = service for resource in resource_path.split("."): diff --git a/app/modules/incident/__init.py b/app/modules/incident/__init__.py similarity index 100% rename from app/modules/incident/__init.py rename to app/modules/incident/__init__.py diff --git a/app/modules/role/role.py b/app/modules/role/role.py index 97aab9a4..575d9f47 100644 --- a/app/modules/role/role.py +++ b/app/modules/role/role.py @@ -1,6 +1,9 @@ import os import i18n # type: ignore -from integrations import google_drive + +from slack_bolt import Ack, App, Respond +from slack_sdk import WebClient +from integrations.google_workspace import google_drive from integrations.slack import users as slack_users, commands as slack_commands from dotenv import load_dotenv @@ -14,9 +17,11 @@ i18n.set("fallback", "en-CA") PREFIX = os.environ.get("PREFIX", "") +BOT_EMAIL = os.environ.get("SRE_BOT_EMAIL", "") +ROLE_SCOPES = ["https://www.googleapis.com/auth/drive"] -def register(bot): +def register(bot: App): bot.command(f"/{PREFIX}talent-role")(role_command) bot.view("role_view")(role_view_handler) bot.action("role_change_locale")(update_modal_locale) @@ -28,7 +33,9 @@ def update_locale(locale): i18n.set("locale", locale) -def role_command(ack, command, logger, respond, client, body): +def role_command( + ack: Ack, command: dict, logger, respond: Respond, client: WebClient, body: dict +): # Function to execute the role command based on the arguments provided # acknowledge to slack that the command was received @@ -172,9 +179,13 @@ def role_view_handler(ack, body, say, logger, client): # 3. Create a new channel and invite users # Step 1: Create a new folder in the Google Drive - folder_id = google_drive.create_new_folder( - role_name, os.getenv("INTERNAL_TALENT_FOLDER") - ) + folder_id = google_drive.create_folder( + role_name, + os.getenv("INTERNAL_TALENT_FOLDER"), + "id", + scopes=ROLE_SCOPES, + delegated_user_email=BOT_EMAIL, + )["id"] logger.info(f"Created folder: {role_name} / {folder_id}") # Step 2: Copy the template files into the new folder (Scoring Guilde, Template for Core Values interview notes, Template for Technical interview notes @@ -184,6 +195,8 @@ def role_view_handler(ack, body, say, logger, client): f"Template 2022/06 - {role_name} Interview Panel Scoring Document - ", os.getenv("TEMPLATES_FOLDER"), folder_id, + scopes=ROLE_SCOPES, + delegated_user_email=BOT_EMAIL, ) logger.info( f"Created document: Scoring Guide in folder: Scoring Guide / {scoring_guide_id}" @@ -194,6 +207,8 @@ def role_view_handler(ack, body, say, logger, client): f"Template EN+FR 2022/09- {role_name} - Core Values Panel - Interview Guide - - ", os.getenv("TEMPLATES_FOLDER"), folder_id, + scopes=ROLE_SCOPES, + delegated_user_email=BOT_EMAIL, ) logger.info( f"Created document: Core Values Interview Notes in folder: Core Values Interview Notes / {core_values_interview_notes_id}" @@ -204,6 +219,8 @@ def role_view_handler(ack, body, say, logger, client): f"Template EN+FR 2022/09 - {role_name} - Technical Panel - Interview Guide - - ", os.getenv("TEMPLATES_FOLDER"), folder_id, + scopes=ROLE_SCOPES, + delegated_user_email=BOT_EMAIL, ) logger.info( f"Created document: Technical Interview Notes in folder: Technical Interview Notes / {technical_interview_notes_id}" @@ -214,6 +231,8 @@ def role_view_handler(ack, body, say, logger, client): f"TEMPLATE Month YYYY - {role_name} - Kick-off form", os.getenv("TEMPLATES_FOLDER"), folder_id, + scopes=ROLE_SCOPES, + delegated_user_email=BOT_EMAIL, ) logger.info( f"Created document: Intake Form in folder: Intake Form / {intake_form_id}" @@ -224,6 +243,8 @@ def role_view_handler(ack, body, say, logger, client): "SoMC Template", os.getenv("TEMPLATES_FOLDER"), folder_id, + scopes=ROLE_SCOPES, + delegated_user_email=BOT_EMAIL, ) logger.info( f"Created document: SoMC Template in folder: SoMC Template / {somc_template_id}" @@ -234,6 +255,8 @@ def role_view_handler(ack, body, say, logger, client): f"Recruitment Feedback - {role_name}", os.getenv("TEMPLATES_FOLDER"), folder_id, + scopes=ROLE_SCOPES, + delegated_user_email=BOT_EMAIL, ) logger.info( f"Created document: Recruitment Feedback Template in folder: Recruitment Feedback/ {recruitment_feedback_template_id}" diff --git a/app/tests/integrations/google_workspace/test_google_drive.py b/app/tests/integrations/google_workspace/test_google_drive.py index 155689ae..283ae422 100644 --- a/app/tests/integrations/google_workspace/test_google_drive.py +++ b/app/tests/integrations/google_workspace/test_google_drive.py @@ -17,6 +17,7 @@ def test_add_metadata_returns_result(execute_google_api_call_mock): "v3", "files", "update", + scopes=["https://www.googleapis.com/auth/drive"], delegated_user_email=None, fileId="file_id", body={"appProperties": {"key": "value"}}, @@ -86,6 +87,8 @@ def test_create_folder_returns_folder(execute_google_api_call_mock): "v3", "files", "create", + scopes=None, + delegated_user_email=None, body={ "name": "test_folder", "mimeType": "application/vnd.google-apps.folder", @@ -109,6 +112,8 @@ def test_create_folder_calls_api_with_fields(execute_google_api_call_mock): "v3", "files", "create", + scopes=None, + delegated_user_email=None, body={ "name": "test_folder", "mimeType": "application/vnd.google-apps.folder", @@ -167,6 +172,8 @@ def test_create_file_from_template_returns_file(execute_google_api_call_mock): "v3", "files", "copy", + scopes=None, + delegated_user_email=None, fileId="template_id", body={"name": "test_document", "parents": ["folder_id"]}, supportsAllDrives=True, @@ -303,8 +310,8 @@ def test_get_file_by_name_no_file_found_returns_empty_list( @patch("integrations.google_workspace.google_drive.execute_google_api_call") def test_copy_file_to_folder_returns_file_id(execute_google_api_call_mock): execute_google_api_call_mock.side_effect = [ - {"id": "file_id"}, # Response from the "copy" method - {"id": "updated_file_id"}, # Response from the "update" method + [{"id": "file_id"}], # Response from the "copy" method + [{"id": "updated_file_id"}], # Response from the "update" method ] assert ( google_drive.copy_file_to_folder( @@ -319,6 +326,8 @@ def test_copy_file_to_folder_returns_file_id(execute_google_api_call_mock): "v3", "files", "copy", + scopes=None, + delegated_user_email=None, fileId="file_id", body={"name": "name", "parents": ["parent_folder"]}, supportsAllDrives=True, @@ -329,6 +338,8 @@ def test_copy_file_to_folder_returns_file_id(execute_google_api_call_mock): "v3", "files", "update", + scopes=None, + delegated_user_email=None, fileId="file_id", addParents="destination_folder", removeParents="parent_folder", @@ -385,6 +396,8 @@ def test_list_files_in_folder_returns_files(execute_google_api_call_mock): "v3", "files", "list", + scopes=None, + delegated_user_email=None, paginate=True, pageSize=25, supportsAllDrives=True, diff --git a/app/tests/integrations/google_workspace/test_google_service.py b/app/tests/integrations/google_workspace/test_google_service.py index 58a4fc6e..0034f27e 100644 --- a/app/tests/integrations/google_workspace/test_google_service.py +++ b/app/tests/integrations/google_workspace/test_google_service.py @@ -216,8 +216,8 @@ def test_execute_google_api_call_calls_get_google_service_with_delegated_user_em mock_get_google_service.assert_called_once_with( "service_name", "version", - "admin.user@email.com", None, + "admin.user@email.com", ) diff --git a/app/tests/intergrations/test_google_drive_module.py b/app/tests/intergrations/test_google_drive_module.py deleted file mode 100644 index d085dc3a..00000000 --- a/app/tests/intergrations/test_google_drive_module.py +++ /dev/null @@ -1,178 +0,0 @@ -"""NOTE: this module requires a suffix while the intergration/google_drive.py still exists. It should be removed after the integration/google_drive.py is properly refactored with the new google_workspace integration.""" - -import os -import pytest - -from integrations import google_drive - -from unittest.mock import patch - -# Constants for the test -START_HEADING = "DO NOT REMOVE this line as the SRE bot needs it as a placeholder." -END_HEADING = "Trigger" - - -@patch("integrations.google_drive.build") -@patch("integrations.google_drive.pickle") -def test_get_google_service_returns_build_object(pickle_mock, build_mock): - google_drive.get_google_service("drive", "v3") - build_mock.assert_called_once_with( - "drive", "v3", credentials=pickle_mock.loads(os.environ.get("PICKLE_STRING")) - ) - - -@patch("integrations.google_drive.PICKLE_STRING", False) -def test_get_google_service_raises_exception_if_pickle_string_not_set(): - with pytest.raises(Exception) as e: - google_drive.get_google_service("drive", "v3") - assert "Pickle string not set" in str(e.value) - - -@patch("integrations.google_drive.pickle") -def test_get_google_service_raises_exception_if_pickle_string_is_invalid(pickle_mock): - pickle_mock.loads.side_effect = Exception("Invalid pickle string") - with pytest.raises(Exception) as e: - google_drive.get_google_service("drive", "v3") - assert "Invalid pickle string" in str(e.value) - - -@patch("integrations.google_drive.get_google_service") -def test_create_new_folder_returns_folder_id(get_google_service_mock): - # test that a new folder created returns the folder id - get_google_service_mock.return_value.files.return_value.create.return_value.execute.return_value = { - "id": "foo" - } - assert google_drive.create_new_folder("name", "parent_folder") == "foo" - - -@patch("integrations.google_drive.get_google_service") -def test_create_new_incident(get_google_service_mock): - get_google_service_mock.return_value.files.return_value.copy.return_value.execute.return_value = { - "id": "test_incident" - } - assert ( - google_drive.create_new_incident("test_incident", "test_folder") - == "test_incident" - ) - - -@patch("integrations.google_drive.get_google_service") -def test_create_new_google_doc_file(get_google_service_mock): - # test that a new google doc is successfully created - get_google_service_mock.return_value.files.return_value.create.return_value.execute.return_value = { - "id": "google_doc_id" - } - assert ( - google_drive.create_new_docs_file("name", "parent_folder_id") == "google_doc_id" - ) - - -@patch("integrations.google_drive.get_google_service") -def test_update_incident_file(get_google_service_mock): - # test that incident doc's status is - get_google_service_mock.return_value.files.return_value.create.return_value.execute.return_value = { - "id": "google_doc_id" - } - assert ( - google_drive.create_new_docs_file("name", "parent_folder_id") == "google_doc_id" - ) - - -@patch("integrations.google_drive.get_google_service") -def test_create_new_google_sheets_file(get_google_service_mock): - # test that a new google sheets is successfully created - get_google_service_mock.return_value.files.return_value.create.return_value.execute.return_value = { - "id": "google_sheets_id" - } - assert ( - google_drive.create_new_sheets_file("name", "parent_folder_id") - == "google_sheets_id" - ) - - -@patch("integrations.google_drive.get_google_service") -def test_copy_file_to_folder(get_google_service_mock): - # test that we can successfully copy files to a different folder - get_google_service_mock.return_value.files.return_value.copy.return_value.execute.return_value = { - "id": "file_id" - } - get_google_service_mock.return_value.files.return_value.update.return_value.execute.return_value = { - "id": "updated_file_id" - } - assert ( - google_drive.copy_file_to_folder( - "file_id", "name", "parent_folder", "destination_folder" - ) - == "updated_file_id" - ) - - -@patch("integrations.google_drive.get_google_service") -def test_list_folders_returns_folder_names(get_google_service_mock): - get_google_service_mock.return_value.files.return_value.list.return_value.execute.return_value = { - "files": [{"name": "test_folder"}] - } - assert google_drive.list_folders() == [{"name": "test_folder"}] - - -@patch("integrations.google_drive.get_google_service") -def test_list_metadata(get_google_service_mock): - get_google_service_mock.return_value.files.return_value.get.return_value.execute.return_value = { - "name": "test_folder", - "appProperties": {"key": "value"}, - } - - assert google_drive.list_metadata("file_id") == { - "name": "test_folder", - "appProperties": {"key": "value"}, - } - - -@patch("integrations.google_drive.get_google_service") -def test_merge_data(get_google_service_mock): - get_google_service_mock.return_value.documents.return_value.batchUpdate.return_value.execute.return_value = ( - True - ) - assert ( - google_drive.merge_data("file_id", "name", "product", "slack_channel", "") - is True - ) - - -@patch("integrations.google_drive.get_google_service") -def test_update_incident_list(get_google_service_mock): - get_google_service_mock.return_value.spreadsheets.return_value.values.return_value.append.return_value.execute.return_value = ( - True - ) - assert ( - google_drive.update_incident_list( - "document_link", "name", "slug", "product", "channel_url" - ) - is True - ) - - -def create_mock_document(content): - elements = [ - { - "paragraph": { - "elements": [ - {"startIndex": 1, "endIndex": 200, "textRun": {"content": text}} - ] - } - } - for text in content - ] - return {"body": {"content": elements}} - - -@patch("integrations.google_drive.list_metadata") -def test_healthcheck_healthy(mock_list_metadata): - mock_list_metadata.return_value = {"id": "test_doc"} - assert google_drive.healthcheck() is True - - -@patch("integrations.google_drive.list_metadata") -def test_healthcheck_unhealthy(mock_list_metadata): - mock_list_metadata.return_value = None - assert google_drive.healthcheck() is False diff --git a/app/tests/modules/role/test_role.py b/app/tests/modules/role/test_role.py index bfd1bfd2..465930b5 100644 --- a/app/tests/modules/role/test_role.py +++ b/app/tests/modules/role/test_role.py @@ -235,8 +235,10 @@ def test_update_modal_locale_to_FR(): assert kwargs["view"]["blocks"][0]["elements"][0]["value"] == "fr-FR" -@patch("integrations.google_drive.get_google_service") -@patch("modules.role.role.google_drive.create_new_folder") +@patch("modules.role.role.BOT_EMAIL", "bot_email") +@patch("modules.role.role.ROLE_SCOPES", ["https://www.googleapis.com/auth/drive"]) +@patch("modules.role.role.google_drive.copy_file_to_folder") +@patch("modules.role.role.google_drive.create_folder") def test_create_new_folder(mock_create_new_folder, get_google_service_mock): # test creating a new folder ack = MagicMock() @@ -244,14 +246,20 @@ def test_create_new_folder(mock_create_new_folder, get_google_service_mock): logger = MagicMock() body = helper_body_payload("en-US") client = MagicMock() - mock_create_new_folder.return_value = "id" + mock_create_new_folder.return_value = {"id": "id"} role.role_view_handler(ack, body, say, logger, client) mock_create_new_folder.assert_called_once_with( - "foo", os.getenv("INTERNAL_TALENT_FOLDER") + "foo", + os.getenv("INTERNAL_TALENT_FOLDER"), + "id", + scopes=["https://www.googleapis.com/auth/drive"], + delegated_user_email="bot_email", ) -@patch("modules.role.role.google_drive.create_new_folder") +@patch("modules.role.role.BOT_EMAIL", "bot_email") +@patch("modules.role.role.ROLE_SCOPES", ["https://www.googleapis.com/auth/drive"]) +@patch("modules.role.role.google_drive.create_folder") @patch("modules.role.role.google_drive.copy_file_to_folder") def test_copy_files_to_internal_talent_folder( mock_copy_file_to_folder, mock_create_new_folder @@ -262,7 +270,7 @@ def test_copy_files_to_internal_talent_folder( logger = MagicMock() body = helper_body_payload("en-US") client = MagicMock() - mock_create_new_folder.return_value = "folder_id" + mock_create_new_folder.return_value = {"id": "folder_id"} mock_copy_file_to_folder.return_value = "id" role.role_view_handler(ack, body, say, logger, client) mock_copy_file_to_folder.assert_has_calls( @@ -272,36 +280,48 @@ def test_copy_files_to_internal_talent_folder( "Template 2022/06 - foo Interview Panel Scoring Document - ", os.getenv("TEMPLATES_FOLDER"), "folder_id", + scopes=["https://www.googleapis.com/auth/drive"], + delegated_user_email="bot_email", ), call( os.getenv("CORE_VALUES_INTERVIEW_NOTES_TEMPLATE"), "Template EN+FR 2022/09- foo - Core Values Panel - Interview Guide - - ", os.getenv("TEMPLATES_FOLDER"), "folder_id", + scopes=["https://www.googleapis.com/auth/drive"], + delegated_user_email="bot_email", ), call( os.getenv("TECHNICAL_INTERVIEW_NOTES_TEMPLATE"), "Template EN+FR 2022/09 - foo - Technical Panel - Interview Guide - - ", os.getenv("TEMPLATES_FOLDER"), "folder_id", + scopes=["https://www.googleapis.com/auth/drive"], + delegated_user_email="bot_email", ), call( os.getenv("INTAKE_FORM_TEMPLATE"), "TEMPLATE Month YYYY - foo - Kick-off form", os.getenv("TEMPLATES_FOLDER"), "folder_id", + scopes=["https://www.googleapis.com/auth/drive"], + delegated_user_email="bot_email", ), call( os.getenv("SOMC_TEMPLATE"), "SoMC Template", os.getenv("TEMPLATES_FOLDER"), "folder_id", + scopes=["https://www.googleapis.com/auth/drive"], + delegated_user_email="bot_email", ), call( os.getenv("RECRUITMENT_FEEDBACK_TEMPLATE"), "Recruitment Feedback - foo", os.getenv("TEMPLATES_FOLDER"), "folder_id", + scopes=["https://www.googleapis.com/auth/drive"], + delegated_user_email="bot_email", ), ] ) @@ -329,7 +349,7 @@ def test_role_modal_view(mock_role_modal_view): mock_role_modal_view.assert_called_once() -@patch("modules.role.role.google_drive.create_new_folder") +@patch("modules.role.role.google_drive.create_folder") @patch("modules.role.role.google_drive.copy_file_to_folder") def test_role_creates_channel_and_sets_topic_and_announces_channel( mock_copy_file_to_folder, mock_create_new_folder @@ -357,7 +377,7 @@ def test_role_creates_channel_and_sets_topic_and_announces_channel( # test that indicated users are invited to the channel -@patch("modules.role.role.google_drive.create_new_folder") +@patch("modules.role.role.google_drive.create_folder") @patch("modules.role.role.google_drive.copy_file_to_folder") def test_role_add_invited_users_to_channel( mock_copy_file_to_folder, mock_create_new_folder