Skip to content
Snippets Groups Projects
Commit 66c46fd9 authored by William Bell's avatar William Bell
Browse files

Updates

parent 426a9f43
No related branches found
No related tags found
No related merge requests found
No preview for this file type
......@@ -65,7 +65,7 @@
Azure allows Web Apps to be deployed as source code that is hosted in a container or as a container. In both cases, it is often necessary to store the state of a web service in an associated database. To benefit from additional support and elasticity, a cloud hosted database service is used. The web service connects to the associated database using a database connection string. To improve the security of the database service, transport layer security (TLS) is normally required.
In these exercises, a simple web service is interfaced with the MariaDB for Azure database service. The exercises present a set of steps that could be used to debug connection issues between the web service and the database.
In these exercises, a simple web service is interfaced with the Azure MySQL flexible database server. The exercises present a set of steps that could be used to debug connection issues between the web service and the database.
\section{Typesetting and spaces}
......@@ -75,94 +75,66 @@ A fixed width font is used for commands or source code. To aid the reader, a sm
command argument
\end{lstlisting}
\section{Dependencies}
\subsection{Azure command-line interface}
The Azure CLI should be installed by following the instructions that are given at \href{https://docs.microsoft.com/en-us/cli/azure/install-azure-cli}{https://docs.microsoft.com/en-us/cli/azure/install-azure-cli}.
\subsection{Bash}
This document discusses the examples using the Bash shell. The Bash shell environment is normally part of a Linux or OSX installation. Bash is not installed on Microsoft Windows by default, but is available on Linux and OSX. Bash can be installed on Windows by installing Git for Windows from \href{https://gitforwindows.org/}{https://gitforwindows.org/}.
\subsection{MariaDB client}
The MariaDB client should be installed, such that the command \texttt{mysql} or \texttt{mariadb} is in the PATH environment variable. The MariaDB client can be downloaded from \href{https://mariadb.com/kb/en/binary-packages/}{https://mariadb.com/kb/en/binary-packages/}.
After the MariaDB client has been installed, open a command prompt and type the command given in Listing~\ref{listing:mysql-path-cmd} or type Listing~\ref{listing:mysql-path-bash} into a Bash window. If successful, the file path to the \texttt{mysql} executable should be returned.
These exercises require the installation of:
\begin{itemize}
\item Git Bash.
\item MySQL, where only the client is needed.
\item Postman.
\item Python 3.11, where python and pip should be in the \texttt{PATH}.
\item Azure command-line client.
\item Visual Studio Code.
\item Visual Studio Code ``Python'' extension, with Extension ID ``ms-python.python''.
\end{itemize}
\begin{lstlisting}[caption={Verifying if mysql is in the PATH environment variable, using a Windows command prompt.},label=listing:mysql-path-cmd,numbers=none,language=,showspaces=true]
where mysql
\end{lstlisting}
\section{Cloning the exercises \label{section:clone}}
\begin{itemize}
\item Open a Git Bash shell window.
\item Clone the repository that contains the exercises by typing the command that is given in Listing~\ref{listing:git-clone} on one line.
\begin{lstlisting}[caption={Verifying if mysql is in the PATH environment variable, using a Bash shell.},label=listing:mysql-path-bash,numbers=none,language=,showspaces=true]
which mysql
\begin{lstlisting}[caption={Cloning the exercises.},label=listing:git-clone,numbers=none,language=Bash,showspaces=true]
git clone https://gitlab.cis.strath.ac.uk/gxb20157/azure-stateful-webapp.git
\end{lstlisting}
\end{itemize}
\subsection{Python packages}
The examples have been written and tested with Python 3. The examples require additional Python packages to be installed. These packages can be installed using \texttt{pip} or \texttt{pip3}, depending on the Python~3 installation. The command to install the packages is given in Listing~\ref{listing:pip}, where the \texttt{requirements.txt} file is provided in this repository in the \texttt{src} directory.
\section{Installing Python dependencies}
\begin{itemize}
\item Open a command-line window, Bash terminal, or Visual Studio Code session in the \texttt{src} directory.
\item Type the command that is given in Listing~\ref{listing:pip} to install the Python dependencies.
\begin{lstlisting}[caption={Installing Python package dependencies.},label=listing:pip,numbers=none,language=Bash,showspaces=true]
pip install -r requirements.txt
\end{lstlisting}
\end{itemize}
\section{Student account}
These exercises can be completed using the Microsoft student account or by using the teaching environment. To create a student account:
These exercises use the Microsoft Student account. To create a student account:
\begin{enumerate}
\item Use a web browser to access \href{https://azure.microsoft.com/en-us/free/students/}{https://azure.microsoft.com/en-us/free/students/} and create a free student account. The free account should be associated with your university email address.
\item Use a web browser to access the Azure portal at \href{https://portal.azure.com/}{https://portal.azure.com/} and login with your university email address.
\end{enumerate}
The exercises use an App Service Plan to host the Web App and a MariaDB database.
{\bf The MariaDB server is paid service.} While this service is running, a small amount of money will be deducted from the starting credit of the student account. Therefore, the server should be deleted when these exercises have been completed.
The portal can be used to create, view and delete resources that are associated with a subscription.
If the student account is used to complete these exercises, the App Service Plan should use the \texttt{Free} SKU. If the teaching subscription is used, the teaching environment App Service Plan should be used to host the Web App.
{\bf The exercises use a MySQL flexible server, which is paid service.} While this service is running, a small amount of money may be deducted from the starting credit of the student account. Therefore, the server should be deleted when these exercises have been completed.
If the student account is used to complete these exercises, the App Service Plan should use the \texttt{F1} SKU. If the teaching subscription is used, the teaching environment App Service Plan should be used to host the Web App.
\section{Teaching environment}
The CIS Azure teaching environment provides shared services and personalised access to allow students to deploy Web Apps and Functions. The teaching environment is described in a separate document ``Azure teaching environment: user guide'', which is made available to students through a MyPlace link.
\section{Testing the web service}
If these exercises are followed using an Azure student account, then a MariaDB database should be created and deleted once the exercises have been completed. If these exercises are followed using the Azure teaching environment, then the existing MariaDB database can be used.
\subsection{Accessing the database}
\begin{enumerate}
\item Follow the instructions at \href{https://docs.microsoft.com/en-us/azure/mysql/howto-configure-ssl}{https://docs.microsoft.com/en-us/azure/mysql/howto-configure-ssl} to download the \texttt{DigiCertGlobalRootG2.crt.pem} file and save it into the \texttt{src} directory.
\item Set the environment variables for either the student account or teaching environment:
\begin{itemize}
\item \texttt{DB\_SERVER} - The full database server name, ending in \texttt{.mariadb.database.azure.com}.
\item \texttt{DB\_USER} - The MariaDB user name.
\item \texttt{DB\_PASSWORD} - The MariaDB password.
\item \texttt{DB\_NAME} - The database name. (This is the same as the \texttt{DB\_USER}, when using the teaching environment.)
\end{itemize}
\item Open a command prompt or Bash shell window in the \texttt{src} directory. Then connect to the remote MariaDB database server using the command given in Listing~\ref{listing:mysql-connect-ssl}. If this command is typed into a Bash shell on Windows, then it must be prefixed by \texttt{winpty}.
\item Use PowerShell to connect to the remote MySQL database server using the command given in Listing~\ref{listing:mysql-connect}, where \texttt{db\_server}, \texttt{db\_user} and \texttt{db\_password} are set as needed to connect to the Azure MySQL flexible server.
\clearpage
\begin{lstlisting}[caption={Connecting to the remote MariaDB server.},label=listing:mysql-connect-ssl,numbers=none,language=,showspaces=true]
mysql --host $DB_SERVER \
--user $DB_USER -p$DB_PASSWORD --ssl \
--tls-version="TLSv1.2,TLSv1.3" $DB_NAME
\begin{lstlisting}[caption={Connecting to the remote MySQL server, where PowerShell line continuation characters have been used.},label=listing:mysql-connect,numbers=none,language=,showspaces=true]
mysql --host "$db_server" `
--user "$db_user" --password=$db_password --ssl-mode=REQUIRED
\end{lstlisting}
%\begin{lstlisting}[caption={Connecting to the remote MariaDB server.},label=listing:mysql-%connect,numbers=none,language=,showspaces=true]
%mysql --host $DB_SERVER \
%--user $DB_USER -p$DB_PASSWORD --ssl \
%--ssl-verify-server-cert --ssl-ca=BaltimoreCyberTrustRoot.crt.pem \
%--tls-version="TLSv1.2,TLSv1.3" $DB_NAME
%\end{lstlisting}
%If the command given in Listing~\ref{listing:mysql-connect} fails with the error that is given in Listing~\ref{listing:ssl-error}, run the command that is given in Listing~\ref{listing:mysql-connect-ssl} instead. This error can occur if the%\texttt{BaltimoreCyberTrustRoot.crt.pem} is already installed on the host computer, such that it clashes with the version provided locally.
%\begin{lstlisting}[caption={Certificate chain error.},label=listing:ssl-error,numbers=none,language=,showspaces=true]
%ERROR 2026 (HY000): SSL connection error: Server certificate validation failed. A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider.
%\end{lstlisting}
\item Check the tables present and remove any existing tables by using Listing~\ref{listing:list-tables} and \ref{listing:db-reset}, respectively. The name \texttt{db\_name} should be replaced with the database name.
\begin{lstlisting}[caption={Listing the tables and columns.},label=listing:list-tables,numbers=none,language=,showspaces=true]
......@@ -170,7 +142,7 @@ SHOW TABLES;
SHOW COLUMNS IN book;
\end{lstlisting}
\begin{lstlisting}[caption={Creating an empty database, where \texttt{db\_user} should be replaced by the database user name.},label=listing:db-reset,numbers=none,language=,showspaces=true]
\begin{lstlisting}[caption={Creating an empty database, where \texttt{db\_user} should be replaced as needed.},label=listing:db-reset,numbers=none,language=,showspaces=true]
DROP DATABASE IF EXISTS db_name;
CREATE DATABASE db_name;
\end{lstlisting}
......@@ -181,94 +153,91 @@ CREATE DATABASE db_name;
It is easier to debug a program when it is running on the local computer, since the log messages may be clearer, the edit and update loop is quicker and a debugger can be used.
\begin{enumerate}
\item Follow the instructions at \href{https://learn.microsoft.com/en-us/azure/mysql/flexible-server/how-to-connect-tls-ssl}{https://learn.microsoft.com/en-us/azure/mysql/flexible-server/how-to-connect-tls-ssl} to download the \texttt{DigiCertGlobalRootCA.crt.pem} file and save it into the \texttt{src} directory. The certificate file can be downloaded using the PowerShell command that is given in Listing~\ref{listing:cert-download}.
\begin{lstlisting}[caption={Downloading DigiCertGlobalRootCA.crt.pem, where PowerShell back tick line continuation characters have been used. },label=listing:cert-download,numbers=none,language=,showspaces=true]
Invoke-WebRequest `
'https://dl.cacerts.digicert.com/DigiCertGlobalRootCA.crt.pem' `
-OutFile DigiCertGlobalRootCA.crt.pem
\end{lstlisting}
\item Use Visual Studio Code to open the \texttt{azure-stateful-webapp/src} folder.
\item Open the \texttt{app.py} file.
\item Run the file \texttt{app.py}. This will fail, since there is no local database server.
\item Set the environment variables within the \texttt{TERMINAL} window:
\item Click on ``Run'', select ``Add Configuration...''.
\item Select ``Python Debugger'' and ``Python File''.
\item Create a new configuration key named ``env'' within the configuration in the new \texttt{launch.json} file as given in Listing~\ref{listing:launch-json}, where \texttt{db\_server}, \texttt{db\_user} and \texttt{db\_password} should be replaced as needed to connect to the Azure MySQL flexible server.
\begin{lstlisting}[caption={Configuration settings that should be added to \texttt{launch.json}. },label=listing:launch-json,numbers=none,language=,showspaces=true]
"env": {
"DB_SERVER": "db_server",
"DB_USER": "db_user",
"DB_PASSWORD": "db_password",
"DB_NAME": "db_name",
"DB_SSL_CERT": "DigiCertGlobalRootG2.crt.pem"
}
\end{lstlisting}
\begin{itemize}
\item \texttt{DB\_SERVER} is the full name of the remote database server, ending in ``mariadb.database.azure.com'' without the quotations.
\item \texttt{DB\_USER} is the user name for the remote database server.
\item \texttt{DB\_PASSWORD} is the password for the remote database server.
\item \texttt{DB\_NAME} is the name of the database within the remote database server.
\item \texttt{DB\_SSL\_CERT} should be set to \texttt{DigiCertGlobalRootG2.crt.pem}.
\end{itemize}
\item Run the file \texttt{app.py}. This will fail, since there is no local database server.
\item Run the file \texttt{app.py}.
\item Verify the end points work as expected by using Listing~\ref{listing:web-service-test} or Postman.
\begin{lstlisting}[caption={Testing the web service.},label=listing:web-service-test,numbers=none,language=,showspaces=true]
curl -s -X GET "http://127.0.0.1:5000/books/"
curl -s -X POST -H "Content-Type: application/json" \
-d '{"title": "Ulysses", "author": "James Joyce"}' \
"http://127.0.0.1:5000/books/"
curl -s -X GET "http://127.0.0.1:5000/books/"
\end{lstlisting}
\item Use Postman to verify that the end points work as expected.
\item Stop the web service by typing \texttt{ctrl-c} in the TERMINAL window.
\item Verify the database table structure using the MariaDB client and Listing~\ref{listing:list-tables}.
\item Verify the database table structure using the \texttt{mysql} client and Listing~\ref{listing:list-tables}.
\end{enumerate}
\subsection{Deploying the web service}
\begin{enumerate}
\item Create a zip file that contains the source code files, configuration and the certification, using Listing~\ref{listing:zip-files}.
\item Open a PowerShell window in the \texttt{azure-stateful-webapp/src} folder.
\begin{lstlisting}[caption={Creating a zip file, ready for deployment.},label=listing:zip-files,numbers=none,language=,showspaces=true]
zip -r zip myapp.zip app.py models.py requirements.txt routes.py \
startup.txt DigiCertGlobalRootG2.crt.pem
\end{lstlisting}
\item Create a Web App by using Listing~\ref{listing:create-web-app}, where the \texttt{plan\_id} should be replaced with the id of the existing plan on the teaching environment. Alternatively, a plan can be created in the student account. Following the Azure resource naming convention, the \texttt{app\_name} should start with the prefix ``app-''.
\item Type the commands given in Listing~\ref{listing:cli-create-webapp}, replacing the app name as needed. The \texttt{app\_name} must create a unique URL. The \texttt{startup.txt} file is used to provide the Web App with a customised start-up command.
\begin{lstlisting}[caption={Creating a Web App.},label=listing:create-web-app,numbers=none,language=,showspaces=true]
az webapp create --name $app_name \
--plan $plan_id \
--resource-group $resource_group \
--runtime "python:3.9" \
--startup-file startup.txt
\end{lstlisting}
\lstinputlisting[caption={Creating a Web App, where PowerShell back tick line continuation characters have been used.},label=listing:cli-create-webapp,language=,showspaces=true]{../scripts/create-web-app.ps1}
\item Configure the Web App by using Listing~\ref{listing:web-app-config}.
\item Configure the Web App using Listing~\ref{listing:web-app-config}.
\clearpage
\begin{lstlisting}[caption={Configuring the Web App, with the database connection details and build flag.},label=listing:web-app-config,numbers=none,language=,showspaces=true]
az webapp config appsettings set --resource-group $resource_group \
--name $app_name --settings DB_SERVER=$DB_SERVER DB_NAME=$DB_NAME \
DB_USER=$DB_USER DB_PASSWORD=$DB_PASSWORD \
DB_SSL_CERT=DigiCertGlobalRootG2.crt.pem \
az webapp config appsettings set --resource-group $resource_group `
--name $app_name --settings DB_SERVER=$db_server DB_NAME=$db_name `
DB_USER=$db_user DB_PASSWORD=$db_password `
DB_SSL_CERT=DigiCertGlobalRootG2.crt.pem `
SCM_DO_BUILD_DURING_DEPLOYMENT=true
\end{lstlisting}
\item Follow the log of the Web App by using Listing~\ref{listing:web-app-log}.
\item Follow the log of the Web App using Listing~\ref{listing:web-app-log}.
\begin{lstlisting}[caption={Configuring the Web App, with the database connection details and build flag.},label=listing:web-app-log,numbers=none,language=,showspaces=true]
az webapp log tail --name $app_name --resource-group $resource_group
\end{lstlisting}
\item Deploy the Web App by using Listing~\ref{listing:web-app-deploy}.
\item Type the command given in Listing~\ref{listing:zip} to create a zip file.
\begin{lstlisting}[caption={Deploying the Web App.},label=listing:web-app-deploy,numbers=none,language=,showspaces=true]
az webapp deployment source config-zip --resource-group $resource_group \
--name $app_name --src ./myapp.zip
\begin{lstlisting}[caption={Creating a zip file that contains the code.},label=listing:zip,numbers=none,language=,showspaces=true]]
Compress-Archive -Path .\app.py, .\models.py, .\requirements.txt, .\routes.py, .\startup.txt, .\DigiCertGlobalRootG2.crt.pem `
-DestinationPath app.zip
\end{lstlisting}
\item Test the Web App by using Listing~\ref{listing:web-app-test}.
\item Deploy the web service by typing the commands that are given in Listing~\ref{listing:deploy}.
\begin{lstlisting}[caption={Testing the Web App.},label=listing:web-app-test,numbers=none,language=,showspaces=true]
curl -s -X GET "https://$app_name.azurewebsites.net/books/"
curl -s -X POST -H "Content-Type: application/json" \
-d '{"title": "Ulysses", "author": "James Joyce"}' \
"https://$app_name.azurewebsites.net/books/"
curl -s -X GET "https://$app_name.azurewebsites.net/books/"
\begin{lstlisting}[caption={Commands to package and deploy the web service.},label=listing:deploy,numbers=none,language=bash,showspaces=true]]
Compress-Archive -Path .\app.py, .\models.py, `
.\requirements.txt, .\routes.py, .\startup.txt, `
.\DigiCertGlobalRootG2.crt.pem -DestinationPath app.zip
az webapp deploy --name $app_name `
--resource-group $resource_group `
--src-path ./app.zip
\end{lstlisting}
\item Test the Web App using Postman.
\item Delete the Web App by using Listing~\ref{listing:web-app-delete}.
\item Delete the Web App using Listing~\ref{listing:web-app-delete}.
\begin{lstlisting}[caption={Deleting the Web App.},label=listing:web-app-delete,numbers=none,language=,showspaces=true]
az webapp delete --name $app_name --resource-group $resource_group
\end{lstlisting}
\item Remove the database tables using the MariaDB client and Listing~\ref{listing:db-reset}.
\item Remove the database tables using the \texttt{mysql} client and Listing~\ref{listing:db-reset}.
\end{enumerate}
......
Package,{Package Type},Version
MariaDB Client,Native,10.11.6
Python,Native,3.11.7
azure-cli,Native,2.56.0
MySQL,Native,8.0.40
Python,Native,3.11.9
{Azure CLI},Python,2.67.0
Flask,Python,3.0.2
Flask-SQLAlchemy,Python,3.1.1
PyMSQL,Python,1.1.0
SQLAlchemy,Python,2.0.25
\ No newline at end of file
PyMySQL,Python,1.1.1
\ No newline at end of file
......@@ -2,6 +2,7 @@ import os
import sqlalchemy
from flask import Flask
from models import db
from routes import books_bp
def database_uri() -> sqlalchemy.engine.url.URL:
......@@ -16,17 +17,13 @@ def database_uri() -> sqlalchemy.engine.url.URL:
return url
# Create a new DB connection string from components.
host=os.environ.get("DB_SERVER", default="127.0.0.1")
username = os.environ.get("DB_USER", default="none")
username = f"{username}@{host}"
url = sqlalchemy.engine.url.URL.create(
drivername=os.environ.get("DB_DRIVER", default="mysql+pymysql"),
username=username,
password=os.environ.get("DB_PASSWORD", default="none"),
host=host,
port=os.environ.get("DB_PORT", default="3306"),
database=os.environ.get("DB_NAME", default="mydb"))
drivername = os.environ.get("DB_DRIVER", default="mysql+pymysql"),
username = os.environ.get("DB_USER", default="none"),
password = os.environ.get("DB_PASSWORD", default="none"),
host = os.environ.get("DB_SERVER", default="127.0.0.1"),
port = os.environ.get("DB_PORT", default="3306"),
database = os.environ.get("DB_NAME", default="mydb"))
return url
......@@ -48,30 +45,36 @@ def database_connection_args() -> dict:
return connection_args
def create_app(test_config: dict = {}):
def create_app(test_config:dict = None) -> Flask:
"""
A factory function to create a Flask app.
A factory function to create Flask apps.
"""
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = database_uri()
if test_config is None:
test_config = {}
flask_app = Flask(__name__)
flask_app.config['SQLALCHEMY_DATABASE_URI'] = database_uri()
connection_args = database_connection_args()
if len(connection_args) > 0:
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {"connect_args": connection_args}
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
flask_app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {"connect_args": connection_args}
flask_app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
if len(test_config) > 0:
app.config.update(test_config)
with app.app_context():
db.init_app(app)
flask_app.config.update(test_config)
with flask_app.app_context():
db.init_app(flask_app)
db.create_all()
from routes import books_bp
app.register_blueprint(books_bp)
return app
flask_app.register_blueprint(books_bp)
return flask_app
if __name__ == '__main__':
def main() -> None:
"""
A test program to create one running Flask app.
A function to create an instance of the app and run it.
"""
app = create_app()
flask_app = create_app()
svc_host = os.environ.get("SVC_HOST", default=None)
app.run(debug=True, host=svc_host)
print(svc_host)
flask_app.run(debug=True, host=svc_host)
if __name__ == '__main__':
main()
"""
A data model classes.
"""
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
......
"""
URL routes that are associated with the web service.
"""
from flask import Blueprint, make_response, jsonify, request
from models import db
import models
......@@ -8,15 +11,15 @@ books_bp = Blueprint('books', __name__, url_prefix='/books')
@books_bp.route('/', methods=['GET', 'POST'])
def books():
if request.method == 'GET':
return jsonify([book.to_dict() for book in models.Book.query.all()])
return jsonify([obj.to_dict() for obj in models.Book.query.all()])
if request.method == 'POST':
# Try to create a new book.
try:
book = models.Book(title=request.json["title"],
obj = models.Book(title=request.json["title"],
author=request.json["author"])
db.session.add(book)
db.session.add(obj)
db.session.commit()
return make_response(jsonify({"id": book.id}), 201)
return make_response(jsonify({"id": obj.id}), 201)
except TypeError:
return make_response(jsonify("No JSON supplied."), 500)
except KeyError:
......@@ -28,14 +31,14 @@ def books():
@books_bp.route('/<int:book_id>', methods=['GET', 'PUT', 'DELETE'])
def book(book_id):
book = models.Book.query.filter_by(id=book_id).first()
if book is None:
obj = models.Book.query.filter_by(id=book_id).first()
if obj is None:
return make_response(jsonify("Index not found."), 500)
if request.method == 'GET':
return jsonify(book.to_dict())
return jsonify(obj.to_dict())
if request.method == 'PUT':
try:
book.from_dict(request.json)
obj.from_dict(request.json)
db.session.commit()
return make_response(jsonify({}), 200)
except TypeError:
......@@ -46,7 +49,7 @@ def book(book_id):
return make_response(jsonify("Internal server error."), 500)
if request.method == 'DELETE':
try:
db.session.delete(book)
db.session.delete(obj)
db.session.commit()
return make_response(jsonify({}), 200)
except Exception:
......
from app import create_app, db
import json
import models
import unittest
import models
from app import create_app, db
class TestApp(unittest.TestCase):
......@@ -29,24 +29,24 @@ class TestApp(unittest.TestCase):
db.session.commit()
def test_get_books(self):
book = models.Book(title="Alice's Adventures in Wonderland", author="Lewis Carroll")
db.session.add(book)
obj = models.Book(title="Alice's Adventures in Wonderland", author="Lewis Carroll")
db.session.add(obj)
db.session.commit()
rv = self.client.get('/books/')
json_data = json.loads(rv.data)
self.assertEqual(len(json_data), 1)
d = book.to_dict()
d = obj.to_dict()
self.assertEqual(d['title'], json_data[0]['title'])
self.assertEqual(d['author'], json_data[0]['author'])
self.assertEqual(rv.status_code, 200)
def test_get_book(self):
book = models.Book(title="Alice's Adventures in Wonderland", author="Lewis Carroll")
db.session.add(book)
obj = models.Book(title="Alice's Adventures in Wonderland", author="Lewis Carroll")
db.session.add(obj)
db.session.commit()
rv = self.client.get('/books/1')
json_data = json.loads(rv.data)
d = book.to_dict()
d = obj.to_dict()
self.assertEqual(d['title'], json_data['title'])
self.assertEqual(d['author'], json_data['author'])
self.assertEqual(rv.status_code, 200)
......@@ -54,15 +54,15 @@ class TestApp(unittest.TestCase):
self.assertEqual(rv.status_code, 500)
def test_put_book(self):
book = models.Book(title="Alice's Adventures in Wonderland", author="Lewis Carroll")
db.session.add(book)
obj = models.Book(title="Alice's Adventures in Wonderland", author="Lewis Carroll")
db.session.add(obj)
db.session.commit()
new_book = models.Book(title="The Hitchhiker's Guide to the Galaxy", author="Douglas Adams")
rv = self.client.put('/books/1', json=new_book.to_dict())
self.assertEqual(rv.status_code, 200)
book = models.Book.query.filter_by(id=book.id).first()
self.assertEqual(book.title, new_book.title)
self.assertEqual(book.author, new_book.author)
obj = models.Book.query.filter_by(id=obj.id).first()
self.assertEqual(obj.title, new_book.title)
self.assertEqual(obj.author, new_book.author)
rv = self.client.put('/books/2', json=new_book.to_dict())
self.assertEqual(rv.status_code, 500)
......@@ -70,13 +70,13 @@ class TestApp(unittest.TestCase):
new_book = models.Book(title="The Hitchhiker's Guide to the Galaxy", author="Douglas Adams")
rv = self.client.post('/books/', json=new_book.to_dict())
self.assertEqual(rv.status_code, 201)
book = models.Book.query.first()
self.assertEqual(book.title, new_book.title)
self.assertEqual(book.author, new_book.author)
obj = models.Book.query.first()
self.assertEqual(obj.title, new_book.title)
self.assertEqual(obj.author, new_book.author)
def test_delete_book(self):
book = models.Book(title="Alice's Adventures in Wonderland", author="Lewis Carroll")
db.session.add(book)
obj = models.Book(title="Alice's Adventures in Wonderland", author="Lewis Carroll")
db.session.add(obj)
db.session.commit()
rv = self.client.delete('/books/1')
self.assertEqual(rv.status_code, 200)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment