chore: eliminate array coercion in fillStoreCache path construction#2973
chore: eliminate array coercion in fillStoreCache path construction#2973joonas wants to merge 3 commits into
fillStoreCache path construction#2973Conversation
`Storage.clear()` collects all store keys into a single `string[]`
and dispatches one `("remove", ["key1", "key2", ...])` call through
to `fillStoreCache`.
The path construction placed `cacheItem.key` as a single element inside
another array literal:
[`/data/${capabilityName}`, cacheItem.version, cacheItem.key]
When `.join("-")` encountered the nested array, it called
`.toString()`, coercing the keys into a comma-separated string and
producing one malformed JSON Patch path like
`/data/cap-v2-key1,v2-key2,v2-key3` instead of three individual
remove operations. The Kubernetes API rejects or misapplies this
path, so `Storage.clear()` never actually removed anything from
the PeprStore resource.
Move path computation out of the shared preamble and into each
branch:
- The `remove` branch now iterates over `cacheItem.key`, computing
a separate path and cache entry per key.
- The `add` branch uses `cacheItem.key[0]` (a `string`) instead of
`cacheItem.key` (a `string[]`), and guards with an explicit
length check to prevent the same class of bug from reappearing
silently.
The `migrateStore` test expectations are updated to match the
corrected paths — the old trailing dashes (e.g.,
`remove:/data/hello-pepr-example-1-`) were artifacts of the array
coercion producing `[""].toString()` → `""` which survived the
`.filter()` as a nested array but became an empty segment after
`.join("-")`.
Signed-off-by: Joonas Bergius <joonas@defenseunicorns.com>
Greptile SummaryThis PR fixes Confidence Score: 5/5Safe to merge — the fix is correct, well-scoped, and regression-tested; the single comment is a minor test-precision suggestion. All remaining findings are P2 style suggestions that do not affect correctness or data integrity. The core bug fix and path construction logic are sound, and the new multi-key test provides direct regression coverage. migrateStore.test.ts — the stringMatching overlap is low-risk but worth tightening before future refactors obscure it. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["fillStoreCache(cache, capabilityName, op, cacheItem)"] --> B{op}
B -->|"add"| C{key.length === 1?}
C -->|No| D["throw Error: ADD expects exactly one key"]
C -->|Yes| E["path = join(['/data/'+capabilityName, version, key[0]])"]
E --> F["cache[add:path:value] = {op, path, value}"]
B -->|"remove"| G{key.length >= 1?}
G -->|No| H["throw Error: Key is required"]
G -->|Yes| I["for each key in cacheItem.key"]
I --> J["path = join(['/data/'+capabilityName, version, key])"]
J --> K["cache[remove:path] = {op, path}"]
K --> I
B -->|other| L["throw Error: Unsupported operation"]
F --> M["return cache"]
K --> M
Reviews (1): Last reviewed commit: "Merge branch 'main' into fix/storage-cle..." | Re-trigger Greptile |
| expect.stringMatching("remove:/data/hello-pepr-example-1"), | ||
| expect.stringMatching("add:/data/hello-pepr-example-1-v2:was-here"), |
There was a problem hiding this comment.
Test matcher is too broad — may silently pass even if one remove is missing
expect.stringMatching("remove:/data/hello-pepr-example-1") is a substring match, so it also matches "remove:/data/hello-pepr-example-1-data". With arrayContaining, both matchers could satisfy themselves against the same "-data" key, meaning the test would pass even if the bare "remove:/data/hello-pepr-example-1" entry was never emitted. Using an anchored regex prevents this ambiguity.
| expect.stringMatching("remove:/data/hello-pepr-example-1"), | |
| expect.stringMatching("add:/data/hello-pepr-example-1-v2:was-here"), | |
| expect.stringMatching(/^remove:\/data\/hello-pepr-example-1$/), | |
| expect.stringMatching("add:/data/hello-pepr-example-1-v2:was-here"), |
fillStoreCache path constructionfillStoreCache path construction
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2973 +/- ##
==========================================
- Coverage 77.40% 77.37% -0.04%
==========================================
Files 93 93
Lines 2536 2541 +5
Branches 532 533 +1
==========================================
+ Hits 1963 1966 +3
- Misses 446 447 +1
- Partials 127 128 +1
🚀 New features to boost your workflow:
|
Description
Storage.clear()collects all store keys into a singlestring[]and dispatches one("remove", ["key1", "key2", ...])call through tofillStoreCache.The path construction placed
cacheItem.keyas a single element inside another array literal:When
.join("-")encountered the nested array, it called.toString(), coercing the keys into a comma-separated string and producing one malformed JSON Patch path like/data/cap-v2-key1,v2-key2,v2-key3instead of three individual remove operations. The Kubernetes API rejects or misapplies this path, soStorage.clear()never actually removed anything from the PeprStore resource.Move path computation out of the shared preamble and into each branch:
The
removebranch now iterates overcacheItem.key, computing a separate path and cache entry per key.The
addbranch usescacheItem.key[0](astring) instead ofcacheItem.key(astring[]), and guards with an explicit length check to prevent the same class of bug from reappearing silently.The
migrateStoretest expectations are updated to match the corrected paths — the old trailing dashes (e.g.,remove:/data/hello-pepr-example-1-) were artifacts of the array coercion producing[""].toString()→""which survived the.filter()as a nested array but became an empty segment after.join("-").End to End Test:
(See Pepr Excellent Examples)
Type of change
Checklist before merging