Repository navigation
Notify only a host that can listen - #96
Conversation
`saveNotification()` and `getGmtDate()` belong to the module, not to this library, and composer cannot hold the pair together: the module requires a minimum library version, a library cannot require a minimum module version, and the module's constraint is a floor with no ceiling. So `composer update` on an old installation pairs whatever module is on disk with the newest library published, and an app/code install pairs the two with no constraint at all. `saveNotification()` arrived in the module on 2024-12-03, in 103.4.65, and the calls to it landed here the day after, first tagged 3.0.42. Every module below that version has been a fatal on both call sites since: on the answer, in Mailchimp::call(), and on the failure, in Mailchimp_Error::getFriendlyMessage(). The error path is the worse of the two. It is reached because Mailchimp answered 4xx, so an installation that cannot listen loses the message explaining the failure and the request that was carrying it, at the moment it needs both most. Guarded per method rather than per helper, which is the discipline the telemetry already follows in readContactAllowed() and readModuleVersion(). getGmtDate() has been in the module since 2018 and is guarded for the same reason rather than because it is likely to be absent: nothing here can assume the shape of the host it was handed. The test covers the error lane directly, through the public getFriendlyMessage(), and asserts the rule over src for the lane inside call() that cannot be reached without the network -- a test that needed Mailchimp to answer would be skipped exactly when it mattered.
Raised in review. The scan listed the three files that call the helper today, which is blind to exactly the case it exists for: a file added later. It now walks src recursively -- scandir and recursion rather than the SPL iterators, which were build-optional before 5.3, and this runner declares the same php >=5.2.0 the library does. Checked by dropping an unguarded call into a new class under a new directory: the failure names it, and the count goes from 64 files to 65. The second half is a comment, not code. The match is per file, not per call site: a guard on a method counts for that method anywhere in the same file, so one guard covers two calls to the same method. Reading a guard back to the call it protects needs a parser, and this runner has none by design. Since the argument for the test is that it pins the rule, what it pins should be stated at its real width -- no file calls a helper method it has never checked for.
|
Both taken, in The hardcoded list. You are right that it was the one blind spot the test exists to cover, and the description was the part that was true — the code lagged it. It now walks Checked rather than assumed: dropping an unguarded Per file, not per call site. Also right, and worth having in writing since the argument for the test is that it pins the rule. Reading a guard back to the call it protects needs a parser, which this runner cannot have, so the comment now states the narrower thing it actually proves — no file calls a helper method it has never checked for — and names the case where that is weaker than per-call-site: one method, two calls, one guard, which is what the current sources look like. Against |
|
Verified Run 2 is the one that matters: the file did not exist, its directory did not exist, and the scan found it. The 64 → 65 count reproduces exactly. Run 3 confirms the assertion did not go soft while being widened — it still fails loudly on sources without the guards. The And narrowing the comment to "no file calls a helper method it has never checked for", with the weak case named, is better than the broader claim it replaced. A test that overstates what it proves is worse than one that proves less, because the next person trusts it. LGTM. |
dipola
left a comment
There was a problem hiding this comment.
Verified caae96a rather than read it — the scan now catches an unguarded call in a file and a directory that did not exist before, and it still fails against the pre-fix sources, so it was widened without going soft.
The scandir() choice keeps the php >=5.2.0 promise the runner makes, which RecursiveDirectoryIterator would have quietly broken.
Good fix, and the error path was the right one to prioritise.
The library calls back into the host's helper in four places. Two of them assume a method the host may not have, and have done since 3.0.42.
What breaks, and for whom
saveNotification()andgetGmtDate()belong to the module. Composer cannot hold that pairing together:>=3.0.52today,>=3.0.38when this landed).So
composer updateon an old installation pairs whatever module is on disk with the newest library published, and an app/code install — extension by clone, library by separate require — pairs the two under no constraint at all.saveNotification()arrived in the module on 2024-12-03, in 103.4.65. The calls to it landed here the day after, first tagged 3.0.42. Every module below 103.4.65 has been a fatal on both call sites ever since:Between them that is most of what the extension does.
The error path is the worse of the two. It is reached because Mailchimp answered 4xx, so an installation that cannot listen loses the message explaining the failure and the request that was carrying it, at the moment it needs both most. A host with no way to be notified is not an error to raise; it is a host to leave alone.
Shape
Guarded per method, not per helper — the discipline the telemetry already follows in
readContactAllowed()andreadModuleVersion().getGmtDate()has been in the module since 2018 and is guarded for the same reason rather than because it is likely to be absent: nothing here can assume the shape of the host it was handed.Nothing changes for a current module. The payload, the call and the order are the same; only the assumption is gone.
Test
The error lane is covered directly through the public
getFriendlyMessage(), across four host shapes — both methods, neither,getGmtDate()only (the real shape of a module below 103.4.65), and no helper at all — plusMailchimp_HttpError, which inherits the guard.The lane inside
Mailchimp::call()needs a curl round trip to Mailchimp to reach, and a test that needed the network to prove a guard would be skipped exactly when it mattered. So the rule is asserted over the source instead: every$this->helper->x()insrcmust be guarded on the method it names, in every file, including ones added later. Without this change that assertion names all four call sites.Verified against a real installation as well, with a helper carrying
getGmtDate()and nosaveNotification(), on a call that answers 200:Not in scope
The library cannot tell a host that a notification was dropped, and does not try. A module too old to store one is also too old to be told about it.