Skip to content

refactor(grails-data-graphql): clean up the entity package - #16202

Open
borinquenkid wants to merge 8 commits into
test/grails-data-graphql-coveragefrom
refactor/graphql-entity
Open

refactor(grails-data-graphql): clean up the entity package#16202
borinquenkid wants to merge 8 commits into
test/grails-data-graphql-coveragefrom
refactor/graphql-entity

Conversation

@borinquenkid

Copy link
Copy Markdown
Member

Summary

Stacked on #16201. Fixes a series of IntelliJ-flagged issues in org.grails.gorm.graphql.entity.*:

  • EntityFetchOptions.java: added first direct unit coverage, parameterized raw Association/ToOne/Selection usages, and restored a null-safe getMergedField() guard that had been accidentally dropped mid-refactor (covered by ClosureDataFetcher.buildFetchOptions tests).
  • PersistentGraphQLProperty: consolidated two multi-branch reassignment patterns (order resolution, getGraphQLType's entity/embedded resolution) into single-assignment forms.
  • CustomArgument/Arguable/ComplexTyped: replaced deprecated GraphQLArgument/GraphQLInputObjectField#defaultValue calls with the non-deprecated defaultValueProgrammatic, guarded against nulls to avoid breaking schema validation.
  • GraphQLMapping.methodMissing: removed the @CompileDynamic escape hatch while preserving the exact (String, Object) signature Groovy's methodMissing protocol requires.
  • CustomOperation/Schema: migrated GraphQLFieldDefinition.Builder#dataFetcher (deprecated) to registration via GraphQLCodeRegistry.Builder.

Test plan

  • ./gradlew :grails-data-graphql-core:test :grails-data-graphql:test :grails-data-graphql-core:codeStyle :grails-data-graphql:codeStyle passes

🤖 Generated with Claude Code

borinquenkid and others added 8 commits August 23, 2026 10:10
- Mark the four instance fields final; each is assigned exactly once,
  in the constructor.
- Fix Javadoc grammar ("need prepended" -> "need to be prepended").
- Use pattern-matching instanceof in isForeignKeyInChild() and the
  single-selection branch of handleField(), replacing the redundant
  cast.
- Use List#getFirst() instead of get(0) (SequencedCollection, JDK 21).
- Tighten the raw Map<String, Map> return/local types on the
  getFetchArgument() overloads to Map<String, Map<String, String>>,
  matching the actual join-map shape they build.

EntityFetchOptions is documented public API (grails-data-graphql docs
point users at it directly for custom data fetchers) but had zero
direct test coverage - only indirect coverage through
DefaultGormDataFetcherSpec. Added EntityFetchOptionsSpec covering
construction (including the null-entity guard), getAssociations(),
getFetchArgument()'s map shape, and isForeignKeyInChild() for
ToMany/hasOne/plain-toOne associations.

Left the single-arg EntityFetchOptions(PersistentEntity) constructor
in place despite it having no internal callers - it's the constructor
external consumers are documented to use directly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ions

Association<T> and ToOne<T> are generic (T extends Property); every
field, parameter, and local variable using them raw is now Association<?>
/ToOne<?>. Same for the Selection<T> pattern variable introduced by the
earlier instanceof cleanup.

The one exception is the local variable holding
SelectionSet#getSelections()'s result: graphql-java itself declares
that method to return a raw List<Selection>, so there's no
parameterized type to assign it to without an unchecked cast. Left
that one raw with a comment and a scoped @SuppressWarnings("rawtypes"),
since the raw type there is coming from the library's own API, not
a gap in ours.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…r.buildFetchOptions

A prior commit in this cleanup ("Parameterize raw
Association/ToOne/Selection usages") accidentally dropped the
null-guard around environment.getMergedField() in
EntityFetchOptions.getJoinProperties(DataFetchingEnvironment, boolean)
while editing nearby code, turning a graceful "no merged field ->
empty fields list" fallback into a NullPointerException. This broke
6+ existing specs whose DataFetchingEnvironment mocks don't stub
getMergedField() (so it returns null, as real callers can also see).
Restored the guard as a single-assignment if/else.

Also adds test coverage for ClosureDataFetcher.buildFetchOptions(),
which had none: null domain type, non-GORM domain type, a real GORM
entity domain type, and that the built EntityFetchOptions is cached
across calls. Converts ClosureDataFetcherSpec to HibernateSpec to
back the GORM-entity case.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…assignment

The final 'order' field was conditionally assigned from up to four
different branches spread across two separate if-blocks in the
constructor. That shape is genuinely ambiguous for definite-assignment
analysis of a blank final field (assigned once on some paths, not
assigned at the point it gets re-checked and possibly assigned again
on others) even though groovyc accepted it. Extracted the whole
decision tree into resolveOrder(), assigned to this.order exactly once.
Behavior is unchanged - confirmed by the existing 9-case
"test graphQL order for #name" spec, which still covers every branch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…resolution

The local 'entity' var followed the same shape as the order field fix:
declared blank, conditionally assigned in an Association check, then
conditionally reassigned again from a null-guard - flagged as unused
assignments since the write is only reachable through a read that
guards a second write to the same variable. Replaced with a single
elvis-operator assignment (association?.associatedEntity ?: fallback)
and a single boolean expression for 'embedded'. Same runtime behavior -
verified against the existing toMany/toOne/embedded specs, all of
which exercise this method.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ue calls

graphql-java 25 deprecates Builder#defaultValue(Object) in favor of
defaultValueLiteral(Value) or defaultValueProgrammatic(Object).
CustomArgument and ComplexTyped supply a plain, uncoerced Java value
from the DSL, so defaultValueProgrammatic is the correct replacement.

Switching unconditionally surfaced a real bug: the old deprecated call
always marked the default value as "set" (even when the DSL user never
configured one), which the old INTERNAL_VALUE state silently exempted
from schema validation. defaultValueProgrammatic's EXTERNAL_VALUE state
is validated, and a null default on a non-null argument/field type
failed schema build. Both call sites now only apply a default value
when one was actually configured via the DSL, leaving it unset
otherwise - matching the DSL's actual intent and passing validation.

Also documents (without changing) the unqualified withDelegate() calls
in Arguable and ComplexTyped: explicitly qualifying the inherited
ExecutesClosures static trait method there does not resolve under
@CompileStatic, so the plain call must stay despite IDE inspections
suggesting otherwise.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
args was typed as plain Object, so indexing it (args[0]) had no
statically resolvable getAt(Object, Integer) overload, forcing
@CompileDynamic and triggering IDE warnings on every index access.
The Groovy methodMissing hook still requires the (String, Object)
signature to be recognized as the protocol method, but the runtime
value is always an Object[], so it's now cast to Object[] once and
indexed from there - letting the method compile statically like the
rest of the class, with identical runtime behavior.

Adds coverage for the two methodMissing failure branches (no
arguments, unsupported argument type) that had no tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GraphQLFieldDefinition.Builder#dataFetcher(DataFetcher) has been
deprecated since graphql-java 12; data fetchers are wired through
GraphQLCodeRegistry instead. CustomOperation.createField() now takes
the parent type name (Query or Mutation) and registers the built
data fetcher with the type manager's code registry, matching the
pattern already used everywhere else in Schema.groovy.

Adds assertions to GraphQLMappingSpec verifying the data fetcher for
each custom query/mutation operation is actually wired into the code
registry, which the previous test never checked.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 23, 2026 16:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codecov

codecov Bot commented Aug 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 53.4048%. Comparing base (36b7716) to head (1654c6c).

Additional details and impacted files

Impacted file tree graph

@@                             Coverage Diff                             @@
##             test/grails-data-graphql-coverage     #16202        +/-   ##
===========================================================================
- Coverage                              53.4149%   53.4048%   -0.0101%     
+ Complexity                               19459      19457         -2     
===========================================================================
  Files                                     2081       2081                
  Lines                                    98993      98993                
  Branches                                 17361      17361                
===========================================================================
- Hits                                     52877      52867        -10     
- Misses                                   38566      38579        +13     
+ Partials                                  7550       7547         -3     

see 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@testlens-app

testlens-app Bot commented Aug 23, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: 1654c6c
▶️ Tests: 47746 executed
⚪️ Checks: 70/70 completed


Learn more about TestLens at testlens.app/docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants