Skip to content

fix: make the suggest/permissions/datasafety/list guards actually run - #3

Open
arena-ai-coding-agent[bot] wants to merge 2 commits into
mainfrom
fix/options-validation
Open

arena-ai-coding-agent[bot] wants to merge 2 commits into
mainfrom
fix/options-validation

Conversation

@arena-ai-coding-agent

Copy link
Copy Markdown

Problem

Four methods guard their required option with && where || is meant:

if (!opts && !opts.appId) { throw Error('appId missing') }

!opts && !opts.appId can only be true when there is no options object — and in that case reading opts.appId blows up first. So the documented error can never escape and callers get an internals-leaking TypeError instead. Reproducible on main with no network access needed (the guard runs before the request):

call on main expected
gplay.suggest() TypeError: Cannot read properties of undefined (reading 'term') Error: term missing
gplay.permissions() TypeError: ... (reading 'appId') Error: appId missing
gplay.datasafety() TypeError: ... (reading 'appId') Error: appId missing
gplay.list() TypeError: ... (reading 'category') the default TOP_FREE/APPLICATION list
gplay.suggest({}) no error: fires a batchexecute request for the literal term undefined Error: term missing

list() is the worse one of the five: every option is documented as optional ("collection (optional, defaults to collection.TOP_FREE)", "category (optional, defaults to no category)"), yet the README-documented gplay.list() throws, because validate(opts) runs on the raw argument before the defaults are merged in. As a side effect validate() also wrote category/collection into the caller's options object.

app(), search(), reviews() and similar() already use the !opts || form — this lines the remaining ones up with them.

Fix

  • lib/suggest.js, lib/permissions.js, lib/datasafety.js: !opts && !opts.x → !opts || !opts.x, so the guard covers both "no object" and "empty object".
  • lib/list.js: merge the defaults into fullListOpts first and validate that, then use it for the request options, the throttle and parseCollectionApps as well (the caller's object is no longer mutated, and fullDetail lookups inherit the same lang/country/cache defaults as the list request itself).

Errors keep being raised inside the promise executor, so they stay rejections (same as the existing Invalid category / Invalid sort assertions in the suite) and no method's resolved value changes.

Testing

New regression tests in test/lib.suggest.js, test/lib.permissions.js, test/lib.datasafety.js and test/lib.list.js. The seven validation ones all fail on main and pass here; they need no network, so they are not subject to Play flakiness:

  Suggest method
    ✔ should throw a "term missing" error when the term is not given
    ✔ should throw a "term missing" error when no options are given
  Permissions method
    ✔ should throw an "appId missing" error when the appId is not given
    ✔ should throw an "appId missing" error when no options are given
  Data Safety method
    ✔ should throw an "appId missing" error when the appId is not given
    ✔ should throw an "appId missing" error when no options are given
  List method
    ✔ should not mutate the options object given by the caller
    ✔ should not need any option to fetch the default collection   (live)

agmgaffar and others added 2 commits August 28, 2026 07:44
Three methods guard their required option with `&&` instead of `||`:

    if (!opts && !opts.appId) { throw Error('appId missing') }

`!opts && !opts.appId` is only true when there is no options object at all,
and then reading `opts.appId` throws first. So the documented error can never
escape, and callers get an internals-leaking TypeError instead:

    gplay.permissions()    // TypeError: Cannot read properties of undefined (reading 'appId')
    gplay.datasafety()     // TypeError: Cannot read properties of undefined (reading 'appId')
    gplay.suggest()        // TypeError: Cannot read properties of undefined (reading 'term')
    gplay.list()           // TypeError: Cannot read properties of undefined (reading 'category')

The empty-object cases are just as bad in the other direction: `gplay.suggest({})`
skips the guard entirely and fires a batchexecute request for the literal term
`undefined`, which comes back as a "no results" answer instead of a usage error.

`app()`, `search()`, `reviews()` and `similar()` already use the `!opts ||`
form; this lines the other four up with them.

For `list()` every option is optional (README: "collection (optional, defaults
to collection.TOP_FREE)", "category (optional...)", ...), but `validate(opts)`
ran on the raw argument, so the documented `gplay.list()` call crashed while
`gplay.list({})` worked. Validation now runs on the object that already holds
the defaults, which also stops `list()` from writing `category`/`collection`
into the caller's options object on the way through.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
`npm audit` runs as a required step of the CI workflow and is red on
main (6 findings, all of them in transitive dev dependencies pulled in
by mocha and eslint). None of them is reachable from the published
runtime code, and none is fixable with a plain devDependency bump: even
mocha@11 pins vulnerable serialize-javascript/diff ranges.

Pin the affected packages through npm `overrides` and refresh the
lockfile so `npm ci && npm run lint && npm test && npm audit` passes
on the CI matrix (node 16/18/20). Runtime dependencies are untouched.

Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant