Skip to content

test: keep every GraphQL operation within the request limits of Unraid 7.4 - #337

Merged
1 commit merged into
mainfrom
test/graphql-request-limits
Nov 13, 2026
Merged

1 commit merged into
mainfrom
test/graphql-request-limits

Conversation

@nofuturekid

@nofuturekid nofuturekid commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #320

What changed and why

Unraid 7.4.0 refuses GraphQL requests above fixed limits. Nothing in NOVA noticed when a query grew; the first sign would have been a failed screen. This adds a CI guard and records the real numbers.

  • GraphqlRequestLimitsTest (unit test, runs in CI): measures every operation in app/src/main/graphql as Apollo sends it (the generated document, fragments and __typename included) by the server's formulas and fails, naming the operation and the measure, when one passes 70 % of a limit. It prints a table of all 45 operations. Nine more tests in the same class pin the re-implemented formulas to the server's own boundaries (1,000 fields pass and 1,001 fail, 100 aliases pass and 101 fail, depth 20 versus 21, fragments add no depth, first/last multiply but limit does not) and prove the gate can fail (an 800-field query is reported).
  • GraphqlDocumentMetrics.kt (test source set): a small hand-written lexer and parser plus the formulas. The Apollo compiler is a Gradle-plugin dependency and not on the test classpath, and graphql-java would be a new dependency, so neither is used. No dependency added.
  • HANDOFF.md: section "Unraid 7.4 request limits (Verify NOVA against the Unraid 7.4.0 request limits #320)" with limits, sources, margin and the live result, and a pointer in the gotchas list.

The limits and where they come from

Read from the source, not from memory: unraid/api commit 8e587fe (2026-11-04), api/src/unraid-api/graph/request-protection.ts and graph.module.ts, configuring @escape.tech/graphql-armor 3.2.0 (cost-limit 2.4.3, max-depth 2.4.2, max-aliases 2.6.2) and graphql 16.11.0. The formulas were taken from the published plugin code.

Measure Limit Counted as
Cost 1,000 object cost 1, scalar cost 1, depth factor 1, fragments flattened, introspection not ignored; integer first/last multiply
Depth 20 field nesting, fragments add no level
Aliases 100 every aliased field, empty allow-list
Tokens 5,000 graphql-js tokens of the whole document (HTTP via parseOptions, WebSocket in onSubscribe); connection_init is not parsed as GraphQL
Body 1 MiB no bodyLimit anywhere, so Fastify 5's default

The test server reports Unraid 7.4.0 with API 4.37.11+8e587fe2, i.e. built from that very commit.

Live check on a real Unraid 7.4.0 (read-only, run once, not part of CI)

Each shipped query was sent once with the read-only API key (no mutation, no subscription). Every query validates against the limits and answers HTTP 200 with data. FetchContainerLogs was sent with a non-existent container id: it passes validation and the resolver answers "Failed to fetch logs" (error expected, no private data involved).

Query HTTP Result Cost Depth Aliases Tokens
Ping 200 data 1 1 0 5
FetchContainerLogs 200 data + resolver error (fake id) 6 4 0 37
GetServerInfo 200 data 17 4 0 35
GetMetrics 200 data 17 5 0 33
GetNetworkThroughput 200 data 5 3 0 13
GetDisplay 200 data 5 2 0 11
GetArray 200 data 59 4 0 77
GetDockerContainers 200 data 18 4 0 28
GetVms 200 data 5 3 0 13
GetNotifications 200 data 14 4 0 26
GetNotificationList 200 data 33 4 2 78
GetPlugins 200 data 5 2 0 11
GetPluginOperations 200 data 8 2 0 14
GetInstalledUnraidPlugins 200 data 1 1 0 5
GetNetworkInterfaces 200 data 26 4 0 40

Limits for comparison: cost 1,000 (margin 700), depth 20 (14), aliases 100 (70), tokens 5,000 (3,500). The largest request body is 477 bytes. The 25 mutations and 5 subscriptions are measured by the test but were not sent; the largest of them costs 10 (SystemMetricsTemperature), the subscriptions use 8 to 20 tokens.

Control requests (harmless __typename queries and aliased copies of shipped queries) confirm the formulas on the live server: cost 1,000 accepted and 1,001 refused ("Query Cost limit of 1000 exceeded, found 1001"); 100 aliases accepted and 101 refused; 5,001 tokens refused ("more that 5000 tokens"); 17 aliased copies of GetArray refused with "found 1003" (17 x 59) and 31 copies of GetNotificationList with "found 1023" (31 x 33, fragments included). The server's numbers equal the test's.

Answers to the open questions of #320: the server does not report the cost of an accepted query, but the refusals above show it equals the test's value, so GetArray costs 59 of 1,000 (6 %). The subscription connection_init message is not tokenised; only the subscribe query is, and the largest subscription uses 20 of 5,000 tokens.

Deliberately left out

  • No app code and no query changes: every operation is far below the margin (largest cost 59 of 700, largest token count 78 of 3,500).
  • No CHANGELOG entry: nothing user-facing changes.
  • Not measured live: depth 21 (needs a recursive schema type) and the 1 MiB body limit (no oversized request sent); both come from the source. Subscriptions were not sent live.
  • No version bump, nothing under .github/.

Checks

  • ./scripts/local-ci.sh: passed (generateApolloSources, lintDirectDebug, assembleDirectDebug, assembleRelease, testDirectDebugUnitTest; 288 unit tests, 0 failed, 0 skipped).

🤖 Generated with Claude Code

…d 7.4

Unraid 7.4.0 refuses requests above a cost of 1,000, depth 20, 100 aliases
and 5,000 GraphQL tokens (unraid/api request-protection.ts, graphql-armor
3.2.0). A query that grows past them would only show up as a failed screen.

GraphqlRequestLimitsTest re-implements the server's formulas (documented
with their source in GraphqlDocumentMetrics), measures every operation in
app/src/main/graphql as Apollo sends it and fails, naming the operation and
the measure, when one passes 70 percent of a limit. Its other tests pin the
formulas to the server's own boundaries. HANDOFF.md records the limits, the
margin and the result measured on a real Unraid 7.4.0.

No app code changes: the largest operation (GetArray) costs 59 of 1,000.

Closes #320

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@nofuturekid nofuturekid closed this pull request by merging all changes into main in dcfb183 Nov 13, 2026
@nofuturekid
nofuturekid deleted the test/graphql-request-limits branch November 13, 2026 16:58
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.

Verify NOVA against the Unraid 7.4.0 request limits

1 participant