From 140d1ec81f48fd367c30966bc960fba4c48fa0de Mon Sep 17 00:00:00 2001 From: imperosol Date: Fri, 22 May 2026 14:46:40 +0200 Subject: [PATCH] clean invalid items from eboutic baskets --- .../static/bundled/eboutic/eboutic-index.ts | 28 +++++++++++-------- eboutic/templates/eboutic/eboutic_main.jinja | 12 +++++++- eboutic/tests/test_basket.py | 17 ++++------- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/eboutic/static/bundled/eboutic/eboutic-index.ts b/eboutic/static/bundled/eboutic/eboutic-index.ts index 18573158..59a258e3 100644 --- a/eboutic/static/bundled/eboutic/eboutic-index.ts +++ b/eboutic/static/bundled/eboutic/eboutic-index.ts @@ -11,7 +11,7 @@ const BASKET_CACHE_KEY = "basket"; const BASKET_CACHE_VERSION = 1; document.addEventListener("alpine:init", () => { - Alpine.data("basket", (lastPurchaseTime?: number) => ({ + Alpine.data("basket", (validPrices: number[], lastPurchaseTime?: number) => ({ basket: [] as BasketItem[], init() { @@ -19,15 +19,6 @@ document.addEventListener("alpine:init", () => { this.$watch("basket", () => { this.saveBasket(); }); - // Invalidate basket if a purchase was made - if (lastPurchaseTime !== null && localStorage.basketTimestamp !== undefined) { - if ( - new Date(lastPurchaseTime) >= - new Date(Number.parseInt(localStorage.basketTimestamp, 10)) - ) { - this.basket = []; - } - } document .getElementById("id_form-TOTAL_FORMS") .setAttribute(":value", "basket.length"); @@ -37,7 +28,22 @@ document.addEventListener("alpine:init", () => { const cached = versionedLocalStorage.getItem(BASKET_CACHE_KEY, { version: BASKET_CACHE_VERSION, }); - return cached ?? []; + if (!cached) { + return []; + } + if ( + lastPurchaseTime !== null && + localStorage.basketTimestamp !== undefined && + new Date(lastPurchaseTime) >= + new Date(Number.parseInt(localStorage.basketTimestamp, 10)) + ) { + // Invalidate basket if a purchase was made + return []; + } + // The basket is cached and not expired, so return it, + // but without items that are invalid + // (e.g. because the product is archived, or sold out) + return cached.filter((item) => validPrices.includes(item.priceId)); }, saveBasket() { diff --git a/eboutic/templates/eboutic/eboutic_main.jinja b/eboutic/templates/eboutic/eboutic_main.jinja index 869c79b1..4962d5ce 100644 --- a/eboutic/templates/eboutic/eboutic_main.jinja +++ b/eboutic/templates/eboutic/eboutic_main.jinja @@ -30,7 +30,17 @@ {% block content %}

{% trans %}Eboutic{% endtrans %}

-
+

Panier

diff --git a/eboutic/tests/test_basket.py b/eboutic/tests/test_basket.py index 43c249f8..7abb0984 100644 --- a/eboutic/tests/test_basket.py +++ b/eboutic/tests/test_basket.py @@ -1,6 +1,8 @@ +import re from datetime import datetime, timezone import pytest +from bs4 import BeautifulSoup from django.http import HttpResponse from django.test import TestCase from django.test.client import Client @@ -130,9 +132,11 @@ def test_eboutic_basket_expiry( _bulk_create=True, ) + soup = BeautifulSoup(client.get(reverse("eboutic:main")).text, "lxml") assert ( - f'x-data="basket({int(expected.timestamp() * 1000) if expected else "null"})"' - in client.get(reverse("eboutic:main")).text + # remove any space from the value before asserting + re.sub(r"\s+", "", soup.find(id="eboutic").attrs["x-data"]) + == f"basket([],{int(expected.timestamp() * 1000) if expected else 'null'},)" ) @@ -243,15 +247,6 @@ class TestEboutic(TestCase): assert response.status_code == 200 assert Basket.objects.first() is None - self.client.force_login(self.new_customer) - response = self.submit_basket([BasketItem(self.cotiz.id, 1)]) - assert response.status_code == 200 - assert Basket.objects.first() is None - - response = self.submit_basket([BasketItem(self.not_in_counter.id, 1)]) - assert response.status_code == 200 - assert Basket.objects.first() is None - def test_create_basket(self): self.client.force_login(self.new_customer) assertRedirects(