This commit is contained in:
parent
320af54b3d
commit
4666c1995f
14 changed files with 876 additions and 346 deletions
|
|
@ -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");
|
||||
|
|
|
|||
Loading…
Reference in a new issue