Skip to content

Commit ce2d5b6

Browse files
authored
fix(controller): namespace pagination handles under request.wheels.$pagination (#3340)
`setPagination()` / `pagination()` keyed themselves on a caller-supplied handle name (default `"query"`), written straight into `request.wheels`. CFML struct keys are case-insensitive, so a handle matching a framework-owned key collided with it in both directions: - Write: `setPagination(handle="tenant")` overwrote the resolved tenant context with a pagination struct; `handle="$queryCache"` did the same to the per-request finder cache. - Read: `pagination()` only validates the handle when `showErrorInformation` is on, so in production an unknown handle that happened to name a framework key returned that key's struct as though it were pagination state. Handles now live under the reserved `request.wheels.$pagination` sub-struct — same fix shape as #3336, which moved the finder cache to `request.wheels.$queryCache`. `$ensurePaginationStore()` returns the namespace struct so callers work through the returned reference rather than a bare statement. That matters: Adobe CF 2025's parser rejects a zero-argument call routed through the `application` scope in statement position, failing at COMPILE time with `CFMLParserBase$MissingNameException: Invalid construct: Either argument or name is missing`. Because the core suite compiles via `directory="wheels.tests.specs"`, one occurrence zeroed the entire adobe2025 leg — `tests="0"` on all six databases while every other engine stayed green. Adobe attributes the error to the enclosing `describe(...)` line, not the offending statement. Documented as cross-engine invariant 16, scoped to the trigger actually verified and listing the compiling counter-examples so nobody "fixes" working code. 7 regression specs in `paginationHandleCollisionSpec`. Verified across the full compatibility matrix by manual dispatch (run 30823803027): testcase delta exactly +7 on every engine x DB leg, and zero failures or errors present on this branch that are not also on develop — including all five running adobe2025 legs. Closes #3339
1 parent af69eb5 commit ce2d5b6

8 files changed

Lines changed: 232 additions & 36 deletions

File tree

CLAUDE.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,20 @@ The framework must run on Lucee 5/6/7, Adobe CF 2018/2021/2023/2025, and BoxLang
5454

5555
15. **A parameter named `request` makes the bare `request` token resolve inconsistently on Adobe 2025.** In a function declaring a parameter named `request`, Adobe CF 2025 can resolve bare `request` to the built-in scope in one expression position and to `arguments.request` in another *within the same function* — so a guard written one way cannot protect an access written the other way. `if (StructKeyExists(request, "wheels")) { StructDelete(request.wheels, "tenant"); }` passed the guard and then threw `Element WHEELS is undefined in REQUEST`. Use `IsDefined("request.wheels.tenant")`, which string-resolves the whole dotted path in one evaluation, or assign before use (`if (!StructKeyExists(request, "wheels")) { request.wheels = {}; }` then write) — never mix the two forms. This hits **every middleware component**, because `wheels.middleware.MiddlewareInterface` mandates the signature `handle(required struct request, required any next)`; anti-pattern 11's "never name a parameter after a reserved scope" is unavailable there. Lucee 6/7, BoxLang and Adobe 2023 all resolve consistently, so **local Lucee green and Adobe 2023 smokes do NOT cover this** — only the Adobe 2025 matrix legs catch it, and `compat-matrix.yml` does not run on PRs (weekly cron + `workflow_dispatch`, `continue-on-error: true`). Hit by `TenantResolver.handle()` in [#3338](https://github.com/wheels-dev/wheels/pull/3338).
5656

57+
16. **A zero-argument call through the `application` scope breaks Adobe 2025's parser in statement position.** Inside a closure, `application.wo.$someMethod()` with an **empty** argument list — used as a bare statement or as the whole right-hand side of an assignment — throws at COMPILE time: `coldfusion.compiler.CFMLParserBase$MissingNameException: Invalid construct: Either argument or name is missing` ("When using named parameters to a function, each parameter must have a name"). Adobe appears to parse it as a script-style tag call and demand at least one attribute. This is the `application`-scope sibling of invariant 2. Verified boundaries — each of these compiles, so **do not "fix" them**:
58+
- any argument at all: `application.wo.$get("showErrorInformation")`
59+
- nested inside another call: `expect(application.wo.$statusCode()).toBe(418)` (long-standing in `renderingSpec`)
60+
- chained further: `application.wo.mapper().resources("posts")` (`RoutePrecedenceSpec`)
61+
- a non-`application` receiver, zero args, bare statement in a closure: `_controller.$clearCachableActions()` (`cachingSpec`), `strategy.logout()` (`SessionStrategySpec`), `local.c.$warnIfConfigSkipsSuper()` (`configSuperWarningSpec`)
62+
63+
Two things make this expensive to diagnose. Adobe attributes the error to the **enclosing `describe(...)` line**, not the offending statement, so it reads like a broken test-block signature. And because the core suite compiles via `directory="wheels.tests.specs"`, one occurrence zeroes out **the entire engine leg** — adobe2025 reports `tests="0"` for every database while Lucee/BoxLang/Adobe 2023 stay green, and `compat-matrix.yml` does not run on PRs. In test code, ensure request state inline (`if (!StructKeyExists(request.wheels, "$pagination")) { request.wheels["$pagination"] = {} }`) rather than calling a void `$`-helper through `application.wo`; in framework code prefer helpers that **return** what they ensure, so callers write `local.store = $ensurePaginationStore();`. Hit by the #3339 pagination-namespace specs.
64+
65+
Bisect this class of bug with a single probe against a running container instead of CI (~13s vs ~19min):
66+
```bash
67+
curl -s "http://localhost:62025/wheels/core/tests?db=sqlite&format=json&cli=true" | \
68+
python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('totalPass','COMPILE FAIL'), d.get('RootCause',{}).get('snippet',''))"
69+
```
70+
5771
Verify Adobe CF fixes locally before pushing — don't iterate via CI:
5872
```bash
5973
curl -s "http://localhost:62023/wheels/core/tests?db=mysql&format=json" | \
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Pagination handles are now stored under `request.wheels.$pagination[handle]` instead of directly in `request.wheels[handle]`. Handles are caller-supplied names, so the flat layout put arbitrary user input in the same case-insensitive keyspace as framework-owned request state, and the collision ran both ways. Writing: `setPagination(handle="tenant")` replaced the resolved tenant context with a pagination struct, and `handle="$queryCache"` did the same to the per-request finder cache — silently, since neither is validated. Reading: `pagination()` only checks that a handle exists when `showErrorInformation` is on, so in production an unknown handle that happened to name a framework key returned that key's struct as though it were pagination data. `request.wheels` currently holds around thirty-five framework-owned keys — including `params`, `execution`, `currentRoute`, `transactions`, `flashKeep` and `exception` — every one of which was reachable this way. Handles now resolve only inside their own sub-struct, so neither direction can cross over. `Wheels.QueryHandleNotFound` behaviour is unchanged (#3339)

vendor/wheels/Global.cfc

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3847,16 +3847,43 @@ return local.$wheels;
38473847
* @handle The handle given to the query to return pagination information for.
38483848
*/
38493849
public struct function pagination(string handle = "query") {
3850+
local.store = $ensurePaginationStore();
38503851
if ($get("showErrorInformation")) {
3851-
if (!StructKeyExists(request.wheels, arguments.handle)) {
3852+
if (!StructKeyExists(local.store, arguments.handle)) {
38523853
Throw(
38533854
type = "Wheels.QueryHandleNotFound",
38543855
message = "Wheels couldn't find a query with the handle of `#arguments.handle#`.",
38553856
extendedInfo = "Make sure your `findAll` call has the `page` argument specified and matching `handle` argument if specified."
38563857
);
38573858
}
38583859
}
3859-
return request.wheels[arguments.handle];
3860+
return local.store[arguments.handle];
3861+
}
3862+
3863+
/**
3864+
* Internal function.
3865+
* Creates the reserved per-request pagination namespace if it doesn't exist yet.
3866+
*
3867+
* Pagination handles are caller-supplied names, so storing them directly in `request.wheels`
3868+
* put arbitrary user input in the same case-insensitive keyspace as framework-owned request
3869+
* state. A handle matching a framework key overwrote it, and — because `pagination()` only
3870+
* validates the handle when `showErrorInformation` is on — production reads of an unknown
3871+
* handle returned whatever framework struct happened to occupy that key. Both directions are
3872+
* closed by confining handles to their own sub-struct (#3339, same fix shape as #3336).
3873+
*
3874+
* Returns the namespace struct so callers can work through the returned reference instead of
3875+
* calling this as a bare statement — Adobe CF 2025's parser rejects a bare dotted call like
3876+
* `application.wo.$ensurePaginationStore()` in a script statement position (see the cross-engine
3877+
* note in CLAUDE.md).
3878+
*/
3879+
public struct function $ensurePaginationStore() {
3880+
if (!StructKeyExists(request, "wheels")) {
3881+
request.wheels = {};
3882+
}
3883+
if (!StructKeyExists(request.wheels, "$pagination")) {
3884+
request.wheels["$pagination"] = {};
3885+
}
3886+
return request.wheels["$pagination"];
38603887
}
38613888

38623889
/**
@@ -3924,7 +3951,8 @@ return local.$wheels;
39243951

39253952
local.args = Duplicate(arguments);
39263953
StructDelete(local.args, "handle");
3927-
request.wheels[arguments.handle] = local.args;
3954+
local.store = $ensurePaginationStore();
3955+
local.store[arguments.handle] = local.args;
39283956
}
39293957

39303958
/**
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* Regression coverage for #3339.
3+
*
4+
* `setPagination()` / `pagination()` key themselves on a caller-supplied handle name (default
5+
* `"query"`), which used to be written straight into `request.wheels`. CFML struct keys are
6+
* case-insensitive, so a handle matching a framework-owned key collided with it in both
7+
* directions:
8+
*
9+
* 1. Write: `setPagination(handle="tenant")` overwrote the resolved tenant context with a
10+
* pagination struct. `handle="$queryCache"` did the same to the per-request finder cache.
11+
* 2. Read: `pagination()` only validates the handle when `showErrorInformation` is on, so in
12+
* production an unknown handle that happened to name a framework key returned that key's
13+
* struct as though it were pagination state.
14+
*
15+
* Handles now live under the reserved `request.wheels.$pagination` sub-struct. Same fix shape as
16+
* #3336, which moved the finder cache to `request.wheels.$queryCache`.
17+
*/
18+
component extends="wheels.WheelsTest" {
19+
20+
function run() {
21+
22+
g = application.wo;
23+
24+
describe("pagination handle / framework key collision (##3339)", () => {
25+
26+
// The whole core suite runs inside a single request, so request.wheels is shared across
27+
// spec files. Only ever remove this spec's own handles — deleting the $pagination
28+
// namespace wholesale would destroy handles other specs set up.
29+
ownHandles = "articles,comments,tenant,$queryCache,noSuchHandleXYZ";
30+
31+
// Ensure the namespace inline rather than calling g.$ensurePaginationStore(): a
32+
// zero-argument dotted call in statement position breaks Adobe CF 2025's parser.
33+
beforeEach(() => {
34+
originalShowErr = application.wheels.showErrorInformation;
35+
originalCacheSetting = application.wheels.cacheQueriesDuringRequest;
36+
StructDelete(request.wheels, "tenant");
37+
if (!StructKeyExists(request.wheels, "$pagination")) {
38+
request.wheels["$pagination"] = {};
39+
}
40+
paginationStore = request.wheels["$pagination"];
41+
for (var h in ListToArray(ownHandles)) {
42+
StructDelete(paginationStore, h, false);
43+
}
44+
})
45+
46+
afterEach(() => {
47+
application.wheels.showErrorInformation = originalShowErr;
48+
application.wheels.cacheQueriesDuringRequest = originalCacheSetting;
49+
StructDelete(request.wheels, "tenant");
50+
if (!StructKeyExists(request.wheels, "$pagination")) {
51+
request.wheels["$pagination"] = {};
52+
}
53+
paginationStore = request.wheels["$pagination"];
54+
for (var h in ListToArray(ownHandles)) {
55+
StructDelete(paginationStore, h, false);
56+
}
57+
})
58+
59+
it("stores handles under the reserved namespace, not the bare key", () => {
60+
g.setPagination(totalRecords = 100, currentPage = 2, perPage = 10, handle = "articles");
61+
62+
expect(StructKeyExists(request.wheels, "$pagination")).toBeTrue();
63+
expect(StructKeyExists(request.wheels["$pagination"], "articles")).toBeTrue();
64+
expect(StructKeyExists(request.wheels, "articles")).toBeFalse();
65+
})
66+
67+
it("round-trips pagination data through the namespace", () => {
68+
g.setPagination(totalRecords = 100, currentPage = 2, perPage = 10, handle = "articles");
69+
var pg = g.pagination("articles");
70+
71+
expect(pg.totalRecords).toBe(100);
72+
expect(pg.currentPage).toBe(2);
73+
expect(pg.perPage).toBe(10);
74+
expect(pg.totalPages).toBe(10);
75+
})
76+
77+
// Write direction — a handle named after a framework key must not clobber it.
78+
it("does not overwrite resolved tenant context when a handle is named tenant", () => {
79+
request.wheels.tenant = {id = "acme", dataSource = "tenant_acme", config = {}, "$locked" = true};
80+
81+
g.setPagination(totalRecords = 50, currentPage = 1, perPage = 25, handle = "tenant");
82+
83+
expect(IsDefined("request.wheels.tenant")).toBeTrue();
84+
expect(request.wheels.tenant.id).toBe("acme");
85+
expect(request.wheels.tenant.dataSource).toBe("tenant_acme");
86+
expect(g.$tenantDataSource()).toBe("tenant_acme");
87+
})
88+
89+
it("does not overwrite the finder cache namespace when a handle is named $queryCache", () => {
90+
application.wheels.cacheQueriesDuringRequest = true;
91+
model("author").findAll(where = "lastName = 'Djurner'");
92+
var cachedBefore = StructCount(request.wheels["$queryCache"]["author"]);
93+
94+
g.setPagination(totalRecords = 50, currentPage = 1, perPage = 25, handle = "$queryCache");
95+
96+
expect(StructKeyExists(request.wheels["$queryCache"], "author")).toBeTrue();
97+
expect(StructCount(request.wheels["$queryCache"]["author"])).toBe(cachedBefore);
98+
})
99+
100+
// Read direction — the case showErrorInformation hides in production.
101+
it("does not return a framework struct for an unknown handle when errors are hidden", () => {
102+
application.wheels.showErrorInformation = false;
103+
request.wheels.tenant = {id = "acme", dataSource = "tenant_acme", config = {}, "$locked" = true};
104+
105+
// Pre-fix this returned the tenant struct as though it were pagination data.
106+
// It must now fail to resolve rather than hand back foreign state.
107+
var result = {returnedTenant = false, threw = false};
108+
try {
109+
var pg = g.pagination("tenant");
110+
result.returnedTenant = IsStruct(pg) && StructKeyExists(pg, "dataSource");
111+
} catch (any e) {
112+
result.threw = true;
113+
}
114+
115+
expect(result.returnedTenant).toBeFalse();
116+
expect(result.threw).toBeTrue();
117+
})
118+
119+
it("still throws Wheels.QueryHandleNotFound for an unknown handle in development", () => {
120+
application.wheels.showErrorInformation = true;
121+
122+
expect(function() {
123+
g.pagination("noSuchHandleXYZ");
124+
}).toThrow("Wheels.QueryHandleNotFound");
125+
})
126+
127+
it("keeps distinct handles isolated from each other", () => {
128+
g.setPagination(totalRecords = 100, currentPage = 1, perPage = 10, handle = "articles");
129+
g.setPagination(totalRecords = 30, currentPage = 3, perPage = 5, handle = "comments");
130+
131+
expect(g.pagination("articles").totalRecords).toBe(100);
132+
expect(g.pagination("comments").totalRecords).toBe(30);
133+
expect(g.pagination("comments").currentPage).toBe(3);
134+
})
135+
136+
})
137+
138+
}
139+
}

vendor/wheels/tests/specs/controller/requestSpec.cfc

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,13 +154,22 @@ component extends="wheels.WheelsTest" {
154154
describe("Tests that pagination", () => {
155155

156156
beforeEach(() => {
157-
request.wheels["myhandle"] = {test = "true"}
157+
// Ensure the namespace inline rather than calling application.wo.$ensurePaginationStore():
158+
// a zero-argument dotted call in statement position breaks Adobe CF 2025's parser.
159+
if (!StructKeyExists(request.wheels, "$pagination")) {
160+
request.wheels["$pagination"] = {}
161+
}
162+
paginationStore = request.wheels["$pagination"]
163+
paginationStore["myhandle"] = {test = "true"}
158164
params = {controller = "dummy", action = "dummy"}
159165
_controller = application.wo.controller("dummy", params)
160166
})
161167

162168
afterEach(() => {
163-
StructDelete(request.wheels, "myhandle", false)
169+
// Delete only this spec's handle. The whole core suite runs in one request, so
170+
// wiping the shared $pagination namespace would destroy other specs' handles.
171+
paginationStore = request.wheels["$pagination"]
172+
StructDelete(paginationStore, "myhandle", false)
164173
})
165174

166175
it("handle exists", () => {

vendor/wheels/tests/specs/model/crudSpec.cfc

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,10 +1477,10 @@ component extends="wheels.WheelsTest" {
14771477
order = "id"
14781478
)
14791479

1480-
expect(request.wheels.pagination_test_1.CURRENTPAGE).toBe(1)
1481-
expect(request.wheels.pagination_test_1.TOTALPAGES).toBe(0)
1482-
expect(request.wheels.pagination_test_1.TOTALRECORDS).toBe(0)
1483-
expect(request.wheels.pagination_test_1.ENDROW).toBe(1)
1480+
expect(request.wheels["$pagination"].pagination_test_1.CURRENTPAGE).toBe(1)
1481+
expect(request.wheels["$pagination"].pagination_test_1.TOTALPAGES).toBe(0)
1482+
expect(request.wheels["$pagination"].pagination_test_1.TOTALRECORDS).toBe(0)
1483+
expect(request.wheels["$pagination"].pagination_test_1.ENDROW).toBe(1)
14841484
expect(e.recordcount).toBe(0)
14851485
})
14861486

@@ -1490,32 +1490,32 @@ component extends="wheels.WheelsTest" {
14901490
/* 1st page */
14911491
e = user.findAll(select = "id", perpage = "2", page = "1", handle = "pagination_test_2", order = "id")
14921492

1493-
expect(request.wheels.pagination_test_2.CURRENTPAGE).toBe(1)
1494-
expect(request.wheels.pagination_test_2.TOTALPAGES).toBe(3)
1495-
expect(request.wheels.pagination_test_2.TOTALRECORDS).toBe(5)
1496-
expect(request.wheels.pagination_test_2.ENDROW).toBe(2)
1493+
expect(request.wheels["$pagination"].pagination_test_2.CURRENTPAGE).toBe(1)
1494+
expect(request.wheels["$pagination"].pagination_test_2.TOTALPAGES).toBe(3)
1495+
expect(request.wheels["$pagination"].pagination_test_2.TOTALRECORDS).toBe(5)
1496+
expect(request.wheels["$pagination"].pagination_test_2.ENDROW).toBe(2)
14971497
expect(e.recordcount).toBe(2)
14981498
expect(e.id[1]).toBe(r.id[1])
14991499
expect(e.id[2]).toBe(r.id[2])
15001500

15011501
/* 2nd page */
15021502
e = user.findAll(perpage = "2", page = "2", handle = "pagination_test_3", order = "id")
15031503

1504-
expect(request.wheels.pagination_test_3.CURRENTPAGE).toBe(2)
1505-
expect(request.wheels.pagination_test_3.TOTALPAGES).toBe(3)
1506-
expect(request.wheels.pagination_test_3.TOTALRECORDS).toBe(5)
1507-
expect(request.wheels.pagination_test_3.ENDROW).toBe(4)
1504+
expect(request.wheels["$pagination"].pagination_test_3.CURRENTPAGE).toBe(2)
1505+
expect(request.wheels["$pagination"].pagination_test_3.TOTALPAGES).toBe(3)
1506+
expect(request.wheels["$pagination"].pagination_test_3.TOTALRECORDS).toBe(5)
1507+
expect(request.wheels["$pagination"].pagination_test_3.ENDROW).toBe(4)
15081508
expect(e.recordcount).toBe(2)
15091509
expect(e.id[1]).toBe(r.id[3])
15101510
expect(e.id[2]).toBe(r.id[4])
15111511

15121512
/* 3rd page */
15131513
e = user.findAll(perpage = "2", page = "3", handle = "pagination_test_4", order = "id")
15141514

1515-
expect(request.wheels.pagination_test_4.CURRENTPAGE).toBe(3)
1516-
expect(request.wheels.pagination_test_4.TOTALPAGES).toBe(3)
1517-
expect(request.wheels.pagination_test_4.TOTALRECORDS).toBe(5)
1518-
expect(request.wheels.pagination_test_4.ENDROW).toBe(5)
1515+
expect(request.wheels["$pagination"].pagination_test_4.CURRENTPAGE).toBe(3)
1516+
expect(request.wheels["$pagination"].pagination_test_4.TOTALPAGES).toBe(3)
1517+
expect(request.wheels["$pagination"].pagination_test_4.TOTALRECORDS).toBe(5)
1518+
expect(request.wheels["$pagination"].pagination_test_4.ENDROW).toBe(5)
15191519
expect(e.recordcount).toBe(1)
15201520
expect(e.id[1]).toBe(r.id[5])
15211521
})
@@ -1568,10 +1568,10 @@ component extends="wheels.WheelsTest" {
15681568
handle = "pagination_order_test_1"
15691569
)
15701570

1571-
expect(request.wheels.pagination_order_test_1.CURRENTPAGE).toBe(1)
1572-
expect(request.wheels.pagination_order_test_1.TOTALPAGES).toBe(13)
1573-
expect(request.wheels.pagination_order_test_1.TOTALRECORDS).toBe(250)
1574-
expect(request.wheels.pagination_order_test_1.ENDROW).toBe(20)
1571+
expect(request.wheels["$pagination"].pagination_order_test_1.CURRENTPAGE).toBe(1)
1572+
expect(request.wheels["$pagination"].pagination_order_test_1.TOTALPAGES).toBe(13)
1573+
expect(request.wheels["$pagination"].pagination_order_test_1.TOTALRECORDS).toBe(250)
1574+
expect(request.wheels["$pagination"].pagination_order_test_1.ENDROW).toBe(20)
15751575
})
15761576

15771577
it("works with renamed primary key", () => {
@@ -1590,10 +1590,10 @@ component extends="wheels.WheelsTest" {
15901590
where = "description1 LIKE '%photo%'"
15911591
)
15921592

1593-
expect(request.wheels.pagination_order_test_1.CURRENTPAGE).toBe(1)
1594-
expect(request.wheels.pagination_order_test_1.TOTALPAGES).toBe(13)
1595-
expect(request.wheels.pagination_order_test_1.TOTALRECORDS).toBe(250)
1596-
expect(request.wheels.pagination_order_test_1.ENDROW).toBe(20)
1593+
expect(request.wheels["$pagination"].pagination_order_test_1.CURRENTPAGE).toBe(1)
1594+
expect(request.wheels["$pagination"].pagination_order_test_1.TOTALPAGES).toBe(13)
1595+
expect(request.wheels["$pagination"].pagination_order_test_1.TOTALRECORDS).toBe(250)
1596+
expect(request.wheels["$pagination"].pagination_order_test_1.ENDROW).toBe(20)
15971597
})
15981598

15991599
it("works with parameterize set to false with numeric", () => {
@@ -1607,10 +1607,10 @@ component extends="wheels.WheelsTest" {
16071607
where = "id = 1"
16081608
)
16091609

1610-
expect(request.wheels.pagination_order_test_1.CURRENTPAGE).toBe(1)
1611-
expect(request.wheels.pagination_order_test_1.TOTALPAGES).toBe(1)
1612-
expect(request.wheels.pagination_order_test_1.TOTALRECORDS).toBe(1)
1613-
expect(request.wheels.pagination_order_test_1.ENDROW).toBe(1)
1610+
expect(request.wheels["$pagination"].pagination_order_test_1.CURRENTPAGE).toBe(1)
1611+
expect(request.wheels["$pagination"].pagination_order_test_1.TOTALPAGES).toBe(1)
1612+
expect(request.wheels["$pagination"].pagination_order_test_1.TOTALRECORDS).toBe(1)
1613+
expect(request.wheels["$pagination"].pagination_order_test_1.ENDROW).toBe(1)
16141614
})
16151615

16161616
it("works with compound keys", () => {

0 commit comments

Comments
 (0)