From b9765baef5574ccec20edbc00b7ef3a667eaef2e Mon Sep 17 00:00:00 2001 From: Vishal T <54330666+vish727@users.noreply.github.com> Date: Tue, 22 Sep 2026 00:12:27 -0400 Subject: [PATCH] fix: a where with two fields filters on both list() built the query with one where= parameter, overwritten per field, so only the last one survived. The server reads where as a repeated parameter and ands them, which is what nook.js has always sent. A caller asking for two conditions got the rows matching one of them and no indication anything had been dropped. Both helpers append now, and a test pins it in each. --- helpers/embed_test.go | 17 +++++++++++++++++ helpers/nook-data.js | 4 +++- helpers/nook_data.py | 14 ++++++++------ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/helpers/embed_test.go b/helpers/embed_test.go index 0910a8f..6b47478 100644 --- a/helpers/embed_test.go +++ b/helpers/embed_test.go @@ -33,3 +33,20 @@ func TestHelpersAreWhatTheSkillDescribes(t *testing.T) { } } } + +// A where with two fields has to send two where= parameters: the server and-s them, and one +// parameter would match on the last field only and quietly return rows the caller filtered out. +func TestWhereSendsOneParameterPerField(t *testing.T) { + if strings.Contains(Node, "q.set('where'") { + t.Error("nook-data.js overwrites where instead of appending") + } + if !strings.Contains(Node, "q.append('where'") { + t.Error("nook-data.js does not append where") + } + if strings.Contains(Python, `q["where"] =`) { + t.Error("nook_data.py overwrites where instead of appending") + } + if !strings.Contains(Python, "doseq=True") { + t.Error("nook_data.py does not encode repeated parameters") + } +} diff --git a/helpers/nook-data.js b/helpers/nook-data.js index 38ec0ec..6f861b4 100644 --- a/helpers/nook-data.js +++ b/helpers/nook-data.js @@ -41,7 +41,9 @@ class Nook { if (opts.limit) q.set('limit', opts.limit) if (opts.cursor) q.set('cursor', opts.cursor) if (opts.order) q.set('order', opts.order) - for (const [field, value] of Object.entries(opts.where || {})) q.set('where', `${field}:${value}`) + // append, not set: one where= per field is how the server reads them, and they are and-ed. + // Setting would quietly match on the last field only and return too much. + for (const [field, value] of Object.entries(opts.where || {})) q.append('where', `${field}:${value}`) const out = await this._call('GET', collection, null, q) return out.documents || [] } diff --git a/helpers/nook_data.py b/helpers/nook_data.py index a4324cc..78c4245 100644 --- a/helpers/nook_data.py +++ b/helpers/nook_data.py @@ -44,15 +44,17 @@ def __init__(self, as_token=None, user=None): def list(self, collection, limit=None, cursor=None, order=None, where=None): """Documents in a collection, newest last unless order='desc'.""" - q = {} + q = [] if limit: - q["limit"] = limit + q.append(("limit", limit)) if cursor: - q["cursor"] = cursor + q.append(("cursor", cursor)) if order: - q["order"] = order + q.append(("order", order)) + # One where= per field, which is how the server reads them: they are and-ed together. + # A single parameter would quietly match on the last field only and return too much. for field, value in (where or {}).items(): - q["where"] = "%s:%s" % (field, value) + q.append(("where", "%s:%s" % (field, value))) return self._call("GET", collection, params=q).get("documents", []) def get(self, collection, id): @@ -73,7 +75,7 @@ def _call(self, method, path, body=None, params=None): raise NookError(0, "NOOK_DATA_URL is not set: this is not running as a nook") url = self.base + urllib.parse.quote(path) if params: - url += "?" + urllib.parse.urlencode(params) + url += "?" + urllib.parse.urlencode(params, doseq=True) data = json.dumps(body).encode() if body is not None else None req = urllib.request.Request(url, method=method, data=data) req.add_header("Authorization", "Bearer " + self.token)