commit - c64c0d1524ca88914520c823d244c150e24926dc
commit + 2895fd6ddcae6c90b53efeaf9f56e9cc0e748689
blob - /dev/null
blob + 41be915dcc8a20f4be322d828dd23126b846ce98 (mode 644)
--- /dev/null
+++ test/book-error-csrf-missing.test
+Book page errors when the CSRF cookie is absent.
+---
+foo
+bar
+---
+baz@example.com,bar,2200-01-01
+baz@example.com,foo,2200-01-02
+---
+SERVER_PROTOCOL=HTTP/1.1
+REQUEST_URI=/book/2200-01-03
+REQUEST_METHOD=POST
+REMOTE_USER=baz@example.com
+CONTENT_TYPE=application/x-www-form-urlencoded
+CONTENT_LENGTH=19
+---
+_csrf=qux&desk=foo
+---
+baz@example.com,bar,2200-01-01
+baz@example.com,foo,2200-01-02
+---
+Status: 403 Forbidden
+Content-Type: text/plain; charset=utf-8
+
+Forbidden
blob - /dev/null
blob + 42ffdb5b43496288c4cc3fc3feb6d213f50b30c5 (mode 644)
--- /dev/null
+++ test/book-error-nobody.test
+Book page errors when the request body is empty.
+---
+foo
+bar
+---
+baz@example.com,bar,2200-01-01
+baz@example.com,foo,2200-01-02
+---
+SERVER_PROTOCOL=HTTP/1.1
+REQUEST_URI=/book/2200-01-03
+REQUEST_METHOD=POST
+REMOTE_USER=baz@example.com
+HTTP_COOKIE=deskd_csrf=qux
+CONTENT_TYPE=application/x-www-form-urlencoded
+CONTENT_LENGTH=0
+---
+
+---
+baz@example.com,bar,2200-01-01
+baz@example.com,foo,2200-01-02
+---
+Status: 400 Bad Request
+Content-Type: text/plain; charset=utf-8
+
+Bad Request
blob - /dev/null
blob + 3ac1ba532e97f163d1dbdc55da35888750fa85b3 (mode 644)
--- /dev/null
+++ test/bookingform-html-escape.test
+Booking form page HTML-escapes desk names and usernames.
+---
+<b>desk</b>
+a&b
+---
+evil@<script>.com,<b>desk</b>,2200-01-03
+---
+SERVER_PROTOCOL=HTTP/1.1
+REQUEST_URI=/book/2200-01-03
+REQUEST_METHOD=GET
+REMOTE_USER=baz@example.com
+---
+---
+evil@<script>.com,<b>desk</b>,2200-01-03
+---
+Status: 200 OK
+Content-Type: text/html; charset=utf-8
+Set-Cookie: deskd_csrf=REGEX{(?<CSRF>.+?)}; Path=/; Max-Age=3600; HttpOnly; Secure; SameSite=Strict
+
+<!DOCTYPE html>
+<html lang="en-GB">
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
+<link rel="stylesheet" type="text/css" href="/static/style.css">
+<title>Hot Desk</title>
+<body>
+<nav>
+ <h1><a href="/about">Hot Desk</a></h1>
+ <a href="/">Your Bookings</a> |
+ <a href="/book">Desk Booking</a>
+</nav>
+<div>
+ <h1>Desk Booking</h1>
+ <img src="/static/floorplan.png" alt="Floor plan">
+ <h2>Current bookings for 03/01/2200</h2>
+ <table>
+ <thead>
+ <tr>
+ <th>Desk</th>
+ <th>Booked By</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td><b>desk</b></td>
+ <td>evil@<script>.com</td>
+ </tr>
+ </tbody>
+ </table>
+ <h2>Book a desk for 03/01/2200</h2>
+ <form method="POST">
+ <input type="hidden" name="_csrf" value="REGEX{\k<CSRF>}">
+ <input type="radio" id="a&b" name="desk" value="a&b">
+ <label for="a&b">a&b</label>
+ <br/>
+ <br/>
+ <button type="submit">Book!</button>
+ </form>
+</div>
+</body>
+</html>
blob - /dev/null
blob + 6db72e09c0bd81314492109f2d112c0a6b6a5033 (mode 644)
--- /dev/null
+++ test/bookingform-no-desks.test
+Booking form page renders correctly when no desks are configured.
+---
+---
+---
+SERVER_PROTOCOL=HTTP/1.1
+REQUEST_URI=/book/2200-01-03
+REQUEST_METHOD=GET
+REMOTE_USER=baz@example.com
+---
+---
+---
+Status: 200 OK
+Content-Type: text/html; charset=utf-8
+Set-Cookie: deskd_csrf=REGEX{(?:.+?)}; Path=/; Max-Age=3600; HttpOnly; Secure; SameSite=Strict
+
+<!DOCTYPE html>
+<html lang="en-GB">
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
+<link rel="stylesheet" type="text/css" href="/static/style.css">
+<title>Hot Desk</title>
+<body>
+<nav>
+ <h1><a href="/about">Hot Desk</a></h1>
+ <a href="/">Your Bookings</a> |
+ <a href="/book">Desk Booking</a>
+</nav>
+<div>
+ <h1>Desk Booking</h1>
+ <img src="/static/floorplan.png" alt="Floor plan">
+ <p>Sorry no desks are available for 03/01/2200.</p>
+</div>
+</body>
+</html>
blob - /dev/null
blob + ba07db59fbd13a8e401be2cae79c146e9753dd2c (mode 644)
--- /dev/null
+++ test/bookings-filters-past.test
+Bookings page only shows future bookings.
+---
+foo
+bar
+---
+baz@example.com,bar,1990-01-01
+baz@example.com,foo,2200-01-01
+---
+SERVER_PROTOCOL=HTTP/1.1
+REQUEST_URI=/
+REQUEST_METHOD=GET
+REMOTE_USER=baz@example.com
+---
+---
+baz@example.com,bar,1990-01-01
+baz@example.com,foo,2200-01-01
+---
+Status: 200 OK
+Content-Type: text/html; charset=utf-8
+Set-Cookie: deskd_csrf=REGEX{(?<CSRF>.+?)}; Path=/; Max-Age=3600; HttpOnly; Secure; SameSite=Strict
+
+<!DOCTYPE html>
+<html lang="en-GB">
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
+<link rel="stylesheet" type="text/css" href="/static/style.css">
+<title>Hot Desk</title>
+<body>
+<nav>
+ <h1><a href="/about">Hot Desk</a></h1>
+ <a href="/">Your Bookings</a> |
+ <a href="/book">Desk Booking</a>
+</nav>
+<div>
+
+ <h1>Your Upcoming Bookings</h1>
+ <table>
+ <thead>
+ <tr>
+ <th>Date</th>
+ <th>Desk</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+
+ <tr>
+ <td>01/01/2200</td>
+ <td>foo</td>
+ <td style="text-align: center">
+ <form method="POST">
+ <input type="hidden" name="_csrf" value="REGEX{\k<CSRF>}">
+ <input type="hidden" name="desk" value="foo">
+ <input type="hidden" name="day" value="2200-01-01">
+ <button type="submit">Cancel</button>
+ </form>
+ </td>
+ </tr>
+
+ </tbody>
+ </table>
+
+</div>
+</body>
+</html>
blob - /dev/null
blob + ddcf88b4ab6642d44c495d44ee33d91dc6bd9e7b (mode 644)
--- /dev/null
+++ test/bookings-html-escape.test
+Bookings page HTML-escapes desk names.
+---
+<b>desk</b>
+---
+baz@example.com,<b>desk</b>,2200-01-01
+---
+SERVER_PROTOCOL=HTTP/1.1
+REQUEST_URI=/
+REQUEST_METHOD=GET
+REMOTE_USER=baz@example.com
+---
+---
+baz@example.com,<b>desk</b>,2200-01-01
+---
+Status: 200 OK
+Content-Type: text/html; charset=utf-8
+Set-Cookie: deskd_csrf=REGEX{(?<CSRF>.+?)}; Path=/; Max-Age=3600; HttpOnly; Secure; SameSite=Strict
+
+<!DOCTYPE html>
+<html lang="en-GB">
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
+<link rel="stylesheet" type="text/css" href="/static/style.css">
+<title>Hot Desk</title>
+<body>
+<nav>
+ <h1><a href="/about">Hot Desk</a></h1>
+ <a href="/">Your Bookings</a> |
+ <a href="/book">Desk Booking</a>
+</nav>
+<div>
+
+ <h1>Your Upcoming Bookings</h1>
+ <table>
+ <thead>
+ <tr>
+ <th>Date</th>
+ <th>Desk</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+
+ <tr>
+ <td>01/01/2200</td>
+ <td><b>desk</b></td>
+ <td style="text-align: center">
+ <form method="POST">
+ <input type="hidden" name="_csrf" value="REGEX{\k<CSRF>}">
+ <input type="hidden" name="desk" value="<b>desk</b>">
+ <input type="hidden" name="day" value="2200-01-01">
+ <button type="submit">Cancel</button>
+ </form>
+ </td>
+ </tr>
+
+ </tbody>
+ </table>
+
+</div>
+</body>
+</html>
blob - /dev/null
blob + a10216ce9160ac9a124e7a7317d354c40ed2dbfc (mode 644)
--- /dev/null
+++ test/cancel-error-csrf-missing.test
+Cancel page errors when the CSRF cookie is absent.
+---
+foo
+bar
+---
+baz@example.com,bar,2200-01-01
+baz@example.com,foo,2200-01-02
+baz@example.com,foo,2200-01-03
+---
+SERVER_PROTOCOL=HTTP/1.1
+REQUEST_URI=/
+REQUEST_METHOD=POST
+REMOTE_USER=baz@example.com
+CONTENT_TYPE=application/x-www-form-urlencoded
+CONTENT_LENGTH=34
+---
+_csrf=qux&desk=foo&day=2200-01-03
+---
+baz@example.com,bar,2200-01-01
+baz@example.com,foo,2200-01-02
+baz@example.com,foo,2200-01-03
+---
+Status: 403 Forbidden
+Content-Type: text/plain; charset=utf-8
+
+Forbidden
blob - /dev/null
blob + b00b8d0c9fb86ae518950aece944e3d281a2bb49 (mode 644)
--- /dev/null
+++ test/cancel-error-missing-day.test
+Cancel page errors when the day is missing.
+---
+foo
+bar
+---
+baz@example.com,bar,2200-01-01
+baz@example.com,foo,2200-01-02
+baz@example.com,foo,2200-01-03
+---
+SERVER_PROTOCOL=HTTP/1.1
+REQUEST_URI=/
+REQUEST_METHOD=POST
+REMOTE_USER=baz@example.com
+HTTP_COOKIE=deskd_csrf=qux
+CONTENT_TYPE=application/x-www-form-urlencoded
+CONTENT_LENGTH=19
+---
+_csrf=qux&desk=foo
+---
+baz@example.com,bar,2200-01-01
+baz@example.com,foo,2200-01-02
+baz@example.com,foo,2200-01-03
+---
+Status: 400 Bad Request
+Content-Type: text/plain; charset=utf-8
+Set-Cookie: deskd_csrf=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Strict
+
+Bad Request
blob - /dev/null
blob + b04db88be992862dafcf1b0a1eaad59f862378df (mode 644)
--- /dev/null
+++ test/cancel-nonexistent.test
+Cancel page redirects even when the booking does not exist.
+---
+foo
+bar
+---
+baz@example.com,bar,2200-01-01
+---
+SERVER_PROTOCOL=HTTP/1.1
+REQUEST_URI=/
+REQUEST_METHOD=POST
+REMOTE_USER=baz@example.com
+HTTP_COOKIE=deskd_csrf=qux
+CONTENT_TYPE=application/x-www-form-urlencoded
+CONTENT_LENGTH=34
+---
+_csrf=qux&desk=foo&day=2200-01-03
+---
+baz@example.com,bar,2200-01-01
+---
+Status: 303 See Other
+Content-Type: text/plain; charset=utf-8
+Location: /
+Set-Cookie: deskd_csrf=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Strict
blob - /dev/null
blob + f1bdaaf41b4fe85a0ae80dd4e46e3d67be4b92f6 (mode 644)
--- /dev/null
+++ test/dateform-empty-day.test
+Date picker page renders form when day parameter is empty.
+---
+---
+---
+REQUEST_URI=/book?day=
+REQUEST_METHOD=GET
+SERVER_PROTOCOL=HTTP/1.1
+---
+---
+---
+Status: 200 OK
+Content-Type: text/html; charset=utf-8
+
+<!DOCTYPE html>
+<html lang="en-GB">
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
+<link rel="stylesheet" type="text/css" href="/static/style.css">
+<title>Hot Desk</title>
+<body>
+<nav>
+ <h1><a href="/about">Hot Desk</a></h1>
+ <a href="/">Your Bookings</a> |
+ <a href="/book">Desk Booking</a>
+</nav>
+<div>
+ <form>
+ <label for="day">Choose a day:</label>
+ <input type="date" id="day" name="day">
+ <br/>
+ <br/>
+ <button type="submit">Next</button>
+ </form>
+</div>
+</body>
+</html>
blob - /dev/null
blob + c2cff2ddd3e2927e3a4bfe163b2fb0a70d194a47 (mode 644)
--- /dev/null
+++ test/route-unknown.test
+Unknown route returns 400.
+---
+---
+---
+REQUEST_URI=/nonexistent
+REQUEST_METHOD=GET
+SERVER_PROTOCOL=HTTP/1.1
+---
+---
+---
+Status: 400 Bad Request
+Content-Type: text/plain; charset=utf-8
+
+Bad Request
blob - /dev/null
blob + 3ffe8a7b49a09aa702ef140dc989d8efc659a991 (mode 644)
--- /dev/null
+++ test/route-wrong-method.test
+Wrong HTTP method for a known route returns 400.
+---
+---
+---
+REQUEST_URI=/about
+REQUEST_METHOD=POST
+SERVER_PROTOCOL=HTTP/1.1
+---
+---
+---
+Status: 400 Bad Request
+Content-Type: text/plain; charset=utf-8
+
+Bad Request