catcrafts.net/tests/ShouldServeRoutes/main.cpp
Jorijn van der Graaf ed61fb5ca5
All checks were successful
Deploy / build-deploy (push) Successful in 3m6s
fp6+
2026-08-19 17:19:51 +02:00

105 lines
5.6 KiB
C++

/*
catcrafts.net
Copyright (C) 2026 Catcrafts
The source code of this website is made available for viewing purposes only.
No permission is granted to copy, modify, distribute, or create derivative works.
*/
// Status codes, redirects, response headers, and the sitemap/feed documents —
// the things only a real request can show. A real 404 is the whole reason the
// backend exists: a client-side router cannot produce one.
import std;
import Catcrafts.E2eHarness;
using namespace Catcrafts::E2e;
int main(int argc, char** argv) {
TestServer srv(argv[1], 8210);
// ── status codes ──────────────────────────────────────────────────
for (std::string_view p : { "/", "/about", "/shop", "/shop/fp6-pmos",
"/shop/fp6plus-pmos", "/shop/donation", "/projects",
"/posts", "/demos", "/demos/raytracer", "/financials",
"/legal/privacy", "/legal/terms", "/legal/imprint",
"/feed.xml", "/sitemap.xml", "/api/healthz" }) {
srv.CheckStatus(std::string(p), "200");
}
// Trailing slashes must normalise, not 404 or duplicate the canonical URL.
srv.CheckStatus("/projects/", "200");
srv.CheckStatus("/shop/", "200");
// A real 404, which a client-side router cannot produce — this is the
// whole reason the backend exists.
srv.CheckStatus("/nope", "404");
srv.CheckStatus("/shop/nope", "404");
srv.CheckStatus("/legal/nope", "404");
// A slug that cannot be one of ours is rejected before any lookup.
srv.CheckStatus("/shop/BAD--slug", "404");
srv.CheckStatus("/demos/nope", "404");
// A post slug that parsed but names nothing must be a real 404, or every
// typo and every retired post becomes an indexable empty page.
srv.CheckStatus("/posts/nope", "404");
srv.CheckStatus("/posts/BAD--slug", "404");
// The retired blog URLs are still in the wild; they must redirect, not 404.
srv.CheckStatus("/blog", "301");
srv.CheckStatus("/blog/hello-world", "301");
// /demo was the single-demo URL before there was a list; it must redirect,
// not 404, because it was linked from the home page.
srv.CheckStatus("/demo", "301");
// ── redirects ─────────────────────────────────────────────────────
{
const auto blog = srv.Get("/blog/hello-world");
const auto it = blog.headers.find("location");
Check(it != blog.headers.end() && it->second.starts_with("/posts"),
"/blog/* sends Location: /posts",
it == blog.headers.end() ? "no location header" : it->second);
}
{
const auto demo = srv.Get("/demo");
const auto it = demo.headers.find("location");
Check(it != demo.headers.end() && it->second.starts_with("/demos"),
"/demo sends Location: /demos",
it == demo.headers.end() ? "no location header" : it->second);
}
// ── headers ───────────────────────────────────────────────────────
srv.HeaderHas("/", "x-content-type-options", "nosniff", "nosniff on pages");
srv.HeaderHas("/", "cache-control", "public", "pages are cacheable");
srv.HeaderHas("/nope", "x-robots-tag", "noindex", "404 is noindex");
srv.HeaderHas("/feed.xml", "content-type", "application/atom", "feed content-type");
srv.HeaderHas("/sitemap.xml", "content-type", "application/xml", "sitemap content-type");
// ── sitemap and feed content ──────────────────────────────────────
srv.BodyHas("/sitemap.xml", "/shop/fp6-pmos", "sitemap lists the product");
srv.BodyHas("/sitemap.xml", "/shop/fp6plus-pmos",
"sitemap lists the refresh — it comes off the catalogue, not a second list");
srv.BodyHas("/sitemap.xml", "/about", "sitemap lists the about page");
srv.BodyHas("/sitemap.xml", "/legal/privacy", "sitemap lists the privacy page");
srv.BodyHas("/sitemap.xml", "/demos", "sitemap lists the demos page");
srv.BodyHas("/sitemap.xml", "/financials", "sitemap lists the financials page");
srv.BodyLacks("/sitemap.xml", "/blog", "sitemap does not advertise the redirect");
srv.BodyLacks("/sitemap.xml", "/order", "sitemap does not advertise order pages");
srv.BodyHas("/feed.xml", "<feed xmlns=\"http://www.w3.org/2005/Atom\">", "feed is Atom");
// ── abuse ─────────────────────────────────────────────────────────
srv.CheckStatus("/shop/fp6-pmos", "413", "POST",
"email=a%40b.example&name=" + std::string(20000, 'x')
+ "&street=x&postal=1&city=y&country=NL");
{
const auto json = srv.Post("/shop/fp6-pmos", "{}", "application/json");
Check(json.status == "415", "POST with a JSON content-type -> 415", json.status);
}
srv.CheckStatus("/shop/nope", "404", "POST",
"email=a%40b.example&name=Ada&street=x&postal=1&city=y&country=NL");
srv.CheckStatus("/projects", "405", "POST", "x=1");
// HEAD must not be a 500 or a body — some crawlers use it exclusively.
srv.CheckStatus("/", "200", "HEAD");
{
const auto head = srv.Head("/");
Check(head.body.empty(), "HEAD carries no body");
}
return Finish();
}