diff --git a/google_sheets/app.py b/google_sheets/app.py index d695690..2971840 100644 --- a/google_sheets/app.py +++ b/google_sheets/app.py @@ -2,11 +2,11 @@ import logging from datetime import datetime from os import environ -from typing import Annotated, Dict, List, Literal, Union +from typing import Annotated, Any, Dict, List, Optional, Union import httpx import pandas as pd -from fastapi import FastAPI, HTTPException, Query, Response, status +from fastapi import Body, FastAPI, HTTPException, Query, Response, status from fastapi.responses import RedirectResponse from googleapiclient.errors import HttpError @@ -59,34 +59,48 @@ async def get_login_url( user_id: Annotated[ int, Query(description="The user ID for which the data is requested") ], + conv_uuid: Annotated[ + Optional[str], Query(description="The conversation UUID") + ] = None, force_new_login: Annotated[bool, Query(description="Force new login")] = False, ) -> Dict[str, str]: + _check_parameters_are_not_none({"conv_uuid": conv_uuid}) if not force_new_login: is_authenticated = await is_authenticated_for_ads(user_id=user_id) if is_authenticated: return {"login_url": "User is already authenticated"} - google_oauth_url = get_google_oauth_url(user_id) + google_oauth_url = get_google_oauth_url(user_id, conv_uuid) # type: ignore markdown_url = f"To navigate Google Ads waters, I require access to your account. Please [click here]({google_oauth_url}) to grant permission." return {"login_url": markdown_url} -@app.get("/login/success", description="Get the success message after login") -async def get_login_success() -> Dict[str, str]: - return {"login_success": "You have successfully logged in"} +def _check_parameters_are_not_none(kwargs: Dict[str, Any]) -> None: + error_message = "The following parameters are required: " + missing_parameters = [key for key, value in kwargs.items() if value is None] + if missing_parameters: + error_message += ", ".join(missing_parameters) + raise HTTPException(status_code=400, detail=error_message) + + +REDIRECT_DOMAIN = environ.get("REDIRECT_DOMAIN", "http://localhost:3000") # Route 2: Save user credentials/token to a JSON file @app.get("/login/callback") async def login_callback( code: Annotated[ - str, Query(description="The authorization code received after successful login") + str, + Query(description="The authorization code received after successful login"), ], - state: Annotated[str, Query(description="State")], + state: Annotated[Optional[str], Query(description="State")] = None, ) -> RedirectResponse: - if not state.isdigit(): + _check_parameters_are_not_none({"state": state}) + user_id_and_chat_uuid = state.split(":") # type: ignore + if not user_id_and_chat_uuid[0].isdigit(): # type: ignore raise HTTPException(status_code=400, detail="User ID must be an integer") - user_id = int(state) + user_id = int(user_id_and_chat_uuid[0]) + chat_uuid = user_id_and_chat_uuid[1] token_request_data = get_token_request_data(code) @@ -122,12 +136,9 @@ async def login_callback( }, ) - # redirect_domain = environ.get("REDIRECT_DOMAIN", "https://captn.ai") - # logged_in_message = "I have successfully logged in" - # redirect_uri = f"{redirect_domain}/chat/{chat_uuid}?msg={logged_in_message}" - # return RedirectResponse(redirect_uri) - # redirect to success page - return RedirectResponse(url=f"{base_url}/login/success") + logged_in_message = "I have successfully logged in" + redirect_uri = f"{REDIRECT_DOMAIN}/chat/{chat_uuid}?msg={logged_in_message}" + return RedirectResponse(redirect_uri) @app.get("/get-sheet", description="Get data from a Google Sheet") @@ -136,16 +147,19 @@ async def get_sheet( int, Query(description="The user ID for which the data is requested") ], spreadsheet_id: Annotated[ - str, Query(description="ID of the Google Sheet to fetch data from") - ], + Optional[str], Query(description="ID of the Google Sheet to fetch data from") + ] = None, title: Annotated[ - str, + Optional[str], Query(description="The title of the sheet to fetch data from"), - ], + ] = None, ) -> Union[str, GoogleSheetValues]: + _check_parameters_are_not_none({"spreadsheet_id": spreadsheet_id, "title": title}) service = await build_service(user_id=user_id, service_name="sheets", version="v4") values = await get_sheet_f( - service=service, spreadsheet_id=spreadsheet_id, range=title + service=service, + spreadsheet_id=spreadsheet_id, # type: ignore + range=title, # type: ignore ) if not values: @@ -163,22 +177,28 @@ async def update_sheet( int, Query(description="The user ID for which the data is requested") ], spreadsheet_id: Annotated[ - str, Query(description="ID of the Google Sheet to fetch data from") - ], + Optional[str], Query(description="ID of the Google Sheet to fetch data from") + ] = None, title: Annotated[ - str, + Optional[str], Query(description="The title of the sheet to update"), - ], - sheet_values: GoogleSheetValues, + ] = None, + sheet_values: Annotated[ + Optional[GoogleSheetValues], + Body(embed=True, description="Values to be written to the Google Sheet"), + ] = None, ) -> Response: + _check_parameters_are_not_none( + {"spreadsheet_id": spreadsheet_id, "title": title, "sheet_values": sheet_values} + ) service = await build_service(user_id=user_id, service_name="sheets", version="v4") try: await update_sheet_f( service=service, - spreadsheet_id=spreadsheet_id, - range=title, - sheet_values=sheet_values, + spreadsheet_id=spreadsheet_id, # type: ignore + range=title, # type: ignore + sheet_values=sheet_values, # type: ignore ) except HttpError as e: raise HTTPException(status_code=e.status_code, detail=e._get_reason()) from e @@ -202,17 +222,20 @@ async def create_sheet( int, Query(description="The user ID for which the data is requested") ], spreadsheet_id: Annotated[ - str, Query(description="ID of the Google Sheet to fetch data from") - ], + Optional[str], Query(description="ID of the Google Sheet to fetch data from") + ] = None, title: Annotated[ - str, + Optional[str], Query(description="The title of the new sheet"), - ], + ] = None, ) -> Response: + _check_parameters_are_not_none({"spreadsheet_id": spreadsheet_id, "title": title}) service = await build_service(user_id=user_id, service_name="sheets", version="v4") try: await create_sheet_f( - service=service, spreadsheet_id=spreadsheet_id, title=title + service=service, + spreadsheet_id=spreadsheet_id, # type: ignore + title=title, # type: ignore ) except HttpError as e: if ( @@ -257,13 +280,15 @@ async def get_all_sheet_titles( int, Query(description="The user ID for which the data is requested") ], spreadsheet_id: Annotated[ - str, Query(description="ID of the Google Sheet to fetch data from") - ], + Optional[str], Query(description="ID of the Google Sheet to fetch data from") + ] = None, ) -> List[str]: + _check_parameters_are_not_none({"spreadsheet_id": spreadsheet_id}) service = await build_service(user_id=user_id, service_name="sheets", version="v4") try: sheets = await get_all_sheet_titles_f( - service=service, spreadsheet_id=spreadsheet_id + service=service, + spreadsheet_id=spreadsheet_id, # type: ignore ) except HttpError as e: raise HTTPException(status_code=e.status_code, detail=e._get_reason()) from e @@ -295,20 +320,51 @@ async def get_all_sheet_titles( ] +def _validate_target_resource(target_resource: Optional[str]) -> None: + if target_resource not in ["ad", "keyword"]: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="The target resource should be either 'ad' or 'keyword'.", + ) + + @app.post( "/process-data", description="Process data to generate new ads or keywords based on the template", ) async def process_data( - template_sheet_values: GoogleSheetValues, - new_campaign_sheet_values: GoogleSheetValues, + template_sheet_values: Annotated[ + Optional[GoogleSheetValues], + Body( + embed=True, + description="Template values to be used for generating new ads or keywords", + ), + ] = None, + new_campaign_sheet_values: Annotated[ + Optional[GoogleSheetValues], + Body( + embed=True, + description="New campaign values to be used for generating new ads or keywords", + ), + ] = None, target_resource: Annotated[ - Literal["ad", "keyword"], Query(description="The target resource to be updated") - ], + Optional[str], + Query( + description="The target resource to be updated. This can be 'ad' or 'keyword'" + ), + ] = None, ) -> GoogleSheetValues: + _check_parameters_are_not_none( + { + "template_sheet_values": template_sheet_values, + "new_campaign_sheet_values": new_campaign_sheet_values, + "target_resource": target_resource, + } + ) + _validate_target_resource(target_resource) if ( - len(template_sheet_values.values) < 2 - or len(new_campaign_sheet_values.values) < 2 + len(template_sheet_values.values) < 2 # type: ignore + or len(new_campaign_sheet_values.values) < 2 # type: ignore ): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -316,11 +372,12 @@ async def process_data( ) try: template_df = pd.DataFrame( - template_sheet_values.values[1:], columns=template_sheet_values.values[0] + template_sheet_values.values[1:], # type: ignore + columns=template_sheet_values.values[0], # type: ignore ) new_campaign_df = pd.DataFrame( - new_campaign_sheet_values.values[1:], - columns=new_campaign_sheet_values.values[0], + new_campaign_sheet_values.values[1:], # type: ignore + columns=new_campaign_sheet_values.values[0], # type: ignore ) except Exception as e: raise HTTPException( @@ -353,7 +410,10 @@ async def process_data( processed_df = process_data_f(template_df, new_campaign_df) - validated_df = validate_output_data(processed_df, target_resource) + validated_df = validate_output_data( + processed_df, + target_resource, # type: ignore + ) values = [validated_df.columns.tolist(), *validated_df.values.tolist()] @@ -369,23 +429,38 @@ async def process_spreadsheet( int, Query(description="The user ID for which the data is requested") ], template_spreadsheet_id: Annotated[ - str, Query(description="ID of the Google Sheet with the template data") - ], + Optional[str], + Query(description="ID of the Google Sheet with the template data"), + ] = None, template_sheet_title: Annotated[ - str, + Optional[str], Query(description="The title of the sheet with the template data"), - ], + ] = None, new_campaign_spreadsheet_id: Annotated[ - str, Query(description="ID of the Google Sheet with the new campaign data") - ], + Optional[str], + Query(description="ID of the Google Sheet with the new campaign data"), + ] = None, new_campaign_sheet_title: Annotated[ - str, + Optional[str], Query(description="The title of the sheet with the new campaign data"), - ], + ] = None, target_resource: Annotated[ - Literal["ad", "keyword"], Query(description="The target resource to be updated") - ], -) -> Response: + Optional[str], + Query( + description="The target resource to be updated, options: 'ad' or 'keyword'" + ), + ] = None, +) -> str: + _check_parameters_are_not_none( + { + "template_spreadsheet_id": template_spreadsheet_id, + "template_sheet_title": template_sheet_title, + "new_campaign_spreadsheet_id": new_campaign_spreadsheet_id, + "new_campaign_sheet_title": new_campaign_sheet_title, + "target_resource": target_resource, + } + ) + _validate_target_resource(target_resource) template_values = await get_sheet( user_id=user_id, spreadsheet_id=template_spreadsheet_id, @@ -417,7 +492,7 @@ async def process_spreadsheet( ) title = ( - f"Captn - {target_resource.capitalize()}s {datetime.now():%Y-%m-%d %H:%M:%S}" + f"Captn - {target_resource.capitalize()}s {datetime.now():%Y-%m-%d %H:%M:%S}" # type: ignore ) await create_sheet( user_id=user_id, @@ -431,7 +506,4 @@ async def process_spreadsheet( sheet_values=processed_values, ) - return Response( - status_code=status.HTTP_201_CREATED, - content=f"Sheet with the name 'Captn - {target_resource.capitalize()}s' has been created successfully.", - ) + return f"Sheet with the name 'Captn - {target_resource.capitalize()}s' has been created successfully." # type: ignore diff --git a/google_sheets/google_api/oauth_settings.py b/google_sheets/google_api/oauth_settings.py index fc55d69..f0cf8a5 100644 --- a/google_sheets/google_api/oauth_settings.py +++ b/google_sheets/google_api/oauth_settings.py @@ -20,12 +20,13 @@ } -def get_google_oauth_url(user_id: int) -> str: +def get_google_oauth_url(user_id: int, conv_uuid: str) -> str: + state = f"{user_id}:{conv_uuid}" google_oauth_url = ( f"{oauth2_settings['auth_uri']}?client_id={oauth2_settings['clientId']}" f"&redirect_uri={oauth2_settings['redirectUri']}&response_type=code" f"&scope={urllib.parse.quote_plus('email https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/drive.metadata.readonly')}" - f"&access_type=offline&prompt=consent&state={user_id}" + f"&access_type=offline&prompt=consent&state={state}" ) return google_oauth_url diff --git a/tests/app/fixtures/openapi.json b/tests/app/fixtures/openapi.json deleted file mode 100644 index af7fe1d..0000000 --- a/tests/app/fixtures/openapi.json +++ /dev/null @@ -1,694 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "google-sheets", - "version": "0.1.0" - }, - "servers": [ - { - "url": "http://localhost:8000", - "description": "Google Sheets app server" - } - ], - "paths": { - "/login": { - "get": { - "summary": "Get Login Url", - "description": "Get the URL to log in with Google", - "operationId": "get_login_url_login_get", - "parameters": [ - { - "name": "user_id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "description": "The user ID for which the data is requested", - "title": "User Id" - }, - "description": "The user ID for which the data is requested" - }, - { - "name": "force_new_login", - "in": "query", - "required": false, - "schema": { - "type": "boolean", - "description": "Force new login", - "default": false, - "title": "Force New Login" - }, - "description": "Force new login" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "title": "Response Get Login Url Login Get" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/login/success": { - "get": { - "summary": "Get Login Success", - "description": "Get the success message after login", - "operationId": "get_login_success_login_success_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "additionalProperties": { - "type": "string" - }, - "type": "object", - "title": "Response Get Login Success Login Success Get" - } - } - } - } - } - } - }, - "/login/callback": { - "get": { - "summary": "Login Callback", - "operationId": "login_callback_login_callback_get", - "parameters": [ - { - "name": "code", - "in": "query", - "required": true, - "schema": { - "type": "string", - "description": "The authorization code received after successful login", - "title": "Code" - }, - "description": "The authorization code received after successful login" - }, - { - "name": "state", - "in": "query", - "required": true, - "schema": { - "type": "string", - "description": "State", - "title": "State" - }, - "description": "State" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {} - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/get-sheet": { - "get": { - "summary": "Get Sheet", - "description": "Get data from a Google Sheet", - "operationId": "get_sheet_get_sheet_get", - "parameters": [ - { - "name": "user_id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "description": "The user ID for which the data is requested", - "title": "User Id" - }, - "description": "The user ID for which the data is requested" - }, - { - "name": "spreadsheet_id", - "in": "query", - "required": true, - "schema": { - "type": "string", - "description": "ID of the Google Sheet to fetch data from", - "title": "Spreadsheet Id" - }, - "description": "ID of the Google Sheet to fetch data from" - }, - { - "name": "title", - "in": "query", - "required": true, - "schema": { - "type": "string", - "description": "The title of the sheet to fetch data from", - "title": "Title" - }, - "description": "The title of the sheet to fetch data from" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "anyOf": [ - { - "type": "string" - }, - { - "$ref": "#/components/schemas/GoogleSheetValues" - } - ], - "title": "Response Get Sheet Get Sheet Get" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/update-sheet": { - "post": { - "summary": "Update Sheet", - "description": "Update data in a Google Sheet within the existing spreadsheet", - "operationId": "update_sheet_update_sheet_post", - "parameters": [ - { - "name": "user_id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "description": "The user ID for which the data is requested", - "title": "User Id" - }, - "description": "The user ID for which the data is requested" - }, - { - "name": "spreadsheet_id", - "in": "query", - "required": true, - "schema": { - "type": "string", - "description": "ID of the Google Sheet to fetch data from", - "title": "Spreadsheet Id" - }, - "description": "ID of the Google Sheet to fetch data from" - }, - { - "name": "title", - "in": "query", - "required": true, - "schema": { - "type": "string", - "description": "The title of the sheet to update", - "title": "Title" - }, - "description": "The title of the sheet to update" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GoogleSheetValues" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {} - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/create-sheet": { - "post": { - "summary": "Create Sheet", - "description": "Create a new Google Sheet within the existing spreadsheet", - "operationId": "create_sheet_create_sheet_post", - "parameters": [ - { - "name": "user_id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "description": "The user ID for which the data is requested", - "title": "User Id" - }, - "description": "The user ID for which the data is requested" - }, - { - "name": "spreadsheet_id", - "in": "query", - "required": true, - "schema": { - "type": "string", - "description": "ID of the Google Sheet to fetch data from", - "title": "Spreadsheet Id" - }, - "description": "ID of the Google Sheet to fetch data from" - }, - { - "name": "title", - "in": "query", - "required": true, - "schema": { - "type": "string", - "description": "The title of the new sheet", - "title": "Title" - }, - "description": "The title of the new sheet" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {} - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/get-all-file-names": { - "get": { - "summary": "Get All File Names", - "description": "Get all sheets associated with the user", - "operationId": "get_all_file_names_get_all_file_names_get", - "parameters": [ - { - "name": "user_id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "description": "The user ID for which the data is requested", - "title": "User Id" - }, - "description": "The user ID for which the data is requested" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "type": "object", - "additionalProperties": { - "type": "string" - }, - "title": "Response Get All File Names Get All File Names Get" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/get-all-sheet-titles": { - "get": { - "summary": "Get All Sheet Titles", - "description": "Get all sheet titles within a Google Spreadsheet", - "operationId": "get_all_sheet_titles_get_all_sheet_titles_get", - "parameters": [ - { - "name": "user_id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "description": "The user ID for which the data is requested", - "title": "User Id" - }, - "description": "The user ID for which the data is requested" - }, - { - "name": "spreadsheet_id", - "in": "query", - "required": true, - "schema": { - "type": "string", - "description": "ID of the Google Sheet to fetch data from", - "title": "Spreadsheet Id" - }, - "description": "ID of the Google Sheet to fetch data from" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "string" - }, - "title": "Response Get All Sheet Titles Get All Sheet Titles Get" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/process-data": { - "post": { - "summary": "Process Data", - "description": "Process data to generate new ads or keywords based on the template", - "operationId": "process_data_process_data_post", - "parameters": [ - { - "name": "target_resource", - "in": "query", - "required": true, - "schema": { - "enum": [ - "ad", - "keyword" - ], - "type": "string", - "description": "The target resource to be updated", - "title": "Target Resource" - }, - "description": "The target resource to be updated" - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Body_process_data_process_data_post" - } - } - } - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GoogleSheetValues" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - }, - "/process-spreadsheet": { - "post": { - "summary": "Process Spreadsheet", - "description": "Process data to generate new ads or keywords based on the template", - "operationId": "process_spreadsheet_process_spreadsheet_post", - "parameters": [ - { - "name": "user_id", - "in": "query", - "required": true, - "schema": { - "type": "integer", - "description": "The user ID for which the data is requested", - "title": "User Id" - }, - "description": "The user ID for which the data is requested" - }, - { - "name": "template_spreadsheet_id", - "in": "query", - "required": true, - "schema": { - "type": "string", - "description": "ID of the Google Sheet with the template data", - "title": "Template Spreadsheet Id" - }, - "description": "ID of the Google Sheet with the template data" - }, - { - "name": "template_sheet_title", - "in": "query", - "required": true, - "schema": { - "type": "string", - "description": "The title of the sheet with the template data", - "title": "Template Sheet Title" - }, - "description": "The title of the sheet with the template data" - }, - { - "name": "new_campaign_spreadsheet_id", - "in": "query", - "required": true, - "schema": { - "type": "string", - "description": "ID of the Google Sheet with the new campaign data", - "title": "New Campaign Spreadsheet Id" - }, - "description": "ID of the Google Sheet with the new campaign data" - }, - { - "name": "new_campaign_sheet_title", - "in": "query", - "required": true, - "schema": { - "type": "string", - "description": "The title of the sheet with the new campaign data", - "title": "New Campaign Sheet Title" - }, - "description": "The title of the sheet with the new campaign data" - }, - { - "name": "target_resource", - "in": "query", - "required": true, - "schema": { - "enum": [ - "ad", - "keyword" - ], - "type": "string", - "description": "The target resource to be updated", - "title": "Target Resource" - }, - "description": "The target resource to be updated" - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {} - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "Body_process_data_process_data_post": { - "properties": { - "template_sheet_values": { - "$ref": "#/components/schemas/GoogleSheetValues" - }, - "new_campaign_sheet_values": { - "$ref": "#/components/schemas/GoogleSheetValues" - } - }, - "type": "object", - "required": [ - "template_sheet_values", - "new_campaign_sheet_values" - ], - "title": "Body_process_data_process_data_post" - }, - "GoogleSheetValues": { - "properties": { - "values": { - "items": { - "items": {}, - "type": "array" - }, - "type": "array", - "title": "Values", - "description": "Values to be written to the Google Sheet." - } - }, - "type": "object", - "required": [ - "values" - ], - "title": "GoogleSheetValues" - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "type": "array", - "title": "Detail" - } - }, - "type": "object", - "title": "HTTPValidationError" - }, - "ValidationError": { - "properties": { - "loc": { - "items": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "integer" - } - ] - }, - "type": "array", - "title": "Location" - }, - "msg": { - "type": "string", - "title": "Message" - }, - "type": { - "type": "string", - "title": "Error Type" - } - }, - "type": "object", - "required": [ - "loc", - "msg", - "type" - ], - "title": "ValidationError" - } - } - } -} diff --git a/tests/app/test_app.py b/tests/app/test_app.py index 90f0de1..f60b806 100644 --- a/tests/app/test_app.py +++ b/tests/app/test_app.py @@ -1,13 +1,12 @@ -import json -from pathlib import Path -from typing import Optional, Union +from typing import Any, Dict, Optional, Union from unittest.mock import MagicMock, patch import pytest +from fastapi import HTTPException from fastapi.testclient import TestClient from googleapiclient.errors import HttpError -from google_sheets.app import app +from google_sheets.app import _check_parameters_are_not_none, app from google_sheets.model import GoogleSheetValues client = TestClient(app) @@ -130,7 +129,9 @@ def test_update_sheet( ) as mock_update_sheet, ): json_data = { - "values": [["Campaign", "Ad Group"], ["Campaign A", "Ad group A"]] + "sheet_values": { + "values": [["Campaign", "Ad Group"], ["Campaign A", "Ad group A"]] + } } response = client.post( "/update-sheet?user_id=123&spreadsheet_id=abc&title=Sheet1", @@ -271,11 +272,39 @@ def test_process_data( class TestOpenAPIJSON: def test_openapi(self) -> None: - path = Path(__file__).parent / "fixtures" / "openapi.json" - with Path.open(path, "r") as f: - expected = f.read() - - expected_json = json.loads(expected) response = client.get("/openapi.json") assert response.status_code == 200 - assert response.json() == expected_json + + paths = response.json()["paths"] + expected_path_keys = [ + "/login", + "/login/callback", + "/get-sheet", + "/update-sheet", + "/create-sheet", + "/get-all-file-names", + "/get-all-sheet-titles", + "/process-data", + "/process-spreadsheet", + ] + + for key in expected_path_keys: + assert key in paths + + +class TestHelperFunctions: + @pytest.mark.parametrize( + ("endpoint_params", "raises_exception"), + [ + ({"user_id": "123", "spreadsheet_id": "abc", "title": "Sheet1"}, False), + ({"user_id": "123", "spreadsheet_id": "abc", "title": None}, True), + ], + ) + def test_check_parameters_are_not_none( + self, endpoint_params: Dict[str, Any], raises_exception: bool + ) -> None: + if raises_exception: + with pytest.raises(HTTPException): + _check_parameters_are_not_none(endpoint_params) + else: + _check_parameters_are_not_none(endpoint_params)