LF-5425: supportTicket POST test sends 11 form parts and trips the multer field limit - #4289
Conversation
…et test The POST helper expanded the fake attachment URL array into one multipart part per URL. With the 10-part field limit on multerDiskUpload, a fixture carrying 10 URLs produced 11 parts and the request was rejected with 400 before reaching the controller. The parts carried no coverage: they are text parts, so req.file is undefined either way, and addSupportTicket sets attachments to an empty array before the insert. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
This is not a very high-stakes ticket so I was using it to explore if I could get Claude to write a more explanatory/story-style PR description. I think I pushed it too far 😅
Here was the original wall of text; having puzzled over this description for a while, now the original reads totally fine to me, but I found it quite opaque at first.
Not a priority, but curious to know which of these two you find more readable -- or if you have a different style preference (e.g. only bullet points). Even better if you could generate that style and share how you prompted it 😁
Description
multerDiskUpload recently gained a fields: 10 limit. multer 2.0.2 forwards its limits object verbatim to busboy, and busboy counts multipart parts, not distinct field names — so ten parts all named _file_ count as ten, even though req.body ends up with a single _file_ key. The postRequest helper in the supportTicket test passed the fixture's attachment array to superagent's .field(), which expands an array into one part per element. mocks.fakeSupportTicket generates 0 to 10 attachment URLs inclusive, so the request carried between 1 and 11 parts. At 11, busboy raised LIMIT_FIELD_COUNT and the middleware returned 400 {"message":"attachment could not be uploaded"} before the controller ran. The test then failed twice over: the expect(res.status).toBe(201) assertion threw inside the superagent callback, so done() was never called and Jest also reported a 5 s timeout. Measured rate: 9.1 percent of runs.
This removes _file_: data.attachments from the helper. Those parts carried no coverage — they are text parts rather than file parts, so req.file was undefined either way, and addSupportTicket overwrites data.attachments with an empty array before the insert. The request now sends one part (data), the field count is constant, and every existing assertion is unchanged.
The production limit is correct and is deliberately left alone: the webapp sends exactly two parts, one _file_ and one data, and the farm notes tests send one field plus at most one attachment. The test was the only caller that exceeded the cap. The rule to carry forward is that limits.fields bounds parts on the wire, so any test that hands an array to .field() multiplies its part count invisibly at the call site. Separately, and not addressed here, the fieldNestingDepth: 2 option set alongside the field limit has no effect — neither multer 2.0.2 nor busboy reads that key, so the nesting-depth protection it was meant to provide is not actually in place; that is filed as its own ticket.
Description
Fixes the flakiness in the
Owner post support tickettest, which can fail on roughly one CI run in eleven since the upload limits were added (5987f69)What the mock is still describing
The test builds its request from
mocks.fakeSupportTicket(), which has built an array of image URLs since the endpoint first shipped:Those URLs matched the original design. The endpoint shipped in commit 30643878a (December 2020) inserting the POST body whole, and
attachmentswas — still is — typed as a list of URL strings:Six days later, commit 3ef07eb95 replaced that design with the upload-and-email one we have now. The same commit added
multerDiskUpload,.single('_file_'), the support email, and this line:So attachments are not uploaded to be discarded: the user's file goes out as an email attachment, and the column is written empty on every insert. Storing attachment URLs in the database is the part that was abandoned, and three traces survive: the schema above,
data.attachments = {}incomponents/Help/index.jsx, and the mock's array.Why those URLs now fail the request
The helper passes the mock's array to superagent's
.field(), which recurses over arrays: a mock holding ten URLs sends ten parts named_file_, plus thedatapart:multerDiskUpload, the only middleware on the route, caps a request at ten fields, and busboy counts parts as they arrive. Part 11 raisesLIMIT_FIELD_COUNTand the request gets a 400 beforeaddSupportTicketruns.Parts, not keys, is the easy thing to miss. Had the test's request parsed, those eleven parts would have collapsed into two keys, so from inside
addSupportTicketit would have looked like an ordinary two-field request:faker.datatype.number(10)is uniform over 0 to 10 inclusive, so ten URLs is one of eleven equally likely mocks: 9.1% of runs.Fix
Delete
_file_: data.attachmentsfrompostRequest, leaving a singledatapart.req.filestaysundefined, exactly as it was when the ten URL parts were there — meaning this test has never exercised the upload path. The real client sends the user's file as a second part, sofields: 10is nowhere near its limit and stays as it is.For reviewers
One more piece of the abandoned design, untouched:
supportTicketFactory(tests/mock.factories.js) is exported but never called anywhere, and it is the only code that would have inserted those URLs into the database.Jira link: https://lite-farm.atlassian.net/browse/LF-5425
Type of change
How Has This Been Tested?
Test-only change, no UI.
Owner post support ticketpasses locally; the suite also reports an unrelatedafterAlltimeout intableCleanup(knex)that every API test file shows on this machine.Checklist:
afterAlltimeout is described above