markdown fix
All checks were successful
Deploy / build-deploy (push) Successful in 2m23s

This commit is contained in:
Jorijn van der Graaf 2026-08-18 11:12:24 +02:00
commit 4666c1995f
14 changed files with 876 additions and 346 deletions

View file

@ -166,20 +166,21 @@ void OpenShopLifecycle(TestServer& srv) {
}
// A non-EU order: ex-VAT goods, world shipping, and the indicative
// national currency line sourced from the build-time ECB rates. GB rather
// than a North American destination because those are refused outright.
const std::string tokenGb = TokenOf(srv.Post("/shop/fp6-pmos",
"email=gb%40example.org&name=Terry&street=1%20Baker%20St&postal=W1U&city=London&country=GB"));
Check(!tokenGb.empty(), "GB checkout issues an order");
if (!tokenGb.empty()) {
const std::string page = srv.Body(std::format("/order/{}", tokenGb));
// national currency line sourced from the build-time ECB rates. CH because
// it is the export destination that actually sells — North America is
// refused on insurance, and GB waits on its e-waste registrations.
const std::string tokenCh = TokenOf(srv.Post("/shop/fp6-pmos",
"email=ch%40example.org&name=Heidi&street=1%20Bahnhofstrasse&postal=8001&city=Zurich&country=CH"));
Check(!tokenCh.empty(), "CH checkout issues an order");
if (!tokenCh.empty()) {
const std::string page = srv.Body(std::format("/order/{}", tokenCh));
// €474.21 goods (green net) + €55 world shipping = €529.21
Check(page.find("€529.21") != std::string::npos,
"export order total is ex-VAT + world shipping");
Check(page.find("Zero-rated export") != std::string::npos,
"export order states the VAT treatment");
Check(std::regex_search(page, std::regex("£[0-9]+")),
"export order shows the indicative GBP amount");
Check(std::regex_search(page, std::regex("CHF [0-9]+")),
"export order shows the indicative CHF amount");
Check(page.find("indicative") != std::string::npos,
"conversion is labelled indicative");
}
@ -187,7 +188,7 @@ void OpenShopLifecycle(TestServer& srv) {
// A two-unit white export order: unit €665.38, line €1330.76, net from the
// LINE total (not per unit) = €1099.80, plus €55 world shipping = €1154.80.
const std::string tokenWhite = TokenOf(srv.Post("/shop/fp6-pmos",
"email=w%40example.org&name=W&street=X%201&postal=1&city=Y&country=GB&color=white&quantity=2"));
"email=w%40example.org&name=W&street=X%201&postal=1&city=Y&country=CH&color=white&quantity=2"));
Check(!tokenWhite.empty(), "white ×2 checkout issues an order");
if (!tokenWhite.empty()) {
const std::string page = srv.Body(std::format("/order/{}", tokenWhite));
@ -357,15 +358,15 @@ void OpenShopLifecycle(TestServer& srv) {
// The NL order's message, found by its own order link (the same email
// address placed two orders, so the address alone would be ambiguous).
std::string nlMail;
std::string gbMail;
std::string chMail;
for (const auto& entry : std::filesystem::directory_iterator(srv.Work())) {
const std::string name = entry.path().filename().string();
if (!name.starts_with("mail-") || !name.ends_with(".eml")) continue;
const std::string mail = ReadFile(entry.path());
if (mail.find(std::format("/order/{}", token)) != std::string::npos) nlMail = mail;
if (!tokenGb.empty()
&& mail.find(std::format("/order/{}", tokenGb)) != std::string::npos) {
gbMail = mail;
if (!tokenCh.empty()
&& mail.find(std::format("/order/{}", tokenCh)) != std::string::npos) {
chMail = mail;
}
}
Check(!nlMail.empty(), "a confirmation email links the NL order");
@ -392,7 +393,7 @@ void OpenShopLifecycle(TestServer& srv) {
}
}
// The export order's message states the VAT treatment its invoice carries.
Check(!gbMail.empty() && gbMail.find("zero-rated export") != std::string::npos,
Check(!chMail.empty() && chMail.find("zero-rated export") != std::string::npos,
"export confirmation states the zero-rated treatment");
// Idempotency comes from the ledger's notified event, not from luck in
@ -467,12 +468,12 @@ void AlwaysOnValidation(TestServer& srv) {
"email=a%40b.example&country=NL"); // missing address
srv.CheckStatus("/shop/fp6-pmos", "422", "POST", Good("&website=spam")); // honeypot
// Destinations the shop refuses (Money::NoSaleCountries). Well-formed,
// real addresses: the refusal is policy, not a shape check, so it has to
// hold for every spelling the form accepts. Deliberately outside the
// shop-open gate — validation runs before the coming-soon check, so this
// must answer 422 whether the shop is open or not, and it is the
// assertion that would catch the block being lost in a refactor.
// Destinations the shop refuses (Money::SellsTo). Well-formed, real
// addresses: the refusal is policy, not a shape check, so it has to hold for
// every spelling the form accepts. Deliberately outside the shop-open gate —
// validation runs before the coming-soon check, so this must answer 422
// whether the shop is open or not, and it is the assertion that would catch
// the gate being lost in a refactor.
const std::size_t before = LedgerLines(srv).size();
srv.CheckStatus("/shop/fp6-pmos", "422", "POST",
"email=us%40example.org&name=Pat&street=1%20Main%20St&postal=43004&city=Columbus&country=US");
@ -489,6 +490,31 @@ void AlwaysOnValidation(TestServer& srv) {
"email=by%40example.org&name=Vanya&street=1%20Kastrychnitskaya&postal=220030&city=Minsk&country=BY");
srv.CheckStatus("/shop/fp6-pmos", "422", "POST",
"email=ru%40example.org&name=Sasha&street=1%20Tverskaya&postal=125009&city=Moscow&country=ru");
// Destinations off the shipping allow-list (Money::ShipsTo). DE and GB are
// the pointed cases: the fixture PRICES both, so a rate exists and the parcel
// is postable — the refusal is entirely the policy's, which is the whole
// reason to assert these rather than a country the carrier never covered.
//
// Status only, like every other refusal in this function. The exact sentence
// is asserted in ShouldValidateForms, where the validator is called directly:
// this function runs in the coming-soon state too, and a closed shop renders
// no order form for a field error to land in. What is worth proving over real
// HTTP is that the refusal holds at all, in both states — which is what 422
// says here.
srv.CheckStatus("/shop/fp6-pmos", "422", "POST",
"email=de%40example.org&name=Klaus&street=1%20Hauptstr&postal=10115&city=Berlin&country=DE");
srv.CheckStatus("/shop/fp6-pmos", "422", "POST",
"email=gb%40example.org&name=Terry&street=1%20Baker%20St&postal=W1U&city=London&country=GB");
srv.CheckStatus("/shop/fp6-pmos", "422", "POST",
"email=no%40example.org&name=Kari&street=1%20Karl%20Johans&postal=0154&city=Oslo&country=NO");
srv.CheckStatus("/shop/fp6-pmos", "422", "POST",
"email=tr%40example.org&name=Emre&street=1%20Istiklal&postal=34430&city=Istanbul&country=TR");
srv.CheckStatus("/shop/fp6-pmos", "422", "POST",
"email=br%40example.org&name=Ana&street=1%20Paulista&postal=01310&city=Sao%20Paulo&country=BR");
// And the default that makes an allow-list worth having: a well-formed code
// nobody ever considered is refused without appearing on any list.
srv.CheckStatus("/shop/fp6-pmos", "422", "POST",
"email=zz%40example.org&name=Sam&street=1%20Main&postal=0000&city=Nowhere&country=ZZ");
// Refused in validation means nothing reached the ledger and no payment
// link was ever created.
Check(LedgerLines(srv).size() == before, "a refused destination creates no order record");