Skip to content

Commit df01627

Browse files
authored
Merge pull request #48 from zspitzer/h73-baseline-tests
Add baseline tests for read-only collections and unique-result multi-row paths
2 parents 5d5dd59 + d1ee4de commit df01627

14 files changed

Lines changed: 269 additions & 0 deletions

tests/session/readOnlyEntity.cfc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
component extends="org.lucee.cfml.test.LuceeTestCase" labels="orm" {
2+
3+
function run( testResults, testBox ) {
4+
5+
describe( "Read-only entity behaviour [H2] — locks down 5.6 baseline for 7.3 migration", function() {
6+
7+
it( "session.setReadOnly() and session.isReadOnly() round-trip", function() {
8+
var result = _InternalRequest( template: "#uri()#/isReadOnlyFlag.cfm" );
9+
expect( trim( result.filecontent ) ).toBe( "ok" );
10+
});
11+
12+
it( "scalar mutation on read-only entity does not persist", function() {
13+
var result = _InternalRequest( template: "#uri()#/setReadOnlyScalar.cfm" );
14+
expect( trim( result.filecontent ) ).toBe( "ok" );
15+
});
16+
17+
// xit until Hibernate 7.3 — 5.6 silently lets read-only collections mutate
18+
// (the asymmetry 7.3 fixes: scalars honour read-only, collections don't)
19+
xit( "collection add on read-only entity does not persist", function() {
20+
var result = _InternalRequest( template: "#uri()#/setReadOnlyCollectionAdd.cfm" );
21+
expect( trim( result.filecontent ) ).toBe( "ok" );
22+
});
23+
24+
xit( "collection remove on read-only entity does not persist", function() {
25+
var result = _InternalRequest( template: "#uri()#/setReadOnlyCollectionRemove.cfm" );
26+
expect( trim( result.filecontent ) ).toBe( "ok" );
27+
});
28+
29+
});
30+
31+
}
32+
33+
private string function uri() {
34+
return getDirectoryFromPath( contractPath( getCurrentTemplatePath() ) ) & "readOnlyEntity";
35+
}
36+
37+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
component {
2+
this.name = "test-readOnlyEntity-#hash( getCurrentTemplatePath() )#";
3+
this.datasource = server.getDatasource( "h2", server._getTempDir( "orm-readOnlyEntity" ) );
4+
this.ormEnabled = true;
5+
this.ormSettings = {
6+
dbcreate: "dropcreate",
7+
cfclocation: [ getDirectoryFromPath( getCurrentTemplatePath() ) ]
8+
};
9+
10+
function onRequestStart() {
11+
queryExecute( "DELETE FROM RO_Child" );
12+
queryExecute( "DELETE FROM RO_Parent" );
13+
queryExecute( "INSERT INTO RO_Parent ( id, name ) VALUES ( 'p1', 'parent-one' )" );
14+
queryExecute( "INSERT INTO RO_Child ( id, name, parentId ) VALUES ( 'c1', 'child-one', 'p1' )" );
15+
queryExecute( "INSERT INTO RO_Child ( id, name, parentId ) VALUES ( 'c2', 'child-two', 'p1' )" );
16+
}
17+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
component persistent="true" table="RO_Child" accessors="true" {
2+
3+
property name="id" fieldtype="id" ormtype="string";
4+
property name="name" ormtype="string";
5+
property name="parent"
6+
fieldtype="many-to-one"
7+
cfc="ROParent"
8+
fkcolumn="parentId";
9+
10+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
component persistent="true" table="RO_Parent" accessors="true" {
2+
3+
property name="id" fieldtype="id" ormtype="string";
4+
property name="name" ormtype="string";
5+
property name="children"
6+
fieldtype="one-to-many"
7+
cfc="ROChild"
8+
fkcolumn="parentId"
9+
type="array"
10+
cascade="all";
11+
12+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<cfscript>
2+
// Verify session.setReadOnly() / session.isReadOnly() round-trip.
3+
4+
parent = entityLoadByPK( "ROParent", "p1" );
5+
// `session` is a CFML scope name, can't be used as a variable
6+
hibSession = ormGetSession();
7+
8+
if ( hibSession.isReadOnly( parent ) )
9+
throw( message="Entity unexpectedly read-only at load" );
10+
11+
hibSession.setReadOnly( parent, true );
12+
if ( !hibSession.isReadOnly( parent ) )
13+
throw( message="setReadOnly( entity, true ) did not flip isReadOnly()" );
14+
15+
hibSession.setReadOnly( parent, false );
16+
if ( hibSession.isReadOnly( parent ) )
17+
throw( message="setReadOnly( entity, false ) did not flip isReadOnly()" );
18+
19+
echo( "ok" );
20+
</cfscript>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<cfscript>
2+
// 7.3 change: collections owned by a read-only entity become read-only.
3+
// arrayAppend then flush should not produce a 3rd DB row regardless of
4+
// whether the platform throws or silently ignores the mutation.
5+
6+
parent = entityLoadByPK( "ROParent", "p1" );
7+
initialCount = arrayLen( parent.getChildren() );
8+
ormGetSession().setReadOnly( parent, true );
9+
10+
newChild = entityNew( "ROChild", { id: "c-new", name: "should-not-persist" } );
11+
newChild.setParent( parent );
12+
13+
try {
14+
arrayAppend( parent.getChildren(), newChild );
15+
ormFlush();
16+
} catch ( any e ) {
17+
// 7.3 throws on read-only collection mutation — acceptable
18+
}
19+
20+
ormClearSession();
21+
reloaded = entityLoadByPK( "ROParent", "p1" );
22+
finalCount = arrayLen( reloaded.getChildren() );
23+
if ( finalCount != initialCount )
24+
throw( message="Read-only collection add persisted, initial [#initialCount#] now [#finalCount#]" );
25+
26+
echo( "ok" );
27+
</cfscript>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<cfscript>
2+
// 7.3 change: removing from a collection on a read-only entity must not persist.
3+
4+
parent = entityLoadByPK( "ROParent", "p1" );
5+
initialCount = arrayLen( parent.getChildren() );
6+
ormGetSession().setReadOnly( parent, true );
7+
8+
try {
9+
arrayDeleteAt( parent.getChildren(), 1 );
10+
ormFlush();
11+
} catch ( any e ) {
12+
// 7.3 may throw — acceptable, persistence outcome is what we lock down
13+
}
14+
15+
ormClearSession();
16+
reloaded = entityLoadByPK( "ROParent", "p1" );
17+
finalCount = arrayLen( reloaded.getChildren() );
18+
if ( finalCount != initialCount )
19+
throw( message="Read-only collection remove persisted, initial [#initialCount#] now [#finalCount#]" );
20+
21+
echo( "ok" );
22+
</cfscript>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<cfscript>
2+
// session.setReadOnly( entity, true ) — scalar mutations must not persist.
3+
// Whether Hibernate throws or silently ignores is platform-defined; the
4+
// contract we assert is "read-only entity changes never reach the DB".
5+
6+
parent = entityLoadByPK( "ROParent", "p1" );
7+
ormGetSession().setReadOnly( parent, true );
8+
9+
try {
10+
parent.setName( "mutated" );
11+
ormFlush();
12+
} catch ( any e ) {
13+
// 7.3 may start throwing — acceptable, the persist outcome is what matters
14+
}
15+
16+
ormClearSession();
17+
reloaded = entityLoadByPK( "ROParent", "p1" );
18+
if ( reloaded.getName() != "parent-one" )
19+
throw( message="Read-only scalar mutation persisted, expected [parent-one] got [#reloaded.getName()#]" );
20+
21+
echo( "ok" );
22+
</cfscript>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
component extends="org.lucee.cfml.test.LuceeTestCase" labels="orm" {
2+
3+
function run( testResults, testBox ) {
4+
5+
describe( "ormExecuteQuery unique=true with multiple rows [H2] — locks down 5.6 baseline for 7.3 dedup change", function() {
6+
7+
it( "throws when no filter and >1 row", function() {
8+
var result = _InternalRequest( template: "#uri()#/executeQueryUniqueMulti.cfm" );
9+
expect( trim( result.filecontent ) ).toBe( "ok" );
10+
});
11+
12+
it( "throws when WHERE matches multiple rows", function() {
13+
var result = _InternalRequest( template: "#uri()#/executeQueryParamUniqueMulti.cfm" );
14+
expect( trim( result.filecontent ) ).toBe( "ok" );
15+
});
16+
17+
it( "returns single entity when WHERE matches exactly one row", function() {
18+
var result = _InternalRequest( template: "#uri()#/executeQuerySingleMatch.cfm" );
19+
expect( trim( result.filecontent ) ).toBe( "ok" );
20+
});
21+
22+
});
23+
24+
}
25+
26+
private string function uri() {
27+
return getDirectoryFromPath( contractPath( getCurrentTemplatePath() ) ) & "uniqueResultMulti";
28+
}
29+
30+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
component {
2+
this.name = "test-uniqueResultMulti-#hash( getCurrentTemplatePath() )#";
3+
this.datasource = server.getDatasource( "h2", server._getTempDir( "orm-uniqueResultMulti" ) );
4+
this.ormEnabled = true;
5+
this.ormSettings = {
6+
dbcreate: "dropcreate",
7+
cfclocation: [ getDirectoryFromPath( getCurrentTemplatePath() ) ]
8+
};
9+
10+
function onRequestStart() {
11+
queryExecute( "DELETE FROM MultiEntity" );
12+
queryExecute( "INSERT INTO MultiEntity ( id, name, status ) VALUES ( 1, 'alpha', 'active' )" );
13+
queryExecute( "INSERT INTO MultiEntity ( id, name, status ) VALUES ( 2, 'bravo', 'active' )" );
14+
queryExecute( "INSERT INTO MultiEntity ( id, name, status ) VALUES ( 3, 'charlie', 'active' )" );
15+
queryExecute( "INSERT INTO MultiEntity ( id, name, status ) VALUES ( 4, 'delta', 'inactive' )" );
16+
}
17+
}

0 commit comments

Comments
 (0)