-
Notifications
You must be signed in to change notification settings - Fork 12
Measure card write size per-file, not per whole request body #6160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2201,6 +2201,76 @@ module('Integration | Store', function (hooks) { | |
| ); | ||
| }); | ||
|
|
||
| test('a small new card saves even when its resident linked graph would overflow the size limit if inlined', async function (assert) { | ||
| // Regression: the client size check used to measure the whole POST body, | ||
| // `included[]` and all. A new card that links to already-saved cards the | ||
| // tab has loaded serialises those cards into `included` — which the realm | ||
| // discards on write — so a tiny card could fail "Card size exceeds maximum" | ||
| // purely because of how much of the realm the tab happened to have resident. | ||
| let environmentService = getService('environment-service') as any; | ||
| let originalMaxSize = environmentService.cardSizeLimitBytes; | ||
| try { | ||
| // A saved, resident linked card large enough that inlining it into | ||
| // `included` would blow the limit, while the new card's own document is | ||
| // tiny. Saved under the realm's default ceiling before the client limit | ||
| // is lowered; the realm keeps its own (unchanged) ceiling throughout. | ||
| let bigFriend = new PersonDef({ name: 'x'.repeat(6000) }); | ||
| let savedFriend = await (storeService as any).persistAndUpdate(bigFriend); | ||
| assert.true(isCardInstance(savedFriend), 'the large linked card saved'); | ||
|
|
||
| environmentService.cardSizeLimitBytes = 2500; | ||
|
|
||
| let instance = new PersonDef({ name: 'Small' }); | ||
| (instance as any).bestFriend = bigFriend; | ||
|
|
||
| let result = await (storeService as any).persistAndUpdate(instance); | ||
| assert.true( | ||
| isCardInstance(result), | ||
| "a new card whose own document is under the limit saves regardless of how large a graph it links to — the check measures what the realm stores, not the tab's loaded `included`", | ||
| ); | ||
| let cardPath = `${(instance as any).id.substring( | ||
| testRealmURL.length, | ||
| )}.json`; | ||
| assert.ok( | ||
| await testRealmAdapter.openFile(cardPath), | ||
| 'the realm holds the created card', | ||
| ); | ||
|
Comment on lines
+2226
to
+2237
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Claude Code 🤖] This pins only the permissive half of the new filter. Replace the filter body with Ask: add the counterpart — a small new card whose unsaved link is over the (lowered) ceiling, asserting Non-blocking on the fix itself; blocking on the PR's test claim, since as written the new test can't distinguish this fix from having deleted the Generated by Claude Code
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Claude Code 🤖] Counterpart test added in a59c285: a card saved alongside an oversized unsaved link is refused, asserting the error names the size limit — the case where dropping the |
||
| } finally { | ||
| environmentService.cardSizeLimitBytes = originalMaxSize; | ||
| } | ||
| }); | ||
|
|
||
| test('an oversized unsaved link created alongside a card still fails the size check', async function (assert) { | ||
| // The counterpart that makes the per-resource filter load-bearing: a | ||
| // `lid`-bearing side-load becomes its own file on the realm, so it must | ||
| // keep being measured. If the check stopped measuring included members | ||
| // entirely, this save would sail through client-side and 413 on the | ||
| // realm instead. | ||
| let environmentService = getService('environment-service') as any; | ||
| let originalMaxSize = environmentService.cardSizeLimitBytes; | ||
| try { | ||
| environmentService.cardSizeLimitBytes = 2500; | ||
|
|
||
| let bigUnsaved = new PersonDef({ name: 'x'.repeat(6000) }); | ||
| let instance = new PersonDef({ name: 'Small' }); | ||
| (instance as any).bestFriend = bigUnsaved; | ||
|
|
||
| let result = await (storeService as any).persistAndUpdate(instance); | ||
| assert.false( | ||
| isCardInstance(result), | ||
| 'the save is refused: the unsaved link is co-created as its own file and is over the ceiling', | ||
| ); | ||
| assert.ok( | ||
| String((result as any)?.message).includes( | ||
| 'exceeds maximum allowed size', | ||
| ), | ||
| `the error names the size limit (got: ${(result as any)?.message})`, | ||
| ); | ||
| } finally { | ||
| environmentService.cardSizeLimitBytes = originalMaxSize; | ||
| } | ||
| }); | ||
|
|
||
| test('a save overlapping a create PATCHes instead of issuing a second POST', async function (assert) { | ||
| // Driven through `persistAndUpdate` rather than `save`, because the | ||
| // autosave queue awaits the in-flight mutation before it saves at all — | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Claude Code 🤖]
lidis only half of what the realm skips — it also drops any resource whosemeta.realmURLisn't this realm, on both write paths (realm.ts, the card POST handler'sresource.meta.realmURL && ensureTrailingSlash(...) !== this.urltest in the same loop as thelidtest; andnamesForeignRealmincard-operations/executors.ts, in both the create and the update side-load loops). So alid-bearing side-load naming another realm is measured here and discarded there — the client can refuse a write the realm would have accepted.Narrow in practice, so non-blocking, but the comment above states this mirrors the realm's per-file
assertWriteSize, and the next person will build on that. Either add the realm check to the filter, or say in the comment that the client is deliberately the stricter of the two.Regression class: new logic in this PR, not a pre-existing bug.
Generated by Claude Code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Claude Code 🤖] Went with the documented-stricter option, in a59c285: the comment now states the foreign-realm
lidedge explicitly — measured here, silently skipped (never written) by the realm — and why the client does not reproduce that skip: it is a request defect the realm swallows, not a contract. Matching it would mean faithfully reproducing a silent drop.