Skip to content

Commit 89e7e1a

Browse files
authored
Merge pull request #23 from constructive-io/feat/orm-spatial-filter-test
test(orm): document PostGIS spatial support on memory.location_geo
2 parents fd24ef8 + 5014ae9 commit 89e7e1a

2 files changed

Lines changed: 137 additions & 0 deletions

File tree

packages/integration-tests/__fixtures__/seed/test-data.sql

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,32 @@ VALUES
2323
INSERT INTO "agentic_db_app_public".tasks (agent_id, title, description, status)
2424
VALUES
2525
('cccccccc-cccc-cccc-cccc-cccccccccccc', 'Summarize docs', 'Summarize all project documents', 'pending');
26+
27+
-- Memories with PostGIS Point locations for spatial-filter tests.
28+
-- SF and Oakland are in the Bay Area bbox; NYC is the negative control.
29+
INSERT INTO "agentic_db_app_public".memories (id, agent_id, title, content, location, location_geo)
30+
VALUES
31+
(
32+
'eeeeeeee-eeee-eeee-eeee-eeeeeeee0001',
33+
'cccccccc-cccc-cccc-cccc-cccccccccccc',
34+
'Coffee in SF',
35+
'Met a collaborator near the Ferry Building.',
36+
'San Francisco, CA',
37+
ST_SetSRID(ST_MakePoint(-122.4194, 37.7749), 4326)::geography
38+
),
39+
(
40+
'eeeeeeee-eeee-eeee-eeee-eeeeeeee0002',
41+
'cccccccc-cccc-cccc-cccc-cccccccccccc',
42+
'Lunch in Oakland',
43+
'Reviewed the retrieval benchmark over lunch.',
44+
'Oakland, CA',
45+
ST_SetSRID(ST_MakePoint(-122.2712, 37.8044), 4326)::geography
46+
),
47+
(
48+
'eeeeeeee-eeee-eeee-eeee-eeeeeeee0003',
49+
'cccccccc-cccc-cccc-cccc-cccccccccccc',
50+
'Meetup in NYC',
51+
'Agent-infra meetup in Manhattan.',
52+
'New York, NY',
53+
ST_SetSRID(ST_MakePoint(-74.0060, 40.7128), 4326)::geography
54+
);

packages/integration-tests/__tests__/orm.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,25 @@ const CONTACT_BOB = '22222222-2222-2222-2222-222222222222';
3838
const NOTE_KICKOFF = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa';
3939
const 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+
4160
describe('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(/parse error - invalid geometry/i);
610+
});
611+
});
612+
505613
// =========================================================================
506614
// Test: Conversation + Message (1:N) with message ordering via orderBy
507615
// =========================================================================

0 commit comments

Comments
 (0)