Skip to content
18 changes: 12 additions & 6 deletions taketwo-webapi/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
db_password = os.getenv("DB_PASSWORD")

client = None
db = None
creds = None

app = FastAPI()
Expand Down Expand Up @@ -97,11 +96,12 @@ def validate_token_IBM(token, authURL, clientId, clientSecret=Depends(oauth2_sch


client = couchdb.Server(f'http://{db_username}:{db_password}@{db_host}:{db_port}/')
try:
db = client.create(db_name)
except couchdb.PreconditionFailed:
db = client[db_name]

def getDb():
try:
return client.create(db_name)
except couchdb.PreconditionFailed:
return client[db_name]

class Flagged(BaseModel):
_id: Optional[str]
Expand Down Expand Up @@ -140,11 +140,13 @@ def login(form_data: OAuth2PasswordRequestForm = Depends()):

@app.get("/mark")
def get_marks(user: dict = Depends(validate)):
db = getDb()
return list(map(lambda item: dict(item.doc.items()), db.view('_all_docs',include_docs=True)))


@app.post("/mark")
def save_mark(item: Flagged, user: dict = Depends(validate)):
db = getDb()
item.user_id = user["sub"]
data = item.dict()
_id, _ = db.save(data)
Expand All @@ -153,6 +155,7 @@ def save_mark(item: Flagged, user: dict = Depends(validate)):

@app.put("/mark/{_id}")
def update_mark(_id: str, item: Flagged, user: dict = Depends(validate)):
db = getDb()
doc = db[_id]
doc["category"] = item.category
db[doc.id] = doc
Expand All @@ -161,6 +164,7 @@ def update_mark(_id: str, item: Flagged, user: dict = Depends(validate)):

@app.delete("/mark")
def delete_mark(_id: str, user: dict = Depends(validate)):
db = getDb()
my_document = db[_id]
db.delete(my_document)
return {"status": "success"}
Expand Down Expand Up @@ -207,6 +211,7 @@ def read_categories():

@app.put("/analyse")
def analyse_text(text: Text):
db = getDb()
res = []
for item in db.view('_all_docs',include_docs=True):
doc = item.doc
Expand All @@ -217,9 +222,10 @@ def analyse_text(text: Text):
@app.put("/check")
def check_words(text: Text):
res = []
db = getDb()
for item in db.view('_all_docs',include_docs=True):
doc = item.doc
if doc["category"] == "racial slur" and doc["flagged_string"].lower() in text.content.lower():
if doc["category"] == "racial-slur" and doc["flagged_string"].lower() in text.content.lower():
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, this didn't match what was being returned from the read_categories method so the marks saved through template.html weren't recognized by this method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can take this out if we'd rather have this fixed in a separate change

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Separate PR works better.

res.append({"flag" : doc["flagged_string"], "category" : doc["category"], "info" : doc["info"]})

line_by_line = []
Expand Down
45 changes: 45 additions & 0 deletions testing/integration/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import requests
#example api test

import json
import sys
sys.path.append('../../taketwo-webapi')
sys.path.append('../util')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm new to python and dependencies so I'm not sure if this is optimal for being able to access adjacent files

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this seems a little odd to me. Please see here on how to load multiple files - https://fastapi.tiangolo.com/it/tutorial/bigger-applications/

Basically, you need empty __init__.py file to tell python that the folder is a module. You can then simply load as import root_folder.sub_folder.sub_folder.filename.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool! I'll update

from main import app
from main import validate
from main import getDb
from fastapi.testclient import TestClient
from util.assert_util import comparePayloads

def override_validate():
return {"sub": 'test'}

app.dependency_overrides[validate] = override_validate

def test_save_mark():
url = '/mark'

# Additional headers.
headers = {'Content-Type': 'application/json' }

# Body
payload = {'user_id': "test", 'flagged_string': "test string", 'category': "stereotyping", 'info': "none", 'url': "example.com"}

client = TestClient(app)
# convert dict to json by json.dumps() for body data.
resp = client.post(url, headers=headers, data=json.dumps(payload,indent=4))

# Validate response headers and body contents, e.g. status code.
assert resp.status_code == 200, "Unexpected status code. Was: " + str(resp.status_code)
resp_body = resp.json()
assert resp_body['url'] == 'example.com', "Unexpected url. Was: " + resp_body['url']

#make sure payload stored properly
storedPayload = getDb().get(resp_body["_id"])
comparePayloads(payload, storedPayload)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Different from the previous version, add comparison of what's stored in the db to the payload we send to make sure it's stored properly


# print response full body as text
print(resp.text)

if __name__ == '__main__':
test_save_mark()
10 changes: 10 additions & 0 deletions testing/integration/util/assert_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

def comparePayloads(expected: dict, actual: dict):
assert expected["user_id"] == actual["user_id"], showDiff(str(expected["user_id"]), str(actual["user_id"]))
assert expected["flagged_string"] == actual["flagged_string"], showDiff(str(expected["flagged_string"]), str(actual["flagged_string"]))
assert expected["category"] == actual["category"], showDiff(str(expected["category"]), str(actual["category"]))
assert expected["info"] == actual["info"], showDiff(str(expected["info"]), str(actual["info"]))
assert expected["url"] == actual["url"], showDiff(str(expected["url"]), str(actual["url"]))

def showDiff(expected: str, actual: str):
return "Expected: " + expected + ", Actual: " + actual
25 changes: 0 additions & 25 deletions testing/test.py

This file was deleted.

60 changes: 60 additions & 0 deletions testing/unit/test_analyse_text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from json import load
import sys
sys.path.append('../../taketwo-webapi')
sys.path.append('../util')
import unittest
from unittest.mock import patch

from main import analyse_text
from main import Text

from util.mock_db_util import getRow
from util.mock_db_util import setupMocks

class TestAnalyseText(unittest.TestCase):

@patch('main.getDb')
def test_multiple_items_with_flagged_string(self, getDbMock):
firstRow = getRow("bad_string", "the_category", "the_info")
secondRow = getRow("bad_string_2", "the_category", "the_info")

setupMocks(getDbMock, [firstRow, secondRow])

data = {'content': 'bad_string, bad_string_2'}
text = Text(**data)
output = analyse_text(text)

expected = {"biased":
[{"flag": "bad_string", "category": "the_category", "info": "the_info"},
{"flag": "bad_string_2", "category": "the_category", "info": "the_info"}]}
assert output == expected, "Actual: " + str(output) + " Expected: " + str(expected)

@patch('main.getDb')
def test_single_item_with_flagged_string(self, getDbMock):
firstRow = getRow("bad_string", "the_category", "the_info")

setupMocks(getDbMock, [firstRow])

data = {'content': 'bad_string'}
text = Text(**data)
output = analyse_text(text)

expected = {"biased":
[{"flag": "bad_string", "category": "the_category", "info": "the_info"}]}
assert output == expected, "Actual: " + str(output) + " Expected: " + str(expected)

@patch('main.getDb')
def test_no_text_provided(self, getDbMock):
firstRow = getRow("bad_string", "the_category", "the_info")

setupMocks(getDbMock, [firstRow])

data = {'content': ''}
text = Text(**data)
output = analyse_text(text)

expected = {"biased": []}
assert output == expected, "Actual: " + str(output) + " Expected: " + str(expected)

if __name__ == '__main__':
unittest.main()
74 changes: 74 additions & 0 deletions testing/unit/test_check_words.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from json import load
import sys
sys.path.append('../../taketwo-webapi')
sys.path.append('../util')
import unittest
from unittest.mock import patch

from main import check_words
from main import Text

from util.mock_db_util import getRow
from util.mock_db_util import setupMocks

class TestCheckWords(unittest.TestCase):

@patch('main.getDb')
def test_no_text_provided(self, getDbMock):
firstRow = getRow("bad_string", "racial-slur", "the_info")

setupMocks(getDbMock, [firstRow])

data = {'content': ''}
text = Text(**data)
output = check_words(text)

expected = []
assert output == expected, "Actual: " + str(output) + " Expected: " + str(expected)

@patch('main.getDb')
def test_single_flagged_string_in_content(self,getDbMock):
firstRow = getRow("bad_string", "racial-slur", "the_info")

setupMocks(getDbMock, [firstRow])

data = {'content': 'bad_string'}
text = Text(**data)
output = check_words(text)

expected = [{"line":1, "word":"bad_string", "additional_info":"the_info"}]
assert output == expected, "Actual: " + str(output) + " Expected: " + str(expected)

@patch('main.getDb')
def test_single_flagged_string_and_valid_string_in_content(self,getDbMock):
firstRow = getRow("bad_string", "racial-slur", "the_info")

setupMocks(getDbMock, [firstRow])

data = {'content': 'bad_string\nokay_string'}
text = Text(**data)
output = check_words(text)

expected = [{"line":1, "word":"bad_string", "additional_info":"the_info"}]
assert output == expected, "Actual: " + str(output) + " Expected: " + str(expected)

@patch('main.getDb')
def test_multiple_flagged_strings_existing_records(self,getDbMock):

firstRow = getRow("bad_string", "racial-slur", "the_info")
secondRow = getRow("bad_string_2", "racial-slur", "the_info_2")
thirdRow = getRow("biased_thing", "other", "the_info_3")

setupMocks(getDbMock, [firstRow, secondRow, thirdRow])

data = {'content': 'bad_string\nbad_string_2'}
text = Text(**data)
output = check_words(text)

expected = [{"line":1, "word":"bad_string", "additional_info":"the_info"},
{"line":2, "word":"bad_string", "additional_info":"the_info"},
{"line":2, "word":"bad_string_2", "additional_info":"the_info_2"}]
assert output == expected, "Actual: " + str(output) + " Expected: " + str(expected)

if __name__ == '__main__':
unittest.main()
19 changes: 19 additions & 0 deletions testing/unit/util/mock_db_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from couchdb.client import Document
from couchdb.client import Row
from unittest.mock import MagicMock

def getRow(flaggedString, category, info):
firstDoc = Document()
firstDoc["flagged_string"] = flaggedString
firstDoc["category"] = category
firstDoc["info"] = info
firstRow = Row()
firstRow['doc'] = firstDoc
return firstRow

def setupMocks(getDbMock, rows):
mockDbViewResults = MagicMock()
mockDbViewResults.return_value = iter(rows)
dbMock = MagicMock()
dbMock.view = mockDbViewResults
getDbMock.return_value=dbMock