scanning directories for unit tests...
scanning present working directory for unit tests...
shuffled the files with seed 478582606 (replay with VERITAS_SEED=478582606)
found unit tests: (task-repository-spec.scm mail-spec.scm templates-spec.scm)
running unit tests from file: task-repository-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 478582606 (replay with VERITAS_SEED=478582606)
+----------------------------------------------+
[📑 suite] binding, not splicing
[🧪 test] a subject with an apostrophe in it round-trips unchanged
[⚙️ assert] O'Brien is a name, not the end of a literal
✅ got: O'Brien'); DROP TABLE tasks; -- "quoted" & <b>bold</b>
[🧪 test] a value that spells out SQL changes nothing
[⚙️ assert] the table the value named is still there
✅ got: 3
+----------------------------------------------+
[📑 suite] reading the queue
[🧪 test] the next task is the oldest pending one
[⚙️ assert] a queue answers in the order it was filled
✅ got: queue-oldest
[🧪 test] a batch is the oldest first and as long as it was asked for
[⚙️ assert] a sweep takes the batch it asked for, oldest first
✅ got: (queue-oldest queue-middle)
[⚙️ assert] a batch never carries a column the row does not have
✅ got: (queue-oldest queue-middle queue-newest)
[🧪 test] the list an operator reads carries the metadata and no secrets
[⚙️ assert] the newest first, and only the columns an operator needs
✅ got: (queue-newest queue-middle queue-oldest)
[⚙️ assert] a reset link must not be on the way to a browser
✅ got: #f
+----------------------------------------------+
[📑 suite] updating and deleting
[🧪 test] an update writes the columns it names
[⚙️ assert] the status and the attempt count are what was written
✅ got: (complete 2)
[🧪 test] a deleted task is gone
[⚙️ assert] nothing answers for a task that was deleted
✅ got: #f
+----------------------------------------------+
[📑 suite] a worker that stopped
[🧪 test] the claim of a task in flight is not undone by the sweep
[⚙️ assert] a claim written a moment ago is newer than the threshold
✅ got: processing
[🧪 test] a task a worker is still on is not taken away from it
[⚙️ assert] a processing task newer than the threshold is left alone
✅ got: processing
[🧪 test] a task old enough to be abandoned goes back in the queue
[⚙️ assert] a processing task older than the threshold is pending again
✅ got: pending
+----------------------------------------------+
[📑 suite] claiming
[🧪 test] a claimed task is no longer pending
[⚙️ assert] claiming takes the task out of the queue
✅ got: #f
[⚙️ assert] and the row says it is being processed
✅ got: processing
[🧪 test] claiming an already claimed task changes nothing
[⚙️ assert] a task another sweep took is left alone
✅ got: processing
ℹ️ info - ran 5 tasks in: 0 seconds, 115415 microseconds
running unit tests from file: mail-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 478582606 (replay with VERITAS_SEED=478582606)
+----------------------------------------------+
[📑 suite] headers
[🧪 test] ASCII is left readable
[⚙️ assert] nothing is encoded that needs no encoding
✅ got: hello
[🧪 test] a line break cannot inject a header
[⚙️ assert] a CRLF in a value cannot open a header of its own
✅ got:
[🧪 test] non-ASCII is encoded
[⚙️ assert] an accented subject is base64 encoded
✅ got: =?UTF-8?B?
[🧪 test] a bare address carries no angle brackets
[⚙️ assert] an address with no name is just the address
✅ got: j@x.y
[🧪 test] a display name with a comma is quoted
[⚙️ assert] a comma in a display name is quoted
✅ got: "Doe, Jane" <j@x.y>
[🧪 test] the date is an RFC 5322 date
[⚙️ assert] the date header is the shape a mail server reads
✅ got: Tue, 10 Nov 2026 20:42:37 +0100
+----------------------------------------------+
[📑 suite] quoted-printable
[🧪 test] a trailing space is escaped
[⚙️ assert] a space before a newline cannot be eaten
✅ got: tail=20
[🧪 test] non-ASCII is escaped byte by byte
[⚙️ assert] an accented character travels as its bytes
✅ got: =C3=A9
[🧪 test] an equals sign is escaped
[⚙️ assert] the escape character escapes itself
✅ got: a=3Db
[🧪 test] lines are split with CRLF
[⚙️ assert] a newline becomes CRLF
✅ got: one
two
[🧪 test] a leading dot is escaped so SMTP cannot end DATA early
[⚙️ assert] a line that starts with a dot is encoded
✅ got: =2Edot
+----------------------------------------------+
[📑 suite] the message
[🧪 test] every line ends CRLF (no bare LF)
[⚙️ assert] no bare newline survives into the message
✅ got: #f
[🧪 test] the boundary opens and closes
[⚙️ assert] a multipart message declares its boundary
✅ got: #t
[🧪 test] both parts are present
[⚙️ assert] the plain and the HTML body are both there
✅ got: #t
[🧪 test] the id is appended to the subject
[⚙️ assert] a reader can match the mail to the task
✅ got: Subject: Hi the-id
[🧪 test] the HTML part is carried through as the caller wrote it
[⚙️ assert] the body's own markup survives
✅ got: <p>html body</p>
[⚙️ assert] no doctype is invented here
✅ got: 0
[🧪 test] a Date header is present
[⚙️ assert] the message is dated
✅ got: Date:
[🧪 test] a Message-ID is generated
[⚙️ assert] the message carries an id of its own
✅ got: Message-ID: <
ℹ️ info - ran 3 tasks in: 0 seconds, 110157 microseconds
running unit tests from file: templates-spec.scm, entrypoint: spec
ℹ️ info - shuffled with seed 478582606 (replay with VERITAS_SEED=478582606)
+----------------------------------------------+
[📑 suite] password reset template
[🧪 test] the frosted panel is there
[⚙️ assert] the panel the family's mails share is drawn
✅ got: pw-panel
[🧪 test] the account is named
[⚙️ assert] the reader is told whose account this is
✅ got: joe@x.y
[🧪 test] the link is present
[⚙️ assert] the reset link is in the mail
✅ got: https://x/y
[🧪 test] the style is inline
[⚙️ assert] a mail client that drops stylesheets still sees it
✅ got: style=
+----------------------------------------------+
[📑 suite] what the dark override can reach
[🧪 test] every class a message wears is a style with a dark reading
[⚙️ assert] every class of the ticket notice is a named style
✅ got: ()
[⚙️ assert] every class of the reset mail is a named style
✅ got: ()
[⚙️ assert] every class of the alert is a named style
✅ got: ()
[🧪 test] nothing paints a colour without a class to re-point it
[⚙️ assert] the ticket notice paints only through classes
✅ got: ()
[⚙️ assert] the reset mail paints only through classes
✅ got: ()
[⚙️ assert] the alert paints only through classes
✅ got: ()
+----------------------------------------------+
[📑 suite] the fonts and the glass
[🧪 test] the panel declares a frost blur
[⚙️ assert] the panel is frosted, not merely translucent
✅ got: backdrop-filter
[🧪 test] the faces are declared
[⚙️ assert] Work Sans is declared
✅ got: Work Sans
[⚙️ assert] Source Serif 4 is declared
✅ got: Source Serif 4
[⚙️ assert] JuliaMono is declared
✅ got: JuliaMono
[🧪 test] both colour schemes are offered
[⚙️ assert] a dark theme is offered
✅ got: prefers-color-scheme
+----------------------------------------------+
[📑 suite] readability, in both readings of the palette
[🧪 test] the measurement is the one WCAG defines
[⚙️ assert] white on black is the 21:1 a ratio can reach
✅ got: 21.0
[⚙️ assert] a colour on itself is 1:1
✅ got: 1.0
[🧪 test] no message ships a text colour the floor was not applied to
[⚙️ assert] the ticket notice ships only measured text colours
✅ got: ()
[⚙️ assert] the reset mail ships only measured text colours
✅ got: ()
[⚙️ assert] the alert ships only measured text colours
✅ got: ()
[🧪 test] every colour clears the floor on every surface it is set on
[⚙️ assert] the light reading clears the floor everywhere it is read
✅ got: ()
[⚙️ assert] the dark reading clears the floor everywhere it is read
✅ got: ()
+----------------------------------------------+
[📑 suite] system alert template
[🧪 test] the system is named
[⚙️ assert] which system raised it is in the mail
✅ got: X
[🧪 test] the alert is shown
[⚙️ assert] what went wrong is in the mail
✅ got: disk full
+----------------------------------------------+
[📑 suite] the markup a mail client is handed
[🧪 test] nothing is closed before it has been opened
[⚙️ assert] the ticket notice is not swallowed by its own accent
✅ got: <!DOCTYPE html>
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width,initial-scale=1.0" /><meta name="color-scheme" content="light dark" /><meta name="supported-color-schemes" content="light dark" /><title>New ticket in Pingwing</title><style>@font-face{font-family:'Work Sans';font-style:normal;font-weight:100 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/work-sans-normal-latin.woff2') format('woff2');}
@font-face{font-family:'Work Sans';font-style:italic;font-weight:100 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/work-sans-italic-latin.woff2') format('woff2');}
@font-face{font-family:'Source Serif 4';font-style:normal;font-weight:200 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/source-serif-4-normal-latin.woff2') format('woff2');}
@font-face{font-family:'Source Serif 4';font-style:italic;font-weight:200 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/source-serif-4-italic-latin.woff2') format('woff2');}
@font-face{font-family:'JuliaMono';font-style:normal;font-weight:300 500;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/juliamono-normal-latin.woff2') format('woff2');}
@font-face{font-family:'JuliaMono';font-style:normal;font-weight:600 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/juliamono-bold-latin.woff2') format('woff2');}
@media (prefers-color-scheme: dark) {
.pw-body { background-color: #232a2e !important; background-image: linear-gradient(160deg,#333b40 0%,#2b3339 55%,#232a2e 100%) !important; color: #d3c6aa !important; }
.pw-panel { background-color: rgba(51,59,64,0.72) !important; border: 1px solid rgba(211,198,170,0.22) !important; box-shadow: 0 18px 44px rgba(0,0,0,0.38) !important; }
.pw-inner { background-color: rgba(211,198,170,0.08) !important; border: 1px solid rgba(211,198,170,0.18) !important; }
.pw-heading { color: #e6ddc4 !important; }
.pw-text { color: #d3c6aa !important; }
.pw-muted { color: #adb9ab !important; }
.pw-mono { color: #e6ddc4 !important; }
.pw-fact-label { color: #adb9ab !important; }
.pw-fact-value { color: #e6ddc4 !important; }
.pw-link { color: #e6ddc4 !important; }
.pw-footer-link { color: #adb9ab !important; }
.pw-alert { color: #e6ddc4 !important; }
.pw-rule { border-top: 1px solid rgba(211,198,170,0.22) !important; }
}
</style></head><body class="pw-body" style="margin: 0; padding: 0; width: 100%; background-color: #e7e4dc; background-image: linear-gradient(160deg,#fbfaf6 0%,#eeece6 45%,#e6e3db 100%); font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 16px; line-height: 1.6; color: #4a5259; -webkit-font-smoothing: antialiased;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;"><tr><td align="center" style="padding:34px 16px;"><table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" style="width: 100%; max-width: 600px; border-collapse: separate; margin: 0 auto;"><tr><td><table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" class="pw-panel" style="width: 100%; max-width: 600px; border-collapse: separate; background-color: rgba(252,251,247,0.72); border: 1px solid rgba(120,113,108,0.26); border-radius: 24px; box-shadow: 0 18px 44px rgba(43,51,57,0.14); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); overflow: hidden;"><tr><td style="padding:0;"><div class="pw-accent" style="height: 6px; line-height: 0; font-size: 0; background-image: linear-gradient(90deg,#a7c080,#e67e80,#dbbc7f,#7fbbb3);"></div><div style="padding:38px 34px;"><h1 class="pw-heading" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 23px; font-weight: 600; line-height: 1.35; margin: 0 0 6px;">New ticket in Pingwing</h1><p class="pw-text" style="color: #4a5259; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 16px; line-height: 1.65; margin: 14px 0;">someone opened a new ticket #12. Here is what it says.</p><div class="pw-inner" style="background-color: rgba(120,113,108,0.08); border: 1px solid rgba(120,113,108,0.18); border-radius: 16px; padding: 18px 20px; margin: 22px 0;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;"><tr><td class="pw-fact-label" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; padding: 4px 12px 4px 0; vertical-align: top;">Ticket</td><td class="pw-fact-value" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 14px; padding: 4px 0; text-align: right; vertical-align: top;"><span class="pw-mono" style="color: #2b3339; font-family: 'JuliaMono','DejaVu Sans Mono','Liberation Mono',Menlo,Consolas,'Courier New',monospace; font-size: 13px; word-break: break-all;">#12</span></td></tr><tr><td class="pw-fact-label" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; padding: 4px 12px 4px 0; vertical-align: top;">Title</td><td class="pw-fact-value" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 14px; padding: 4px 0; text-align: right; vertical-align: top;">Fix the SMTP retry</td></tr><tr><td class="pw-fact-label" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; padding: 4px 12px 4px 0; vertical-align: top;">Project</td><td class="pw-fact-value" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 14px; padding: 4px 0; text-align: right; vertical-align: top;"><span class="pw-mono" style="color: #2b3339; font-family: 'JuliaMono','DejaVu Sans Mono','Liberation Mono',Menlo,Consolas,'Courier New',monospace; font-size: 13px; word-break: break-all;">Pingwing</span></td></tr></table></div><table role="presentation" cellpadding="0" cellspacing="0" border="0" style="border-collapse:separate;margin:24px 0;"><tr><td class="pw-button" style="background-color: #a7c080; border-radius: 999px; padding: 13px 26px;"><a href="https://lucidplan/x/ticket/12" class="pw-button-ink" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 16px; font-weight: 600; text-decoration: none; display: inline-block;">Open ticket on Lucidplan</a></td></tr></table><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;">If the button does not work, copy and paste this link into your browser:</p><p class="pw-mono" style="color: #2b3339; font-family: 'JuliaMono','DejaVu Sans Mono','Liberation Mono',Menlo,Consolas,'Courier New',monospace; font-size: 13px; word-break: break-all;">https://lucidplan/x/ticket/12</p><div class="pw-rule" style="border-top: 1px solid rgba(120,113,108,0.28); margin: 24px 0; font-size: 0; line-height: 0;"></div><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;">You receive this because you take part in this project; no action is needed to keep receiving them.</p></div></td></tr></table></td></tr><tr><td><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;"><tr><td align="center" style="padding:20px 12px 10px;"><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;">this e-mail was powered by pingwing - the free and open Lisp-powered notification nexus</p><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;"><a href="https://codeberg.org/jjba23/pingwing" class="pw-footer-link" style="color: #5c655f; text-decoration: underline;">🔗 pingwing's source code on Codeberg</a></p></td></tr></table></td></tr></table></td></tr></table></body></html>
[⚙️ assert] the reset mail is not swallowed by its own rule
✅ got: <!DOCTYPE html>
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width,initial-scale=1.0" /><meta name="color-scheme" content="light dark" /><meta name="supported-color-schemes" content="light dark" /><title>Password Reset</title><style>@font-face{font-family:'Work Sans';font-style:normal;font-weight:100 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/work-sans-normal-latin.woff2') format('woff2');}
@font-face{font-family:'Work Sans';font-style:italic;font-weight:100 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/work-sans-italic-latin.woff2') format('woff2');}
@font-face{font-family:'Source Serif 4';font-style:normal;font-weight:200 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/source-serif-4-normal-latin.woff2') format('woff2');}
@font-face{font-family:'Source Serif 4';font-style:italic;font-weight:200 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/source-serif-4-italic-latin.woff2') format('woff2');}
@font-face{font-family:'JuliaMono';font-style:normal;font-weight:300 500;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/juliamono-normal-latin.woff2') format('woff2');}
@font-face{font-family:'JuliaMono';font-style:normal;font-weight:600 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/juliamono-bold-latin.woff2') format('woff2');}
@media (prefers-color-scheme: dark) {
.pw-body { background-color: #232a2e !important; background-image: linear-gradient(160deg,#333b40 0%,#2b3339 55%,#232a2e 100%) !important; color: #d3c6aa !important; }
.pw-panel { background-color: rgba(51,59,64,0.72) !important; border: 1px solid rgba(211,198,170,0.22) !important; box-shadow: 0 18px 44px rgba(0,0,0,0.38) !important; }
.pw-inner { background-color: rgba(211,198,170,0.08) !important; border: 1px solid rgba(211,198,170,0.18) !important; }
.pw-heading { color: #e6ddc4 !important; }
.pw-text { color: #d3c6aa !important; }
.pw-muted { color: #adb9ab !important; }
.pw-mono { color: #e6ddc4 !important; }
.pw-fact-label { color: #adb9ab !important; }
.pw-fact-value { color: #e6ddc4 !important; }
.pw-link { color: #e6ddc4 !important; }
.pw-footer-link { color: #adb9ab !important; }
.pw-alert { color: #e6ddc4 !important; }
.pw-rule { border-top: 1px solid rgba(211,198,170,0.22) !important; }
}
</style></head><body class="pw-body" style="margin: 0; padding: 0; width: 100%; background-color: #e7e4dc; background-image: linear-gradient(160deg,#fbfaf6 0%,#eeece6 45%,#e6e3db 100%); font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 16px; line-height: 1.6; color: #4a5259; -webkit-font-smoothing: antialiased;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;"><tr><td align="center" style="padding:34px 16px;"><table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" style="width: 100%; max-width: 600px; border-collapse: separate; margin: 0 auto;"><tr><td><table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" class="pw-panel" style="width: 100%; max-width: 600px; border-collapse: separate; background-color: rgba(252,251,247,0.72); border: 1px solid rgba(120,113,108,0.26); border-radius: 24px; box-shadow: 0 18px 44px rgba(43,51,57,0.14); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); overflow: hidden;"><tr><td style="padding:0;"><div class="pw-accent" style="height: 6px; line-height: 0; font-size: 0; background-image: linear-gradient(90deg,#a7c080,#e67e80,#dbbc7f,#7fbbb3);"></div><div style="padding:38px 34px;"><h1 class="pw-heading" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 23px; font-weight: 600; line-height: 1.35; margin: 0 0 6px;">Reset your password WikiMusic</h1><p class="pw-text" style="color: #4a5259; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 16px; line-height: 1.65; margin: 14px 0;">We received a request to reset the password for your account. If that was you, follow the button below; the link is yours alone, and it is safe to ignore this message if it was not.</p><div class="pw-inner" style="background-color: rgba(120,113,108,0.08); border: 1px solid rgba(120,113,108,0.18); border-radius: 16px; padding: 18px 20px; margin: 22px 0;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;"><tr><td class="pw-fact-label" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; padding: 4px 12px 4px 0; vertical-align: top;">Account</td><td class="pw-fact-value" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 14px; padding: 4px 0; text-align: right; vertical-align: top;"><span class="pw-mono" style="color: #2b3339; font-family: 'JuliaMono','DejaVu Sans Mono','Liberation Mono',Menlo,Consolas,'Courier New',monospace; font-size: 13px; word-break: break-all;">joe@x.y</span></td></tr></table></div><table role="presentation" cellpadding="0" cellspacing="0" border="0" style="border-collapse:separate;margin:24px 0;"><tr><td class="pw-button" style="background-color: #a7c080; border-radius: 999px; padding: 13px 26px;"><a href="https://x/y" class="pw-button-ink" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 16px; font-weight: 600; text-decoration: none; display: inline-block;">Reset your password</a></td></tr></table><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;">If the button does not work, copy and paste this link into your browser:</p><p class="pw-mono" style="color: #2b3339; font-family: 'JuliaMono','DejaVu Sans Mono','Liberation Mono',Menlo,Consolas,'Courier New',monospace; font-size: 13px; word-break: break-all;">https://x/y</p><div class="pw-rule" style="border-top: 1px solid rgba(120,113,108,0.28); margin: 24px 0; font-size: 0; line-height: 0;"></div><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;">This message was sent because someone asked to reset the password on this account. If that was not you, no action is needed and your password has not changed.</p></div></td></tr></table></td></tr><tr><td><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;"><tr><td align="center" style="padding:20px 12px 10px;"><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;">this e-mail was powered by pingwing - the free and open Lisp-powered notification nexus</p><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;"><a href="https://codeberg.org/jjba23/pingwing" class="pw-footer-link" style="color: #5c655f; text-decoration: underline;">🔗 pingwing's source code on Codeberg</a></p></td></tr></table></td></tr></table></td></tr></table></body></html>
[⚙️ assert] the alert is not swallowed either
✅ got: <!DOCTYPE html>
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width,initial-scale=1.0" /><meta name="color-scheme" content="light dark" /><meta name="supported-color-schemes" content="light dark" /><title>System Alert</title><style>@font-face{font-family:'Work Sans';font-style:normal;font-weight:100 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/work-sans-normal-latin.woff2') format('woff2');}
@font-face{font-family:'Work Sans';font-style:italic;font-weight:100 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/work-sans-italic-latin.woff2') format('woff2');}
@font-face{font-family:'Source Serif 4';font-style:normal;font-weight:200 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/source-serif-4-normal-latin.woff2') format('woff2');}
@font-face{font-family:'Source Serif 4';font-style:italic;font-weight:200 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/source-serif-4-italic-latin.woff2') format('woff2');}
@font-face{font-family:'JuliaMono';font-style:normal;font-weight:300 500;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/juliamono-normal-latin.woff2') format('woff2');}
@font-face{font-family:'JuliaMono';font-style:normal;font-weight:600 900;font-display:swap;src:url('https://jointhefreeworld.org/static-assets/fonts/juliamono-bold-latin.woff2') format('woff2');}
@media (prefers-color-scheme: dark) {
.pw-body { background-color: #232a2e !important; background-image: linear-gradient(160deg,#333b40 0%,#2b3339 55%,#232a2e 100%) !important; color: #d3c6aa !important; }
.pw-panel { background-color: rgba(51,59,64,0.72) !important; border: 1px solid rgba(211,198,170,0.22) !important; box-shadow: 0 18px 44px rgba(0,0,0,0.38) !important; }
.pw-inner { background-color: rgba(211,198,170,0.08) !important; border: 1px solid rgba(211,198,170,0.18) !important; }
.pw-heading { color: #e6ddc4 !important; }
.pw-text { color: #d3c6aa !important; }
.pw-muted { color: #adb9ab !important; }
.pw-mono { color: #e6ddc4 !important; }
.pw-fact-label { color: #adb9ab !important; }
.pw-fact-value { color: #e6ddc4 !important; }
.pw-link { color: #e6ddc4 !important; }
.pw-footer-link { color: #adb9ab !important; }
.pw-alert { color: #e6ddc4 !important; }
.pw-rule { border-top: 1px solid rgba(211,198,170,0.22) !important; }
}
</style></head><body class="pw-body" style="margin: 0; padding: 0; width: 100%; background-color: #e7e4dc; background-image: linear-gradient(160deg,#fbfaf6 0%,#eeece6 45%,#e6e3db 100%); font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 16px; line-height: 1.6; color: #4a5259; -webkit-font-smoothing: antialiased;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;"><tr><td align="center" style="padding:34px 16px;"><table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" style="width: 100%; max-width: 600px; border-collapse: separate; margin: 0 auto;"><tr><td><table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" class="pw-panel" style="width: 100%; max-width: 600px; border-collapse: separate; background-color: rgba(252,251,247,0.72); border: 1px solid rgba(120,113,108,0.26); border-radius: 24px; box-shadow: 0 18px 44px rgba(43,51,57,0.14); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); overflow: hidden;"><tr><td style="padding:0;"><div class="pw-accent" style="height: 6px; line-height: 0; font-size: 0; background-image: linear-gradient(90deg,#a7c080,#e67e80,#dbbc7f,#7fbbb3);"></div><div style="padding:38px 34px;"><h1 class="pw-heading" style="color: #2b3339; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 23px; font-weight: 600; line-height: 1.35; margin: 0 0 6px;">System alert X</h1><p class="pw-text" style="color: #4a5259; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 16px; line-height: 1.65; margin: 14px 0;">An alert has been triggered from a system you watch. The details are below.</p><div class="pw-inner" style="background-color: rgba(120,113,108,0.08); border: 1px solid rgba(120,113,108,0.18); border-radius: 16px; padding: 18px 20px; margin: 22px 0;"><div class="pw-alert" style="color: #2b3339; font-family: 'JuliaMono','DejaVu Sans Mono','Liberation Mono',Menlo,Consolas,'Courier New',monospace; font-size: 13px; white-space: pre-wrap; word-break: break-word;">disk full</div></div><div class="pw-rule" style="border-top: 1px solid rgba(120,113,108,0.28); margin: 24px 0; font-size: 0; line-height: 0;"></div><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;">This message was sent automatically; reply to it if the alert needs a human.</p></div></td></tr></table></td></tr><tr><td><table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse;"><tr><td align="center" style="padding:20px 12px 10px;"><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;">this e-mail was powered by pingwing - the free and open Lisp-powered notification nexus</p><p class="pw-muted" style="color: #5c655f; font-family: 'Work Sans','Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif; font-size: 13px; line-height: 1.55; margin: 0 0 6px;"><a href="https://codeberg.org/jjba23/pingwing" class="pw-footer-link" style="color: #5c655f; text-decoration: underline;">🔗 pingwing's source code on Codeberg</a></p></td></tr></table></td></tr></table></td></tr></table></body></html>
+----------------------------------------------+
[📑 suite] the document a mail client is handed
[🧪 test] every message opens with the doctype
[⚙️ assert] the ticket notice is a whole document
✅ got: #t
[⚙️ assert] the reset mail is a whole document
✅ got: #t
[⚙️ assert] the alert is a whole document
✅ got: #t
[🧪 test] and says it only once
[⚙️ assert] the doctype is at the front, not repeated
✅ got: #f
[🧪 test] the message a client receives opens with it
[⚙️ assert] the doctype opens the HTML part of the message
✅ got: #t
[⚙️ assert] the message carries exactly one doctype
✅ got: 1
+----------------------------------------------+
[📑 suite] lucidplan new-ticket template
[🧪 test] the HTML part names the project, the ticket and the link
[⚙️ assert] the project is named
✅ got: Pingwing
[⚙️ assert] the ticket number reads as #12
✅ got: #12
[⚙️ assert] the title is shown
✅ got: Fix the SMTP retry
[⚙️ assert] the link is shown
✅ got: https://lucidplan/x/ticket/12
[🧪 test] the plain part says the same thing
[⚙️ assert] the plain part names the ticket too
✅ got: Fix the SMTP retry
[⚙️ assert] the plain part names the project
✅ got: Pingwing
ℹ️ info - ran 8 tasks in: 1 seconds, -710182 microseconds
finished running tests
--------------------------------------------------------------
Maak 0.14.0: the infinitely extensible command runner
Loading tasks from Maak file: /etc/byggsteg/job-clone/pingwing/trunk/maak.scm
Executing Maak tasks: test
--------------------------------------------------------------
=> Running Maak task: test
Run the project's test suite with Veritas.
The runner scans ./test for suites and loads each spec as a module, which is
why the specs under test/veritas/unit are named `(veritas unit ...)': that is
the name the runner derives from where they sit, and `-L ./test' is what lets
Guile resolve it.
Execute command:
guix time-machine --channels=channels.scm -- shell -m manifest.scm -- guile -L ./lib -L . -L ./test -c '(begin (use-modules (veritas runner)) (run-tests))'
Executing: guix time-machine --channels=channels.scm -- shell -m manifest.scm -- guile -L ./lib -L . -L ./test -c '(begin (use-modules (veritas runner)) (run-tests))'