From 2fdf403b3915702510bc1a765a4e12a6a5feab9c Mon Sep 17 00:00:00 2001 From: David Megginson Date: Thu, 20 Jul 2023 11:48:26 -0400 Subject: [PATCH] Remove all remaining database code from the HXL Proxy. HXL-79 --- Makefile | 3 + config.py.TEMPLATE | 50 ----- hxl_proxy/admin.py | 110 ----------- hxl_proxy/controllers.py | 2 +- hxl_proxy/dao.py | 357 ----------------------------------- hxl_proxy/recipes.py | 32 +--- hxl_proxy/schema-mysql.sql | 34 ---- hxl_proxy/schema-sqlite3.sql | 25 --- requirements.txt | 5 +- setup.cfg | 5 +- tests/base.py | 6 +- tests/test-data.sql | 9 - tests/test_controllers.py | 2 +- tests/test_dao.py | 112 ----------- tests/test_recipe.py | 5 +- 15 files changed, 14 insertions(+), 743 deletions(-) delete mode 100644 hxl_proxy/admin.py delete mode 100644 hxl_proxy/dao.py delete mode 100644 hxl_proxy/schema-mysql.sql delete mode 100644 hxl_proxy/schema-sqlite3.sql delete mode 100644 tests/test-data.sql delete mode 100644 tests/test_dao.py diff --git a/Makefile b/Makefile index caf37ec2..8ee3ffcb 100644 --- a/Makefile +++ b/Makefile @@ -64,6 +64,9 @@ test-docker: run-local: $(VENV) . $(VENV) && HXL_PROXY_CONFIG=../local/config.py flask --app hxl_proxy run +run-detached: $(VENV) + . $(VENV) && HXL_PROXY_CONFIG=../local/config.py screen -d -m flask --app hxl_proxy run + debug-local: $(VENV) . $(VENV) && HXL_PROXY_CONFIG=../local/config.py flask --debug --app hxl_proxy run diff --git a/config.py.TEMPLATE b/config.py.TEMPLATE index f34e7c49..3df7ce2b 100644 --- a/config.py.TEMPLATE +++ b/config.py.TEMPLATE @@ -115,60 +115,10 @@ ITOS_CACHE_NAME = 'itos-in' # no trailing colon needed ITOS_CACHE_BACKEND = 'memory' ITOS_CACHE_TIMEOUT = 604800 -# -# Database connection info -# - -# 'sqlite3' or 'mysql' -DB_TYPE=os.getenv('DB_TYPE', 'sqlite3') - -# Schema file, for tests. -DB_SCHEMA_FILE = os.getenv('DB_SCHEMA_FILE', 'schema-sqlite3.sql') - -# SQLite3 settings -DB_FILE='/tmp/hxl-proxy.db' - -# MySQL settings -DB_HOST = os.getenv('MYSQL_HOST', 'localhost') -DB_DATABASE = os.getenv('MYSQL_DB', 'hxl_proxy') -DB_PORT = os.getenv('DB_PORT', 3306) -DB_USERNAME = os.getenv('MYSQL_USER', '') -DB_PASSWORD = os.getenv('MYSQL_PASS', '') - # # Values for Google Drive access # GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID', '') GOOGLE_OAUTH_ID = os.getenv('GOOGLE_OAUTH_ID', '') -# -# Values for Humanitarian.ID remote login -# -HID_CLIENT_ID = os.getenv('HID_CLIENT_ID', '') -HID_CLIENT_SECRET = os.getenv('HID_CLIENT_SECRET', '') -HID_REDIRECT_URI = os.getenv('HID_REDIRECT_URI', '') -HID_BASE_URL = os.getenv('HID_BASE_URL', 'https://auth.humanitarian.id') # change to http://auth.dev.humanitarian.id for dev testing - -# -# Where to find shapes, etc. for p-codes. Usually leave as-is. -# -PCODE_BASE_URL = 'https://hxlstandard.github.io/p-codes' - -# -# Countries available for mapping (update as needed). -# -PCODE_COUNTRY_MAP = { - 'Burundi': 'BDI', - 'Cameroon': 'CMR', - 'Chad': 'TCD', - 'Ecuador': 'ECU', - 'Guinea': 'GIN', - 'Haiti': 'HTI', - 'Mali': 'MLI', - 'Nepal': 'NPL', - 'Niger': 'NER', - 'Nigeria': 'NGA', - 'Somalia': 'SOM', -} - # end diff --git a/hxl_proxy/admin.py b/hxl_proxy/admin.py deleted file mode 100644 index ec96a452..00000000 --- a/hxl_proxy/admin.py +++ /dev/null @@ -1,110 +0,0 @@ -""" Admin functions """ - -from hxl_proxy import app -from flask import flash, session -from hxl_proxy import dao, exceptions, util - -import hashlib, json - - - -######################################################################## -# Authentication and authorisation -######################################################################## - -def admin_auth (): - """ Check authorisation for admin tasks - Will redirect to a login page if not authorised. - ADMIN_PASSWORD_MD5 must be set in the config file - """ - if not session.get('is_admin'): - flash("Password required for admin functions") - raise exceptions.RedirectException('/admin/login') - - -def do_admin_login (password): - """ POST action: log into admin functions - @param password: the text of the admin password - """ - expected_passhash = app.config.get('ADMIN_PASSWORD_MD5') - actual_passhash = hashlib.md5(password.encode("utf-8")).hexdigest() - if expected_passhash == actual_passhash: - session['is_admin'] = True - else: - flash("Wrong password") - raise exceptions.Redirect('/admin/login') - - -def do_admin_logout (): - """ POST action: log out of admin functions """ - admin_auth() - session['is_admin'] = False - - - -######################################################################## -# Admin functions for recipes -######################################################################## - -def admin_get_recipes (): - """ Get a list of saved recipes for the admin page - @returns: a list of recipes (just dicts, not Recipe objects) - """ - admin_auth() - recipes = dao.recipes.list() - return recipes - - -def admin_get_recipe (recipe_id): - """ Look up and return a single recipe for the admin page - @param recipe_id: the hash id for the saved recipe - @returns: a Recipe object - """ - admin_auth() - recipe = dao.recipes.read(recipe_id) - return recipe - - -def do_admin_update_recipe (fields): - """ POST action: force-update a saved recipe """ - - admin_auth() - - # original data fields - data_fields = dao.recipes.read(fields['recipe_id']) - - # try parsing the JSON args string - try: - fields['args'] = json.loads(fields['args']) - except: - flash("Parsing error in JSON arguments; restoring old values") - raise exceptions.RedirectException("/admin/recipes/{}/edit.html".format(fields['recipe_id'])) - - # munge the checkbox value - if fields.get('cloneable') == 'on': - if 'authorization_token' in fields['args']: - flash("Cannot make recipe cloneable (contains authorisation token)") - fields['cloneable'] = False - else: - fields['cloneable'] = True - else: - fields['cloneable'] = False - - # see if there's a new password - if fields.get('password'): - flash("Updated recipe password") - fields['passhash'] = util.make_md5(fields['password']) - del fields['password'] - - # copy over the new fields - for key in fields: - data_fields[key] = fields[key] - - # save to the database - dao.recipes.update(data_fields) - - -def do_admin_delete_recipe (recipe_id): - admin_auth() - dao.recipes.delete(recipe_id) - diff --git a/hxl_proxy/controllers.py b/hxl_proxy/controllers.py index ced4533e..e49f2d46 100644 --- a/hxl_proxy/controllers.py +++ b/hxl_proxy/controllers.py @@ -11,7 +11,7 @@ import hxl_proxy from hxl.input import HXLIOException -from hxl_proxy import admin, app, cache, caching, dao, exceptions, filters, pcodes, preview, recipes, util, validate +from hxl_proxy import app, cache, caching, exceptions, filters, pcodes, preview, recipes, util, validate import datetime, flask, hxl, importlib, io, json, logging, requests, requests_cache, signal, werkzeug, csv, urllib diff --git a/hxl_proxy/dao.py b/hxl_proxy/dao.py deleted file mode 100644 index 94308a0a..00000000 --- a/hxl_proxy/dao.py +++ /dev/null @@ -1,357 +0,0 @@ -"""Database access functions. - -This module contains all database dependencies for the HXL Proxy. It -uses three classes as submodules: L{db} for low-level access, L{user} -for managing user records, and L{recipe} for managing recipe -records. L{user} and L{recipe} both have the standard CRUD (create, -read, update, and delete) functions. Example: - - from hxl_proxy import dao - - user = dao.users.read('user@example.org') - user['name_given'] = 'Fred' - dao.users.update(user) - -Database access usually has to happen inside a Flask request context, -and the database connection exists only for the scope of the request; -however, there is a kludge to keep the connection as a local variable -outside a request context for unit testing. - -The name of the database is in the Flask config option C{DB_FILE}. The -SQL schema is in the hxl_proxy module directory as C{schema.sql} (see -that file for the properties of each record type). - -""" - -import sqlite3, mysql.connector, json, os, random, time, base64, hashlib, flask -import hxl_proxy - - -class db: - """Low-level database functions""" - - schema_file = hxl_proxy.app.config.get('DB_SCHEMA_FILE', 'schema-sqlite3.sql') - - TEST_SCHEMA_FILE = os.path.join(os.path.dirname(__file__), schema_file) - """The filename of the SQL schema.""" - - type = hxl_proxy.app.config.get('DB_TYPE', 'sqlite3') - - - _database = None - """Internal connection, for testing use outside a request context.""" - - - @staticmethod - def connect(): - """Get a database connection - - Will reuse the same connection throughout a request - context. Uses the C{DB_FILE} Flask config option for the - location of SQLite3 file. - - @return: a SQLite3 database connection - """ - if flask.has_request_context(): #FIXME - this is an ugly dependency - database = getattr(flask.g, '_database', None) - else: - database = db._database - if database is None: - if db.type == 'sqlite3': - file = hxl_proxy.app.config.get('DB_FILE', ':memory:') - database = sqlite3.dbapi2.connect(file) - database.row_factory = sqlite3.Row - elif db.type == 'mysql': - database = mysql.connector.connect( - host=hxl_proxy.app.config.get('DB_HOST', 'localhost'), - port=hxl_proxy.app.config.get('DB_PORT', 3306), - database=hxl_proxy.app.config.get('DB_DATABASE', 'hxl_proxy'), - user=hxl_proxy.app.config.get('DB_USERNAME'), - password=hxl_proxy.app.config.get('DB_PASSWORD'), - ) - else: - raise Exception('Unknown database type: {}'.format(db.DB_TYPE)) - if flask.has_request_context(): - flask.g._database = database - else: - db._database = database - return database - - @hxl_proxy.app.teardown_appcontext - def close(exception): - """Close the connection at the end of the request.""" - database = getattr(flask.g, '_database', None) - if database is not None: - database.close() - - @staticmethod - def cursor(): - database = db.connect() - if db.type == 'mysql': - return database.cursor(dictionary=True) - else: - return database.cursor() - - @staticmethod - def commit(): - db.connect().commit() - - @staticmethod - def execute_statement(statement, params=(), commit=False): - """Execute a single SQL statement, and optionally commit. - - @param statement: the SQL statement to execute. - @param params: sequence of values for any C{%s} placeholders in the statement. - @param commit: if True, autocommit at the end of the statement (default: False) - @return: a SQLite3 cursor object. - """ - cursor = db.cursor() - cursor.execute(db.fix_params(statement), params) - if commit: - db.commit() - return cursor - - @staticmethod - def execute_script(sql_statements, commit=True): - """Execute a script of statements, and optionally commit. - @param sql_statements: a string containing multiple SQL statements, separated by ';' - @param commit: if True, autocommit after executing the statements (default: True) - @return: a SQLite3 cursor object. - """ - cursor = db.cursor() - if db.type == 'mysql': - for _ in cursor.execute(sql_statements, multi=True): - pass - else: - cursor.executescript(sql_statements) - - if commit: - db.commit() - return cursor - - @staticmethod - def execute_file(filename, commit=True): - """Open a SQL file and execute it as a script. - @param filename: path to a file containing SQL statements, separated by ';' - @param commit: if True, autocommit after executing the statments (default: True) - @return: a SQLite3 cursor object. - """ - with open(filename, 'r') as input: - return db.execute_script(input.read(), commit) - - @staticmethod - def fetchone(statement, params=()): - """Fetch a single row of data. - @param statement: the SQL statement to execute. - @param params: sequence of values for any placeholders in the statement. - @return: a single row as a dict. - @see: L{db.execute_statement} - """ - row = db.execute_statement(statement, params, commit=False).fetchone() - if row: - return dict(row) - else: - return None - - @staticmethod - def fetchall(statement, params=()): - """Fetch a multiple rows of data. - @param statement: the SQL statement to execute. - @param params: sequence of values for any C{%s} placeholders in the statement. - @return: multiple rows as a list of dicts. - @see: L{db.execute_statement} - """ - return [dict(row) for row in db.execute_statement(statement, params, commit=False).fetchall()] - - @staticmethod - def create_db(): - """Create a new database, erasing the current one. - Use this method only for temporary databases in unit testing. Uses L{db.TEST_SCHEMA_FILE} to create - the temporary database. - """ - db.execute_file(db.TEST_SCHEMA_FILE) - - @staticmethod - def fix_params(statement): - if db.type == 'sqlite3': - statement = statement.replace('%s', '?') - return statement - - -class users: - """Functions for managing database records for users.""" - - @staticmethod - def create(user, commit=True): - """Add a new user record and optionally commit. - @param user: a dict of properties for a user. - @param commit: if True, autocommit after adding the user record (default: True). - @return: a SQLite3 cursor object. - """ - return db.execute_statement( - "insert into Users" - " (user_id, email, name, name_given, name_family, last_login)" - " values (%s, %s, %s, %s, %s, %s)", - ( - user.get('user_id'), - user.get('email'), - user.get('name'), - user.get('name_given'), - user.get('name_family'), - time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()), - ), - commit=commit - ) - - @staticmethod - def read(user_id): - """Look up a user record by id. - @param user_id: the user's unique identifier in the database. - @return: a dict of user properties, or None if the record doesn't exist. - """ - return db.fetchone( - 'select * from Users where user_id=%s', - (user_id,) - ) - - @staticmethod - def update(user, commit=True): - """Update an existing user record. - @param user: a dict of user properties, including the C{user_id}. - @param commit: if True, autocommit after updating the user record (default: True). - @return: a SQLite3 cursor object. - """ - return db.execute_statement( - "update Users" - " set email=%s, name=%s, name_given=%s, name_family=%s, last_login=%s" - " where user_id=%s", - ( - user.get('email'), - user.get('name'), - user.get('name_given'), - user.get('name_family'), - time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()), - user.get('user_id'), - ), - commit=commit - ) - - @staticmethod - def delete(user_id, commit=True): - """Delete an existing user record and optionally commit. - @param user_id: the user's unique identifier in the database. - @param commit: if True, autocommit after deleting the user record (default: True). - @return: a SQLite3 cursor object. - """ - return db.execute_statement( - "delete from Users where user_id=%s", - (user_id,), - commit=commit - ) - - -class recipes: - """Database recipe records""" - - @staticmethod - def create(recipe, commit=True): - """Add a new recipe record and optionally commit. - @param recipe: a dict of properties for a recipe. - @param commit: if True, autocommit after adding the recipe record (default: True). - @return: a SQLite3 cursor object. - """ - return db.execute_statement( - "insert into Recipes" - " (recipe_id, passhash, name, description, cloneable, stub, args, date_created, date_modified)" - " values (%s, %s, %s, %s, %s, %s, %s, %s, %s)", - ( - recipe.get('recipe_id'), - recipe.get('passhash'), - recipe.get('name'), - recipe.get('description'), - recipe.get('cloneable'), - recipe.get('stub'), - json.dumps(recipe.get('args', {})), - time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()), - time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()), - ), - commit=commit - ) - - @staticmethod - def read(recipe_id): - """Look up a recipe record by id. - @param recipe_id: the recipe's unique identifier in the database. - @return: a dict of recipe properties, or None if the record doesn't exist. - """ - recipe = db.fetchone( - 'select * from Recipes where recipe_id=%s', - (recipe_id,) - ) - if recipe: - recipe['args'] = json.loads(recipe.get('args')) - return recipe - - @staticmethod - def update(recipe, commit=True): - """Update an existing recipe record. - @param recipe: a dict of recipe properties, including the C{recipe_id}. - @param commit: if True, autocommit after updating the recipe record (default: True). - @return: a SQLite3 cursor object. - """ - return db.execute_statement( - "update Recipes" - " set passhash=%s, name=%s, description=%s, cloneable=%s, stub=%s, args=%s, " - " date_modified=%s" - " where recipe_id=%s", - ( - recipe.get('passhash'), - recipe.get('name'), - recipe.get('description'), - recipe.get('cloneable'), - recipe.get('stub'), - json.dumps(recipe.get('args', {})), - time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime()), - recipe.get('recipe_id'), - ), - commit=commit - ) - - @staticmethod - def delete(recipe_id, commit=True): - """Delete an existing recipe record and optionally commit. - @param recipe_id: the recipe's unique identifier in the database. - @param commit: if True, autocommit after deleting the recipe record (default: True). - @return: a SQLite3 cursor object. - """ - return db.execute_statement( - "delete from Recipes where recipe_id=%s", - (recipe_id,), - commit=commit - ) - - @staticmethod - def list(): - """List all existing recipes, sorted in descending creation-date order""" - return db.fetchall("select * from Recipes order by date_created desc") - - -######################################################################## -# Support functions -######################################################################## - -def gen_recipe_id(): - """ - Generate a pseudo-random, 6-character hash for use as a recipe_id. - """ - salt = str(time.time() * random.random()) - encoded_hash = hxl_proxy.util.make_md5(salt) - return encoded_hash[:6] - -def make_recipe_id(): - """Make a unique recipe_id for a saved recipe.""" - recipe_id = gen_recipe_id() - while hxl_proxy.dao.recipes.read(recipe_id): - recipe_id = gen_recipe_id() - return recipe_id - diff --git a/hxl_proxy/recipes.py b/hxl_proxy/recipes.py index 68e409cc..33d5e0f7 100644 --- a/hxl_proxy/recipes.py +++ b/hxl_proxy/recipes.py @@ -3,7 +3,7 @@ License: Public Domain """ -import flask, hxl_proxy, hxl_proxy.dao, hxl_proxy.filters, logging, werkzeug +import flask, hxl_proxy, hxl_proxy.filters, logging, werkzeug class Recipe: @@ -42,34 +42,8 @@ def __init__(self, recipe_id=None, auth=False, request_args=None): if request_args is None: request_args = flask.request.args - # do we have a saved recipe? if so, then populate from the saved data - if recipe_id is not None: - - # read the recipe from the database - saved_recipe = hxl_proxy.dao.recipes.read(self.recipe_id) - - if not saved_recipe: - raise werkzeug.exceptions.NotFound("No saved recipe for {}".format(recipe_id)) - - # populate the class from the saved recipe dict - self.fromDict(saved_recipe) - - # check if this page requires authorisation - if auth and not self.check_auth(): - raise werkzeug.exceptions.Unauthorized("Wrong or missing password.") - - # allow overrides *only* if we're not using a private dataset - # (not sending an HTTP Authorization: header) - if "authorization_token" not in self.args: - for key in self.RECIPE_OVERRIDES: - if key in request_args: - self.overridden = True - self.args[key] = request_args[key] - - # we don't have a saved recipe: use the HTTP GET parameters - else: - self.args = request_args - self.stub = request_args.get("stub") + self.args = request_args + self.stub = request_args.get("stub") @property diff --git a/hxl_proxy/schema-mysql.sql b/hxl_proxy/schema-mysql.sql deleted file mode 100644 index 1d08df06..00000000 --- a/hxl_proxy/schema-mysql.sql +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Schema to empty and recreate the HXL Proxy database in MySQL. - * - * Warning: will delete any existing data. - * Use this schema only for a fresh installation. - */ - -/** - * Consider uncommenting locally the line below. - * CREATE DATABASE IF NOT EXISTS hxl; -*/ - -DROP TABLE IF EXISTS Users; -CREATE TABLE Users ( - user_id VARCHAR(128) PRIMARY KEY, - email VARCHAR(128) NOT NULL, - name VARCHAR(128) NOT NULL, - name_given VARCHAR(64), - name_family VARCHAR(64), - last_login DATETIME NOT NULL -) DEFAULT charset=utf8; - -DROP TABLE IF EXISTS Recipes; -CREATE TABLE Recipes ( - recipe_id CHAR(6) PRIMARY KEY, - name VARCHAR(128) NOT NULL, - passhash char(32) NOT NULL, - description TEXT, - cloneable BOOLEAN DEFAULT true, - stub VARCHAR(64), - args TEXT NOT NULL, - date_created DATETIME NOT NULL, - date_modified DATETIME NOT NULL -) DEFAULT charset=utf8; diff --git a/hxl_proxy/schema-sqlite3.sql b/hxl_proxy/schema-sqlite3.sql deleted file mode 100644 index dcdb1c3f..00000000 --- a/hxl_proxy/schema-sqlite3.sql +++ /dev/null @@ -1,25 +0,0 @@ -pragma foreign_keys='on'; - -drop table if exists Recipes; -drop table if exists Users; - -create table Users ( - user_id varchar(128) primary key, - email varchar(128) not null, - name varchar(128) not null, - name_given varchar(64), - name_family varchar(64), - last_login datetime not null -); - -create table Recipes ( - recipe_id char(6) primary key, - name varchar(128) not null, - passhash char(32) not null, - description text, - cloneable boolean default true, - stub varchar(64), - args text not null, - date_created datetime not null, - date_modified datetime not null -); diff --git a/requirements.txt b/requirements.txt index cd6a0d29..7daa7453 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,9 +2,8 @@ urllib3<1.27,>1.21.1 # avoid caching bug requests_cache ckanapi>=3.5 flask>=2.2.5<2.3 # 2.3 messes up pip dependencies -mysql-connector-python>=8.0.29 -# git+https://github.com/HXLStandard/libhxl-python.git@dev#egg=libhxl # for development -libhxl==5.0.2 # for release +git+https://github.com/HXLStandard/libhxl-python.git@dev#egg=libhxl # for development +#libhxl==5.0.2 # for release flask-caching redis requests diff --git a/setup.cfg b/setup.cfg index dfc72ca1..f9f94f89 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,9 +25,8 @@ install_requires = requests_cache ckanapi>=3.5 flask>=2.2.5<2.3 # 2.3 messes up pip dependencies - mysql-connector-python>=8.0.29 - # libhxl @ git+https://github.com/HXLStandard/libhxl-python.git@dev # for development - libhxl==5.0.2 # for release + libhxl @ git+https://github.com/HXLStandard/libhxl-python.git@dev # for development + #libhxl==5.0.2 # for release flask-caching redis structlog diff --git a/tests/base.py b/tests/base.py index 961a5bb9..3e63204b 100644 --- a/tests/base.py +++ b/tests/base.py @@ -12,16 +12,12 @@ import hxl_proxy -TEST_DATA = os.path.join(os.path.dirname(__file__), 'test-data.sql') - -class AbstractDBTest(unittest.TestCase): +class AbstractTest(unittest.TestCase): """Base for all HXL Proxy tests that require a database. """ def setUp(self): super().setUp() - hxl_proxy.dao.db.create_db() - hxl_proxy.dao.db.execute_file(TEST_DATA) def tearDown(self): super().tearDown() diff --git a/tests/test-data.sql b/tests/test-data.sql deleted file mode 100644 index 146b9f29..00000000 --- a/tests/test-data.sql +++ /dev/null @@ -1,9 +0,0 @@ -INSERT INTO Users (user_id, email, name, name_given, name_family, last_login) -VALUES -('user1', 'user1@example.org', 'User One', 'User', 'One', '2015-11-11 11:00:00'), -('user2', 'user2@example.org', 'User Two', 'User', 'Two', '2016-11-11 11:00:00'); - -INSERT INTO Recipes (recipe_id, passhash, name, description, cloneable, stub, args, date_created, date_modified) -VALUES -('AAAAA', '5f4dcc3b5aa765d61d8327deb882cf99', 'Recipe #1', 'First test recipe', 1, 'recipe1', '{"url":"http://example.org/basic-dataset.csv"}', '2015-11-11 11:00:00', '2015-11-11 11:00:00'), -('BBBBB', '5f4dcc3b5aa765d61d8327deb882cf99', 'Recipe #2', null, 0, null, '{}', '2016-11-11 11:00:00', '2016-11-11 11:00:00'); diff --git a/tests/test_controllers.py b/tests/test_controllers.py index a89bcbfe..2557932a 100644 --- a/tests/test_controllers.py +++ b/tests/test_controllers.py @@ -24,7 +24,7 @@ # Base class for controller tests ######################################################################## -class AbstractControllerTest(base.AbstractDBTest): +class AbstractControllerTest(base.AbstractTest): """Base class for controller tests.""" def setUp(self): diff --git a/tests/test_dao.py b/tests/test_dao.py deleted file mode 100644 index 5469afda..00000000 --- a/tests/test_dao.py +++ /dev/null @@ -1,112 +0,0 @@ -""" -Unit tests for hxl-proxy dao module -David Megginson -February 2016 - -License: Public Domain -""" - -import unittest, os -from hxl_proxy import app, dao -from . import base - - -class AbstractDAOTest(base.AbstractDBTest): - """Abstract base class for DAO tests.""" - - def setUp(self): - super().setUp() - - def tearDown(self): - super().tearDown() - - def assertEquiv(self, model, actual): - """Test equivalence where everything in model must be the same in actual - (but actual can have extra values).""" - for key in model: - self.assertEqual(model.get(key), actual.get(key), key) - - -class TestUser(AbstractDAOTest): - """Test user DAO functionality""" - - NEW_USER = { - 'user_id': 'user3', - 'email': 'user3@example.org', - 'name': 'User Three', - 'name_given': 'User', - 'name_family': 'Three' - } - - def test_create(self): - dao.users.create(self.NEW_USER) - result = dao.users.read(self.NEW_USER['user_id']) - self.assertEquiv(self.NEW_USER, result) - assert result.get('last_login') is not None - - def test_read(self): - user = { - 'user_id': 'user1', - 'email': 'user1@example.org', - 'name': 'User One', - 'name_given': 'User', - 'name_family': 'One' - } - self.assertEquiv(user, dao.users.read('user1')) - - def test_update(self): - user = dict(self.NEW_USER) - user['user_id'] = 'user1' - dao.users.update(user) - self.assertEquiv(user, dao.users.read(user['user_id'])) - - def test_delete(self): - dao.users.create(self.NEW_USER) - assert dao.users.read(self.NEW_USER['user_id']) is not None - dao.users.delete(self.NEW_USER['user_id']) - assert dao.users.read(self.NEW_USER['user_id']) is None - - -class TestRecipe(AbstractDAOTest): - - NEW_RECIPE = { - 'recipe_id': 'XXXXX', - 'passhash': '5f4dcc3b5aa765d61d8327deb882cf99', - 'name': 'Recipe X', - 'description': 'New test recipe', - 'cloneable': 1, - 'stub': 'recipex', - 'args': {} - } - - def test_create(self): - dao.recipes.create(self.NEW_RECIPE) - result = dao.recipes.read(self.NEW_RECIPE['recipe_id']) - self.assertEquiv(self.NEW_RECIPE, result) - assert result['date_created'] - self.assertEqual(result['date_created'], result['date_modified']) - - def test_read(self): - recipe = { - 'recipe_id': 'AAAAA', - 'passhash': '5f4dcc3b5aa765d61d8327deb882cf99', - 'name': 'Recipe #1', - 'description': 'First test recipe', - 'cloneable': 1, - 'stub': 'recipe1', - 'args': {'url':'http://example.org/basic-dataset.csv'} - } - self.assertEquiv(recipe, dao.recipes.read(recipe['recipe_id'])) - - def test_update(self): - recipe = dict(self.NEW_RECIPE) - recipe['recipe_id'] = 'AAAAA' - dao.recipes.update(recipe) - result = dao.recipes.read('AAAAA') - self.assertEquiv(recipe, result) - self.assertNotEqual(result['date_created'], result['date_modified']) - - def test_delete(self): - assert dao.recipes.read('AAAAA') is not None - dao.recipes.delete('AAAAA') - assert dao.recipes.read('AAAAA') is None diff --git a/tests/test_recipe.py b/tests/test_recipe.py index 195576ed..14ff440e 100644 --- a/tests/test_recipe.py +++ b/tests/test_recipe.py @@ -11,7 +11,7 @@ from . import base -class TestConstructor(base.AbstractDBTest): +class TestConstructor(base.AbstractTest): def setUp(self): super().setUp() @@ -28,6 +28,3 @@ def test_params(self): self.assertEqual(args["url"], recipe.url) self.assertTrue(recipe.recipe_id is None) - def test_saved(self): - recipe_id = 'AAAAA' - recipe = Recipe(recipe_id=recipe_id, request_args={})