@@ -38,6 +38,25 @@ const CONTACT_BOB = '22222222-2222-2222-2222-222222222222';
3838const NOTE_KICKOFF = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' ;
3939const AGENT_RESEARCH = 'cccccccc-cccc-cccc-cccc-cccccccccccc' ;
4040
41+ // Memories with seeded PostGIS Point locations (see test-data.sql)
42+ const MEMORY_SF = 'eeeeeeee-eeee-eeee-eeee-eeeeeeee0001' ;
43+ const MEMORY_OAKLAND = 'eeeeeeee-eeee-eeee-eeee-eeeeeeee0002' ;
44+ const MEMORY_NYC = 'eeeeeeee-eeee-eeee-eeee-eeeeeeee0003' ;
45+
46+ // Bounding-box polygon around the Bay Area: covers SF + Oakland, excludes NYC.
47+ const BAY_AREA_POLYGON = {
48+ type : 'Polygon' ,
49+ coordinates : [
50+ [
51+ [ - 122.55 , 37.70 ] ,
52+ [ - 122.20 , 37.70 ] ,
53+ [ - 122.20 , 37.85 ] ,
54+ [ - 122.55 , 37.85 ] ,
55+ [ - 122.55 , 37.70 ] ,
56+ ] ,
57+ ] ,
58+ } ;
59+
4160describe ( 'ORM integration' , ( ) => {
4261 let db : PgTestClient ;
4362 let teardown : ( ) => Promise < void > ;
@@ -502,6 +521,95 @@ describe('ORM integration', () => {
502521 } ) ;
503522 } ) ;
504523
524+ // =========================================================================
525+ // Test: PostGIS spatial support on memories.location_geo
526+ //
527+ // What IS testable through the typed ORM today:
528+ // - geography columns round-trip correctly (insert WKT/GeoJSON, read
529+ // back GeoJSON + srid through the generated output type)
530+ // - `locationGeo: { isNull: false }` filters out rows without geometry
531+ //
532+ // What is NOT testable through the typed ORM today (verified against the
533+ // real schema + graphile-postgis@2.9.7 in CI): the generated
534+ // `GeographyInterfaceFilter` exposes `bboxIntersects2D`, `coveredBy`,
535+ // `covers`, `exactlyEquals`, `intersects` — but all of them fail at
536+ // runtime with `parse error - invalid geometry` because the spatial
537+ // function operators in graphile-postgis 2.9.7 pass the GeoJSON value
538+ // straight through to PostgreSQL instead of wrapping it with
539+ // ST_GeomFromGeoJSON(...)::geography. Only the `withinDistance` operator
540+ // has the correct wrapping, and it is not exposed on the generated
541+ // filter type at all. As a result, "find memories within 5km of here"
542+ // and "find memories inside this polygon" are NOT possible through the
543+ // typed ORM today — they require raw SQL (ST_DWithin / ST_Covers).
544+ // =========================================================================
545+ describe ( 'PostGIS spatial support on memory.location_geo' , ( ) => {
546+ it ( 'returns the stored Point as GeoJSON + srid via the generated output type' , async ( ) => {
547+ const result = await orm . memory
548+ . findMany ( {
549+ where : { id : { equalTo : MEMORY_SF } } ,
550+ select : {
551+ id : true ,
552+ title : true ,
553+ locationGeo : { select : { geojson : true , srid : true } } ,
554+ } ,
555+ } )
556+ . execute ( ) ;
557+ expectOk ( result , 'memory.findMany(MEMORY_SF)' ) ;
558+ const nodes = unwrapData ( result . data ) . nodes ;
559+ expect ( nodes ) . toHaveLength ( 1 ) ;
560+ const sf = nodes [ 0 ] ;
561+ expect ( sf . locationGeo ) . toBeTruthy ( ) ;
562+ expect ( sf . locationGeo . srid ) . toBe ( 4326 ) ;
563+ // GeoJSON is serialized as a string by the postgis plugin.
564+ const geojson =
565+ typeof sf . locationGeo . geojson === 'string'
566+ ? JSON . parse ( sf . locationGeo . geojson )
567+ : sf . locationGeo . geojson ;
568+ expect ( geojson . type ) . toBe ( 'Point' ) ;
569+ expect ( geojson . coordinates [ 0 ] ) . toBeCloseTo ( - 122.4194 , 3 ) ;
570+ expect ( geojson . coordinates [ 1 ] ) . toBeCloseTo ( 37.7749 , 3 ) ;
571+ } ) ;
572+
573+ it ( 'locationGeo: { isNull: false } returns the geo-tagged memories' , async ( ) => {
574+ const result = await orm . memory
575+ . findMany ( {
576+ where : { locationGeo : { isNull : false } } ,
577+ select : {
578+ id : true ,
579+ title : true ,
580+ locationGeo : { select : { srid : true } } ,
581+ } ,
582+ } )
583+ . execute ( ) ;
584+ expectOk ( result , 'memory.findMany(isNull:false)' ) ;
585+ const nodes = unwrapData ( result . data ) . nodes ;
586+ // Every returned row must actually have geometry attached.
587+ for ( const n of nodes ) {
588+ expect ( n . locationGeo ) . toBeTruthy ( ) ;
589+ expect ( n . locationGeo . srid ) . toBe ( 4326 ) ;
590+ }
591+ const ids = nodes . map ( ( n : any ) => n . id ) ;
592+ expect ( ids ) . toEqual (
593+ expect . arrayContaining ( [ MEMORY_SF , MEMORY_OAKLAND , MEMORY_NYC ] ) ,
594+ ) ;
595+ } ) ;
596+
597+ // Regression-guard: make the upstream bug explicit so that if a future
598+ // graphile-postgis release fixes it, this test will start failing and
599+ // we'll know we can promote these operators to "supported".
600+ it ( 'bboxIntersects2D is currently broken upstream (graphile-postgis@2.9.7)' , async ( ) => {
601+ const result = await orm . memory
602+ . findMany ( {
603+ where : { locationGeo : { bboxIntersects2D : BAY_AREA_POLYGON } } ,
604+ select : { id : true , title : true } ,
605+ } )
606+ . execute ( ) ;
607+ expect ( result . ok ) . toBe ( false ) ;
608+ const err = JSON . stringify ( result . errors ?? result ) ;
609+ expect ( err ) . toMatch ( / p a r s e e r r o r - i n v a l i d g e o m e t r y / i) ;
610+ } ) ;
611+ } ) ;
612+
505613 // =========================================================================
506614 // Test: Conversation + Message (1:N) with message ordering via orderBy
507615 // =========================================================================
0 commit comments