diff --git a/tex/part-practical.tex b/tex/part-practical.tex index 8c25193..7c3e690 100644 --- a/tex/part-practical.tex +++ b/tex/part-practical.tex @@ -1105,51 +1105,58 @@ then after pushing to remote in the CI. \n{3}{func TestUserExists(t *testing.T)} An example integration test shown in Listing~\ref{integrationtest} can be seen -to declare a helper function \texttt{getCtx() context.Context}, which takes no -arguments and returns a new\\ \texttt{context.Context} initialised with a value -of the global logger, which is how the logger gets injected into the user -module functions. The function \texttt{TestUserExists(t *testing.T)} first -declares a database connection string and attempting to open a connection to -the database. The database in use here is SQLite3 running in memory mode, -meaning no file is actually written to disk during this process. Since the -testing data is not needed after the test, this is deemed good enough. Next, a -defer statement calling the \texttt{Close()} method on the database object is -made, which is the idiomatic Go way of closing files and network connections -(which are also an abstraction over files on UNIX-like operating systems such -as GNU/Linux). The \emph{defer} statement gets called after all of the +at line 10 to declare a helper function \texttt{getCtx() context.Context}, +which takes no arguments and returns a new\\ \texttt{context.Context} +initialised with the value of the global logger, which is how the logger gets +injected into the user module functions. The actual test function with the +signature \texttt{TestUserExists(t *testing.T)} defines a database connection +string at line 21 and attempts to open a connection to the database. The +database in use here is SQLite3 running in memory mode, meaning no file is +actually written to disk during this process. Since the testing data is not +needed after the test, this is desirable. Next, a defer statement calls the +\texttt{Close()} method on the database object , which is the Go idiomatic way +of closing files and network connections (which are also an abstraction over +files on UNIX-like operating systems such as GNU/Linux). Contrary to where it +is declared, the \emph{defer} statement is onlycalled after all of the statements in the surrounding function, which makes sure no file descriptors (FDs) are leaked and the file is properly closed when the function returns. -In the next step a database schema creation is attempted, handling the -potential error in a Go idiomatic way, which uses the return value from the +In the next step at line 25 a database schema creation is attempted, handling +the potential error in a Go idiomatic way, which uses the return value from the function in an assignment to a variable declared in the \texttt{if} statement, and checks whether the \texttt{err} was \texttt{nil} or not. In case the \texttt{err} was not \texttt{nil}, i.e.\ \emph{there was an error in the callee function}, the condition evaluates to \texttt{true}, which is followed by -entering the inner block. Inside the inner block, the error is announced to the -user (likely a developer running the test in this case) and the testing -object's \texttt{FailNow()} method is called, which marks the test function as -having failed, and thus stops its execution. In this case, that is the desired +entering the inner block. Inside it, the error is announced to the user (likely +a developer running the test in this case) and the testing object's +\texttt{FailNow()} method is called. That marks the test function as having +failed, and thus stops its execution. In this case, that is the desired outcome, since if the database schema creation call fails, there really is no -point in continuing testing of user creation. - -Conversely, if the schema does get created without an error, the code continues -to declare a few variables: \texttt{username}, \texttt{email} and \texttt{ctx}, -where the context injected with the logger is saved. Some of them are -subsequently passed into the \texttt{UsernameExists} function, \texttt{ctx} as -the first argument, with the database pointer and username being passed next, -while the \texttt{email} variable is only used at a later stage, but was -declared here to give a sense of grouping. The error value returned from this -function is again checked and if everything goes well, the value of the -\texttt{usernameFound} boolean is checked next. +point in continuing the testing of user creation. +\\ +Conversely, if the schema \emph{does} get created without an error, the code +continues to declare a few variables (lines 30-32): \texttt{username}, +\texttt{email} and \texttt{ctx}, where the context injected with the logger is +saved. Two of them are subsequently (line 33) passed into the +\texttt{UsernameExists} function, \texttt{ctx} being the first argument and the +database pointer and \texttt{username} following, while the \texttt{email} +variable is only used at a later stage (line 46). The point of declaring them +together is to give a sense of relatedness. The error value returned from this +function is again checked (line 33) and if everything goes well, the +\texttt{usernameFound} boolean value is checked next at line 38. \smallskip \smallskip \begin{lstlisting}[language=Go, caption={Example integration test.}, label=integrationtest,basicstyle=\linespread{0.9}\scriptsize\ttfamily, -% numbers=left, numberstyle=\tiny\ttfamily\color{codegray}, backgroundcolor=\color{lstbg}, -morekeywords=any +numbers=left, +numberstyle=\linespread{0.9}\scriptsize\ttfamily, +frame=l, +framesep=18.5pt, +framerule=0.1pt, +xleftmargin=18.7pt, +otherkeywords={\%s, \%q, \%v}, ] // modules/user/user_test.go package user @@ -1166,7 +1173,6 @@ import ( func getCtx() context.Context { l := slogging.Init(false) ctx := context.WithValue(context.Background(), CtxKey{}, l) - return ctx } @@ -1186,22 +1192,15 @@ func TestUserExists(t *testing.T) { usernameFound, err := UsernameExists(ctx, db, username) if err != nil { - t.Errorf("error checking for username {%s} existence: %q", - username, - err, - ) + t.Errorf("error checking for username {%s} existence: %q", username, err) } if usernameFound { - t.Errorf("unexpected: user{%s} should not have been found", - username, - ) + t.Errorf("unexpected: user{%s} should not have been found", username) } if _, err := EmailExists(ctx, db, email); err != nil { - t.Errorf("unexpected: user email '%s' should not have been found", - email, - ) + t.Errorf("unexpected: user email '%s' should not have been found", email) } usr, err := CreateUser(ctx, db, email, username, "so strong") @@ -1222,16 +1221,23 @@ func TestUserExists(t *testing.T) { \end{lstlisting} Since the database has just been created, there should be no users, which is -checked in the next \texttt{if} statement. The same check is then performed for -the earlier-declared user email that is also expected to fail. +checked in the body of the \texttt{if} statement (line 35). The same check is +then performed using an email (line 42), which is also correctly expected to +fail. -The final statements of the described test attempts a user creation call, which -is again checked for both error and \emph{nilability}. The test continues with -more similar checks but it has been cut short for brevity. +The final statements of the described test attempts to create a user by calling +the function \texttt{CreateUser(...)} at line 46, whose return values are again +checked for both error and \emph{nilability}, respectively. The test continues +with more of the checks similar to what has been described so far, but the rest +was omitted for brevity. -A neat thing about error handling in Go is that it allows for very easy -checking of all paths, not just the \emph{happy path} where there are no -issues. +As was just demonstrated in the test, a neat thing about error handling in Go +is that it allows for very easy checking of all code paths, not just the +\emph{happy path} where there are no issues. The recommended approach of +immediately explicitly handling (or deciding to ignore) the error is in +author's view superior to wrapping hundreds of lines in \texttt{try} blocks and +then \emph{catching} (or not) \emph{all the} exceptions, as is the practice in +some other languages. \n{2}{Test environment} @@ -1242,8 +1248,8 @@ by \emph{Let's Encrypt}\allowbreak issued, short-lived, ECDSA \texttt{secp384r1} curve TLS certificate, and configured with strict CSP. It is a test instance; therefore limits (and rate-limits) to prevent abuse might be imposed. - -The application in the test environment is available over both modern IPv6 and +\\ +The test environment makes the program available over both modern IPv6 and legacy IPv4 protocols, to maximise accessibility. Redirects have been set up from plain HTTP to HTTPS, as well as from \texttt{www} to non-\texttt{www} domain. The subject domain configuration has been hardened with a \texttt{CAA} @@ -1256,17 +1262,19 @@ speaking HTTP only connect to it (and the subdomains) using TLS. The whole deployment has been orchestrated using an Ansible\footnotemark{} playbook created for this occasion, with the aim to have the whole deployment -process reliably automated. At the same time, it is now described reasonably -well in the code. An effort has been made to make the playbook idempotent. Its -code is available at \url{https://git.dotya.ml/mirre-mt/ansible-pcmt.git}. +process reliably automated, focusing on idempotence. At the same time, it is +now described reasonably well in the code. Its code is available at +\url{https://git.dotya.ml/mirre-mt/ansible-pcmt.git}. \footnotetext{A Nix-ops approach was also considered, however, Ansible was -picked as more suitable since the existing host runs Arch.} +deemed more suitable since the existing host runs Arch.} \n{3}{Deployment validation} -TODO: show the results of testing the app in prod using: \url{https://testssl.sh/}. +% TODO: show the results of testing the app in prod using: +% \url{https://testssl.sh/} and +% \url{https://gtmetrix.com/reports/testpcmt.dotya.ml/}. The deployed application has been validated using the \textit{Security Headers} tool (see \url{https://securityheaders.com/?q=https%3A%2F%2Ftestpcmt.dotya.ml}), @@ -1319,7 +1327,7 @@ HSTS is deployed (including for the subdomains), the server supports TLS 1.3, the DNS Certificate Authority Authorisation (CAA) has been configured for the domain, with the overall grade being A+. -\obr{Quallys SSL Labs}{fig:ssllabs}{.70}{graphics/screen-sslLabs} +\obr{Quallys SSL Labs}{fig:ssllabs}{.79}{graphics/screen-sslLabs}