Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package grails.plugin.json.view

import tools.jackson.databind.json.JsonMapper
import grails.persistence.Entity
import grails.plugin.json.view.test.JsonRenderResult
import grails.plugin.json.view.test.JsonViewTest
import org.grails.datastore.mapping.core.Session
Expand All @@ -32,23 +33,23 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest {
JsonMapper objectMapper = JsonMapper.builder().build()

void setup() {
mappingContext.addPersistentEntities(Team, Player)
mappingContext.addPersistentEntities(ExpandTeam, ExpandPlayer)

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.

This is the part of the PR I'd keep — an unqualified same-package reference silently binding to another spec's @Entity classes is a real trap, and giving ExpandSpec its own fixtures is the right shape of fix.

But it's applied to one of four specs doing exactly this, and not to the two with the worst numbers in #16030. Team and Player are declared once, in JsonViewHelperSpec.groovy:663 and :672, and after this PR they are still registered into three other independently-built KeyValueMappingContext instances:

  • IncludeAssociationsSpecimport grails.plugin.json.view.* plus addPersistentEntities(Player, Team)
  • HalEmbeddedSpec — same-package unqualified reference, addPersistentEntities(Team, Player)
  • IterableRenderSpec — same-package unqualified reference, addPersistentEntities(Player, Team), in five separate features

The same pattern holds for two other classes:

  • grails.plugin.json.view.api.Author is declared in api/JsonApiSpec.groovy:449 and also registered by api/JsonApiHandleAssociationsSpec (addPersistentEntities(Author, PublishedBook, Publisher))
  • Person is declared in EmbeddedAssociationsSpec.groovy:180 and also registered by HalEmbeddedSpec (addPersistentEntities(Person, Parent))

Per the dashboard the two worst specs are JsonViewHelperSpec (8 methods, 20 failures) and JsonApiSpec (7 methods, 20 failures) — and both still share entity classes with another spec after this change. ExpandSpec (19 failures) is the only one decoupled. So if class-keyed GORM state is the root cause, the flakiness should survive this PR.

Could you extend the same treatment to those specs? Given Team/Player are wanted by four specs, a shared read-only fixture file plus one mapping context, or per-spec copies as done here, would both work — the important thing is that no two independently-built mapping contexts see the same Class. A dedicated fixture source file would also make the ownership obvious and stop the next spec from picking them up by accident.

}

void 'Test expand parameter allows expansion of child associations'() {
given: 'An entity with a proxy association'
def mockSession = Mock(Session)
mockSession.getMappingContext() >> mappingContext
mockSession.retrieve(Team, 1L) >> new Team(name: 'Manchester United')
def teamProxy = mappingContext.proxyFactory.createProxy(mockSession, Team, 1L)
mockSession.retrieve(ExpandTeam, 1L) >> new ExpandTeam(name: 'Manchester United')
def teamProxy = mappingContext.proxyFactory.createProxy(mockSession, ExpandTeam, 1L)

def player = new Player(name: 'Cantona', team: teamProxy)
def player = new ExpandPlayer(name: 'Cantona', team: teamProxy)

def templateText = '''
import grails.plugin.json.view.*
import grails.plugin.json.view.ExpandPlayer as Player

@Field Player player

json g.render(player)
'''

Expand Down Expand Up @@ -84,15 +85,13 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest {
given: 'An entity with a proxy association'
def mockSession = Mock(Session)
mockSession.getMappingContext() >> mappingContext
mockSession.retrieve(Team, 1L) >> new Team(name: 'Manchester United')
def teamProxy = mappingContext.proxyFactory.createProxy(mockSession, Team, 1L)
mockSession.retrieve(ExpandTeam, 1L) >> new ExpandTeam(name: 'Manchester United')
def teamProxy = mappingContext.proxyFactory.createProxy(mockSession, ExpandTeam, 1L)

def player = new Player(name: 'Cantona', team: teamProxy)
def player = new ExpandPlayer(name: 'Cantona', team: teamProxy)
def templateText = '''
import grails.plugin.json.view.*

@Field Map map

json g.render(map)
'''

Expand All @@ -119,13 +118,13 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest {
given: 'An entity with a proxy association'
def mockSession = Mock(Session)
mockSession.getMappingContext() >> mappingContext
mockSession.retrieve(Team, 1L) >> new Team(name: 'Manchester United')
def teamProxy = mappingContext.proxyFactory.createProxy(mockSession, Team, 1L)
mockSession.retrieve(ExpandTeam, 1L) >> new ExpandTeam(name: 'Manchester United')
def teamProxy = mappingContext.proxyFactory.createProxy(mockSession, ExpandTeam, 1L)

def player = new Player(name: 'Cantona', team: teamProxy)
def player = new ExpandPlayer(name: 'Cantona', team: teamProxy)

def templateText = '''
import grails.plugin.json.view.*
import grails.plugin.json.view.ExpandPlayer as Player
model {
Player player
}
Expand All @@ -140,7 +139,7 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest {
{
"_links": {
"self": {
"href": "http://localhost:8080/player",
"href": "http://localhost:8080/expandPlayer",
"hreflang": "en",
"type": "application/hal+json"
}
Expand All @@ -161,7 +160,7 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest {
"team": {
"_links": {
"self": {
"href": "http://localhost:8080/team/1",
"href": "http://localhost:8080/expandTeam/1",
"hreflang": "en",
"type": "application/hal+json"
}
Expand All @@ -171,7 +170,7 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest {
},
"_links": {
"self": {
"href": "http://localhost:8080/player",
"href": "http://localhost:8080/expandPlayer",
"hreflang": "en",
"type": "application/hal+json"
}
Expand All @@ -185,48 +184,48 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest {
given: 'An entity with a proxy association'
def mockSession = Mock(Session)
mockSession.getMappingContext() >> mappingContext
mockSession.retrieve(Team, 9L) >> new Team(name: 'Manchester United')
def teamProxy = mappingContext.proxyFactory.createProxy(mockSession, Team, 9L)
def player = new Player(name: 'Cantona', team: teamProxy)
mockSession.retrieve(ExpandTeam, 9L) >> new ExpandTeam(name: 'Manchester United')
def teamProxy = mappingContext.proxyFactory.createProxy(mockSession, ExpandTeam, 9L)
def player = new ExpandPlayer(name: 'Cantona', team: teamProxy)
player.id = 3

when: 'The domain is rendered with expand parameters'
JsonRenderResult result = render('''
import grails.plugin.json.view.*
import grails.plugin.json.view.ExpandPlayer as Player
model {
Player player
}

json jsonapi.render(player, [expand: 'team'])
''', [player: player])

then: 'The JSON relationships are in place'
objectMapper.readTree(result.jsonText) == objectMapper.readTree('''
{
"data": {
"type": "player",
"type": "expandPlayer",
"id": "3",
"attributes": {
"name": "Cantona"
},
"relationships": {
"team": {
"links": {
"self": "/team/9"
"self": "/expandTeam/9"
},
"data": {
"type": "team",
"type": "expandTeam",
"id": "9"
}
}
}
},
"links": {
"self": "/player/3"
"self": "/expandPlayer/3"
},
"included": [
{
"type":"team",
"type":"expandTeam",
"id": "9",
"attributes": {
"titles": null,
Expand All @@ -241,11 +240,33 @@ class ExpandSpec extends Specification implements JsonViewTest, GrailsUnitTest {
}
},
"links": {
"self": "/team/9"
"self": "/expandTeam/9"
}
}
]
}
''')
}
}

@Entity
class ExpandTeam {
String name
ExpandPlayer captain
List players
List<String> titles
@SuppressWarnings('unused')
static hasMany = [players: ExpandPlayer]
}

@Entity
class ExpandPlayer {
Long version
String name
@SuppressWarnings('unused')
static belongsTo = [team: ExpandTeam]

static constraints = {
name nullable: false
}
}
Loading
Loading