mirror of
https://github.com/kevin-DL/ShortMe-URL-Shortener.git
synced 2026-01-11 19:14:29 +00:00
Initial commit
This commit is contained in:
0
app/tests/api_testing/__init__.py
Normal file
0
app/tests/api_testing/__init__.py
Normal file
18
app/tests/api_testing/api_helper.py
Normal file
18
app/tests/api_testing/api_helper.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import json
|
||||
from app.app import app as a
|
||||
|
||||
|
||||
class ApiHelper:
|
||||
HEADERS = {
|
||||
'Authorization': f'Bearer {a.secret_key}'
|
||||
}
|
||||
|
||||
def get_auth_token(self, app):
|
||||
r = app.test_client().get(
|
||||
'/api/get_token', headers=self.HEADERS
|
||||
)
|
||||
|
||||
print(r.get_data(as_text=True))
|
||||
|
||||
key = json.loads(r.get_data(as_text=True))
|
||||
return key
|
||||
26
app/tests/api_testing/settings.py
Normal file
26
app/tests/api_testing/settings.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import os
|
||||
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY')
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI')
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = os.environ.get('SQLALCHEMY_TRACK_MODIFICATIONS')
|
||||
ADMIN_USERNAME = os.environ.get('ADMIN_USERNAME')
|
||||
ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD')
|
||||
|
||||
MAIL_SERVER = os.environ.get('MAIL_SERVER')
|
||||
MAIL_PORT = int(os.environ.get('MAIL_PORT'))
|
||||
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
|
||||
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
|
||||
MAIL_USE_TLS = False
|
||||
MAIL_USE_SSL = True
|
||||
|
||||
"""
|
||||
:::::::: .env file content ::::::::
|
||||
SQLALCHEMY_DATABASE_URI=sqlite:///db.sqlite3
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS=False
|
||||
SECRET_KEY=randomKey
|
||||
|
||||
MAIL_SERVER=smtp.gmail.com
|
||||
MAIL_PORT=465
|
||||
MAIL_USERNAME=email@email.com
|
||||
MAIL_PASSWORD=password
|
||||
"""
|
||||
81
app/tests/api_testing/test_api.py
Normal file
81
app/tests/api_testing/test_api.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# ------- standard library imports -------
|
||||
import json
|
||||
import unittest
|
||||
|
||||
# ------- local imports -------
|
||||
from time import sleep
|
||||
|
||||
from app.server.db.extensions import db
|
||||
from app.server.db.models import Url
|
||||
from app.app import create_app
|
||||
from app.tests.api_testing.api_helper import ApiHelper
|
||||
|
||||
|
||||
class TestApp(unittest.TestCase):
|
||||
VALID_URL = 'youtube.com'
|
||||
INVALID_URL = 'www.youtube.com/what?a=b&c=d'
|
||||
INVALID_PARAM = 'INVALID'
|
||||
|
||||
def setUp(self):
|
||||
self.helper = ApiHelper()
|
||||
self.app = create_app(config_file='settings.py')
|
||||
sleep(1)
|
||||
self.key = self.helper.get_auth_token(self.app)
|
||||
|
||||
def test_01_shorten_url_success(self):
|
||||
response = self.app.test_client().post(
|
||||
'/api/shorten',
|
||||
headers={'Authorization': f'Bearer {self.key}'},
|
||||
data={'url': self.VALID_URL}
|
||||
)
|
||||
|
||||
res_dict = json.loads(response.get_data(as_text=True))
|
||||
|
||||
short_url = res_dict['short_url']
|
||||
original_url = res_dict['original_url']
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(short_url), 5)
|
||||
self.assertEqual(original_url, 'http://youtube.com')
|
||||
|
||||
def test_02_shorten_url_fail(self):
|
||||
response = self.app.test_client().post(
|
||||
'/api/shorten',
|
||||
headers={'Authorization': f'Bearer {self.key}'},
|
||||
data={'url': self.INVALID_URL},
|
||||
)
|
||||
|
||||
res_dict = json.loads(response.get_data(as_text=True))
|
||||
|
||||
self.assertEqual(response.status_code, 404)
|
||||
self.assertEqual(res_dict['success'], False)
|
||||
self.assertEqual(res_dict['message'], 'could not shorten this URL (page_not_found)')
|
||||
|
||||
def test_03_total_clicks(self):
|
||||
# add url to db
|
||||
response = self.app.test_client().post(
|
||||
'/api/shorten',
|
||||
headers={'Authorization': f'Bearer {self.key}'},
|
||||
data={'url': 'youtube.com'},
|
||||
)
|
||||
|
||||
with self.app.app_context():
|
||||
url = Url.query.filter_by(original_url='http://youtube.com').first()
|
||||
short_url = url.short_url
|
||||
|
||||
response = self.app.test_client().get(
|
||||
'/api/total_clicks',
|
||||
data={'url': short_url},
|
||||
)
|
||||
|
||||
res_dict = json.loads(response.get_data(as_text=True))
|
||||
self.assertEqual(res_dict['total'], 0)
|
||||
|
||||
def tearDown(self):
|
||||
db.session.remove()
|
||||
with self.app.app_context():
|
||||
db.drop_all()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
0
app/tests/front_end_testing/__init__.py
Normal file
0
app/tests/front_end_testing/__init__.py
Normal file
0
app/tests/front_end_testing/index/__init__.py
Normal file
0
app/tests/front_end_testing/index/__init__.py
Normal file
40
app/tests/front_end_testing/index/index.py
Normal file
40
app/tests/front_end_testing/index/index.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from app.tests.utilities import selenium_utility
|
||||
|
||||
|
||||
class Index(selenium_utility.SeleniumUtility):
|
||||
_heading_locator = '//p[@id="heading-p"]'
|
||||
_url_input_locator = '//input[@id="url-input"]'
|
||||
_shorten_button_locator = '//button[@type="submit"]'
|
||||
_enter_url_warning = '//div[@class="alert-box alert-warning"]'
|
||||
_try_again_button = '//button[@id="try-again-btn"]'
|
||||
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
super().__init__(driver)
|
||||
self.url_input = self.get_element(self._url_input_locator)
|
||||
self.shorten_button = self.get_element(self._shorten_button_locator)
|
||||
|
||||
def get_heading_text(self):
|
||||
return self.get_element(self._heading_locator).text
|
||||
|
||||
def enter_valid_url(self):
|
||||
self.url_input = self.get_element(self._url_input_locator)
|
||||
self.url_input.click()
|
||||
self.url_input.send_keys('youtube.com')
|
||||
|
||||
def enter_invalid_url(self):
|
||||
self.url_input.click()
|
||||
self.url_input.send_keys('https://www.youtube.com/what?a=b&c=d')
|
||||
|
||||
def click_shorten_button(self):
|
||||
self.shorten_button = self.get_element(self._shorten_button_locator)
|
||||
self.shorten_button.click()
|
||||
|
||||
def check_warning_present(self):
|
||||
return self.get_element(self._enter_url_warning).is_displayed()
|
||||
|
||||
def get_current_url(self):
|
||||
return self.driver.current_url.split('/')[-1]
|
||||
|
||||
def click_try_again(self):
|
||||
self.wait_for_element(self._try_again_button).click()
|
||||
0
app/tests/front_end_testing/result/__init__.py
Normal file
0
app/tests/front_end_testing/result/__init__.py
Normal file
29
app/tests/front_end_testing/result/result.py
Normal file
29
app/tests/front_end_testing/result/result.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from app.tests.utilities import selenium_utility
|
||||
import pyperclip as pc
|
||||
|
||||
|
||||
class Result(selenium_utility.SeleniumUtility):
|
||||
_url_input_locator = '//input[@id="copy-able"]'
|
||||
_copy_button_locator = '//button[@id="copy-btn"]'
|
||||
_total_clicks_url = '//a[@id="total-clicks-link"]'
|
||||
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
super().__init__(driver)
|
||||
|
||||
self.url_input = self.get_element(self._url_input_locator)
|
||||
self.copy_button = self.get_element(self._copy_button_locator)
|
||||
|
||||
def get_input_text(self):
|
||||
return self.get_element(self._url_input_locator).get_attribute('value')
|
||||
|
||||
def click_copy_button(self):
|
||||
self.copy_button.click()
|
||||
|
||||
def go_to_total_clicks(self):
|
||||
self.get_element(self._total_clicks_url).click()
|
||||
|
||||
@staticmethod
|
||||
def get_clipboard_content():
|
||||
text = pc.paste()
|
||||
return text
|
||||
78
app/tests/front_end_testing/test_front_end.py
Normal file
78
app/tests/front_end_testing/test_front_end.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# ------- standard library imports -------
|
||||
import unittest
|
||||
import multiprocessing
|
||||
|
||||
# ------- 3rd party imports -------
|
||||
from selenium import webdriver
|
||||
from flask_testing import LiveServerTestCase
|
||||
|
||||
# ------- local imports -------
|
||||
from app.app import create_app
|
||||
from app.tests.front_end_testing.index.index import Index
|
||||
from app.tests.front_end_testing.result.result import Result
|
||||
from app.tests.front_end_testing.total_clicks.total_clicks import TotalClicks
|
||||
|
||||
|
||||
class TestAppSuccess(LiveServerTestCase, unittest.TestCase):
|
||||
multiprocessing.set_start_method("fork")
|
||||
|
||||
def create_app(self):
|
||||
app = create_app(config_file='tests/front_end_testing/settings.py')
|
||||
app.testing = True
|
||||
app.config.update(LIVESERVER_PORT=5002)
|
||||
return app
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.chrome_browser = webdriver.Chrome()
|
||||
|
||||
def test(self):
|
||||
try:
|
||||
"""Test the index page."""
|
||||
self.chrome_browser.get(self.get_server_url())
|
||||
index = Index(self.chrome_browser)
|
||||
# test that empty input shows error
|
||||
index.click_shorten_button()
|
||||
self.assertEqual(index.check_warning_present(), True)
|
||||
|
||||
# test that an invalid url redirecrt to error page
|
||||
index.enter_invalid_url()
|
||||
index.click_shorten_button()
|
||||
self.assertEqual(index.get_current_url(), 'error')
|
||||
# Go back to home page
|
||||
index.click_try_again()
|
||||
|
||||
# check if heading is present
|
||||
heading = index.get_heading_text()
|
||||
self.assertEqual(heading,
|
||||
'ShortMe is a free tool to shorten URLs. Create a short & memorable URL in seconds.')
|
||||
|
||||
# test that a valid URL is working
|
||||
index.enter_valid_url()
|
||||
index.click_shorten_button()
|
||||
|
||||
"""Test the result page once the long URL has been shortened"""
|
||||
result = Result(self.chrome_browser)
|
||||
# test that the short URL exist inside the input element
|
||||
short_url = result.get_input_text()
|
||||
self.assertIsNotNone(short_url)
|
||||
|
||||
# test that the copy button works
|
||||
result.click_copy_button()
|
||||
clipboard_content = result.get_clipboard_content()
|
||||
self.assertEqual(clipboard_content, short_url)
|
||||
|
||||
# go to the total_clicks page
|
||||
result.go_to_total_clicks()
|
||||
|
||||
total_clicks = TotalClicks(self.chrome_browser)
|
||||
# test that the text matches the expected
|
||||
p_text = total_clicks.get_total_paragraph_text()
|
||||
self.assertEqual(p_text, 'Your URL has been clicked 0 times so far.')
|
||||
|
||||
finally:
|
||||
self.chrome_browser.quit()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
12
app/tests/front_end_testing/total_clicks/total_clicks.py
Normal file
12
app/tests/front_end_testing/total_clicks/total_clicks.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from app.tests.utilities import selenium_utility
|
||||
|
||||
|
||||
class TotalClicks(selenium_utility.SeleniumUtility):
|
||||
_url_input_locator = '//p[@id="total-p"]'
|
||||
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
super().__init__(driver)
|
||||
|
||||
def get_total_paragraph_text(self):
|
||||
return self.get_element(self._url_input_locator).text
|
||||
0
app/tests/utilities/__init__.py
Normal file
0
app/tests/utilities/__init__.py
Normal file
20
app/tests/utilities/logger.py
Normal file
20
app/tests/utilities/logger.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import inspect
|
||||
import logging
|
||||
|
||||
|
||||
def Logger(log_level=logging.INFO):
|
||||
# Gets the name of the class / method from where this method is called
|
||||
logger_name = inspect.stack()[1][3]
|
||||
logger = logging.getLogger(logger_name)
|
||||
# By default, log all messages
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
file_handler = logging.FileHandler("{0}.log".format(logger_name), mode='w')
|
||||
file_handler.setLevel(log_level)
|
||||
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s: %(message)s',
|
||||
datefmt='%m/%d/%Y %I:%M:%S %p')
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
return logger
|
||||
137
app/tests/utilities/selenium_utility.py
Normal file
137
app/tests/utilities/selenium_utility.py
Normal file
@@ -0,0 +1,137 @@
|
||||
# ------- standard library imports -------
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
# ------- 3rd party imports -------
|
||||
from selenium.common.exceptions import *
|
||||
from selenium.webdriver.common.by import By
|
||||
from time import sleep, strftime, localtime
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.webdriver.support.ui import Select
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
|
||||
# ------- local imports -------
|
||||
from app.tests.utilities import logger
|
||||
|
||||
|
||||
class SeleniumUtility:
|
||||
"""
|
||||
This utilitie is used to make it easier to use Selenium
|
||||
"""
|
||||
|
||||
log = logger.Logger(logging.DEBUG)
|
||||
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
self.actions = ActionChains(driver)
|
||||
|
||||
@staticmethod
|
||||
def _get_by_type(locator_type):
|
||||
"""Returning the By type. xpath -> By.XPATH"""
|
||||
return getattr(By, locator_type.upper())
|
||||
|
||||
def send_key_command(self, element, key):
|
||||
"""Sending key commands to a pre-found element. Keys.RETURN"""
|
||||
try:
|
||||
element.send_keys(getattr(Keys, key.upper()))
|
||||
self.log.info(f'Key: {key} sent to element: {element}')
|
||||
|
||||
except AttributeError as e:
|
||||
print(e)
|
||||
self.log.info(f'Could not send keys to {element}')
|
||||
|
||||
def take_screenshot(self, sleep_time=0):
|
||||
sleep(sleep_time)
|
||||
Path("screenshots").mkdir(exist_ok=True)
|
||||
t = localtime()
|
||||
current_time = str(strftime("%H:%M:%S", t))
|
||||
file_name = ''.join([current_time, '.png'])
|
||||
screenshot_directory = "screenshots"
|
||||
destination_file = '/'.join([screenshot_directory, file_name])
|
||||
self.driver.save_screenshot(destination_file)
|
||||
self.log.info(f'screenshot saved to {destination_file}')
|
||||
|
||||
def get_element(self, locator, locator_type='xpath'):
|
||||
"""Return found element"""
|
||||
by_type = self._get_by_type(locator_type)
|
||||
|
||||
try:
|
||||
element = self.driver.find_element(by_type, locator)
|
||||
self.log.info(f'Element found. Locator: {locator}, Loctor type: {locator_type}')
|
||||
return element
|
||||
|
||||
except NoSuchElementException as e:
|
||||
print(e)
|
||||
self.log.info(f'Element not found. Locator: {locator}, Loctor type: {locator_type}')
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.log.info(f'Error while locating {locator}. {e}')
|
||||
|
||||
def get_elements(self, locator, locator_type='xpath'):
|
||||
"""Return matching elements"""
|
||||
by_type = self._get_by_type(locator_type)
|
||||
try:
|
||||
elements = self.driver.find_elements(by_type, locator)
|
||||
return elements
|
||||
|
||||
except NoSuchElementException as e:
|
||||
print(e)
|
||||
self.log.info(f'Element not found. Locator: {locator}, Loctor type: {locator_type}')
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.log.info(f'Error while locating {locator}. {e}')
|
||||
|
||||
def scroll_to_element(self, locator, locator_type='xpath'):
|
||||
"""Scroll to matching element"""
|
||||
element = self.get_element(locator, locator_type)
|
||||
if element:
|
||||
self.actions.move_to_element(element).perform()
|
||||
|
||||
def deselct_dropdown(self, locator, locator_type='xpath'):
|
||||
"""deselect all options from that SELECT on the page"""
|
||||
select = Select(self.get_element(locator, locator_type))
|
||||
select.deselect_all()
|
||||
|
||||
def dropdown_select(self,
|
||||
locator,
|
||||
locator_type,
|
||||
by_index=False,
|
||||
by_visible_text=False,
|
||||
by_value=False):
|
||||
|
||||
select = Select(self.get_element(locator, locator_type))
|
||||
if by_index:
|
||||
select.select_by_index(by_index)
|
||||
elif by_visible_text:
|
||||
select.select_by_visible_text(by_visible_text)
|
||||
elif by_value:
|
||||
select.select_by_value(by_value)
|
||||
|
||||
def wait_for_element(self, locator,
|
||||
locator_type='xpath',
|
||||
timeout=10,
|
||||
poll_frequency=0.5):
|
||||
"""Wait to presence of an element"""
|
||||
try:
|
||||
by_type = self._get_by_type(locator_type)
|
||||
|
||||
wait = WebDriverWait(self.driver,
|
||||
timeout,
|
||||
poll_frequency,
|
||||
ignored_exceptions=[NoSuchElementException,
|
||||
ElementNotVisibleException,
|
||||
ElementNotSelectableException])
|
||||
|
||||
element = wait.until(EC.element_to_be_clickable((by_type, locator)))
|
||||
self.log.info(f'Element found. Locator: {locator}, Loctor type: {locator_type}')
|
||||
return element
|
||||
|
||||
except TimeoutException:
|
||||
self.log.info('time out exception')
|
||||
|
||||
except InvalidArgumentException:
|
||||
self.log.info(f'Element not found. Locator: {locator}, Loctor type: {locator_type}')
|
||||
Reference in New Issue
Block a user