From 84bf85581098158984a6f10518a1fa20eb2f4aca Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Thu, 16 Jul 2026 11:53:00 +0100 Subject: [PATCH] fix: payment filter for time range (#4023) --- lnbits/db.py | 11 ++++++----- tests/unit/test_db.py | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/lnbits/db.py b/lnbits/db.py index e9e926add..dc50b36ce 100644 --- a/lnbits/db.py +++ b/lnbits/db.py @@ -497,7 +497,7 @@ class Filter(BaseModel, Generic[TFilterModel]): validated, errors = compare_field.validate(raw_value, {}, loc="none") if errors: raise ValidationError(errors=[errors], model=model) - values[f"{field}__{index}"] = validated + values[f"{field}__{i}_{index}"] = validated else: raise ValueError("Unknown filter field") @@ -510,11 +510,12 @@ class Filter(BaseModel, Generic[TFilterModel]): for key in self.values.keys() if self.values else []: if self.model and self.model.__fields__[self.field].type_ == datetime: placeholder = compat_timestamp_placeholder(key) - stmt.append(f"{prefix}{self.field} {self.op.as_sql} {placeholder}") - if self.op in {Operator.INCLUDE, Operator.EXCLUDE}: - stmt.append(f":{key}") else: - stmt.append(f"{prefix}{self.field} {self.op.as_sql} :{key}") + placeholder = f":{key}" + if self.op in {Operator.INCLUDE, Operator.EXCLUDE}: + stmt.append(placeholder) + else: + stmt.append(f"{prefix}{self.field} {self.op.as_sql} {placeholder}") if self.op in {Operator.INCLUDE, Operator.EXCLUDE}: statement = f"{prefix}{self.field} {self.op.as_sql} ({', '.join(stmt)})" diff --git a/tests/unit/test_db.py b/tests/unit/test_db.py index 94b4239f7..e0bf10bbf 100644 --- a/tests/unit/test_db.py +++ b/tests/unit/test_db.py @@ -1,4 +1,4 @@ -from datetime import date, timezone +from datetime import date, datetime, timezone import pytest @@ -9,9 +9,41 @@ from lnbits.core.crud import ( get_wallet_for_key, ) from lnbits.core.crud.payments import get_payment -from lnbits.core.models import CreateInvoice +from lnbits.core.models import CreateInvoice, PaymentFilters from lnbits.core.services.payments import create_wallet_invoice -from lnbits.db import POSTGRES +from lnbits.db import POSTGRES, SQLITE, Filter, Filters + + +@pytest.mark.parametrize( + ("db_type", "lower_statement", "upper_statement"), + [ + ( + POSTGRES, + "(time >= to_timestamp(:time__0_0))", + "(time <= to_timestamp(:time__1_0))", + ), + (SQLITE, "(time >= :time__0_0)", "(time <= :time__1_0)"), + ], +) +def test_datetime_filter_uses_database_timestamp_placeholder( + monkeypatch, db_type, lower_statement, upper_statement +): + monkeypatch.setattr("lnbits.db.DB_TYPE", db_type) + lower_bound = Filter.parse_query( + "time[ge]", ["2026-06-16T00:00:00"], PaymentFilters, 0 + ) + upper_bound = Filter.parse_query( + "time[le]", ["2026-06-23T23:59:59"], PaymentFilters, 1 + ) + filters = Filters(filters=[lower_bound, upper_bound], model=PaymentFilters) + values = filters.values() + + assert isinstance(values["time__0_0"], datetime) + assert values["time__0_0"] == datetime(2026, 6, 16) + assert values["time__1_0"] == datetime(2026, 6, 23, 23, 59, 59) + assert filters.where() == f"WHERE {lower_statement} AND {upper_statement}" + assert lower_bound.statement == lower_statement + assert upper_bound.statement == upper_statement @pytest.mark.anyio