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
1 change: 1 addition & 0 deletions graphql/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
},
"devDependencies": {
"@aws-sdk/client-s3": "^3.1052.0",
"@dataplan/pg": "1.0.3",
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.17",
"@types/express": "^5.0.6",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ describe('collectMetricsSample', () => {
expect(typeof sample.counters.evictions.lru).toBe('number');
expect(typeof sample.counters.builds).toBe('number');
expect(typeof sample.counters.buildQueueDepth).toBe('number');
expect(typeof sample.counters.rewritePool.rewrittenQueries).toBe('number');
expect(typeof sample.counters.introspectionFilter.swaps).toBe('number');
expect(typeof sample.counters.introspectionFilter.discoveries).toBe('number');
expect(typeof sample.gc).toBe('object');
Expand Down
3 changes: 3 additions & 0 deletions graphql/server/src/diagnostics/metrics-sampler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getCacheCounters, getCacheStats } from 'graphile-cache';

import { getBuildQueueDepth, getGraphileCounters } from '../middleware/graphile';
import { getIntrospectionFilterCounters } from '../middleware/introspection-filter';
import { getRewritePoolCounters } from '../middleware/rewrite-pool';
import { getConnectionErrorGuardCounters } from './connection-error-guard';

const log = new Logger('metrics-sampler');
Expand Down Expand Up @@ -127,6 +128,7 @@ export interface MetricsSample {
ReturnType<typeof getGraphileCounters> & {
buildQueueDepth: number;
connGuard: ReturnType<typeof getConnectionErrorGuardCounters>;
rewritePool: ReturnType<typeof getRewritePoolCounters>;
introspectionFilter: ReturnType<typeof getIntrospectionFilterCounters>;
};
gc: GcStats;
Expand Down Expand Up @@ -156,6 +158,7 @@ export const collectMetricsSample = (): MetricsSample => {
...getGraphileCounters(),
buildQueueDepth: getBuildQueueDepth(),
connGuard: getConnectionErrorGuardCounters(),
rewritePool: getRewritePoolCounters(),
introspectionFilter: getIntrospectionFilterCounters()
},
gc: snapshotGcStats()
Expand Down
149 changes: 149 additions & 0 deletions graphql/server/src/middleware/__tests__/blueprint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import {
computeBlueprintKey,
hasBm25Indexes,
stripSchemaHashPrefix
} from '../blueprint';

describe('hasBm25Indexes', () => {
it('is true when the catalog reports a bm25 index in the served schemas', async () => {
const query = jest.fn().mockResolvedValueOnce({ rows: [{ exists: true }] });
await expect(hasBm25Indexes({ query } as any, ['s1', 's2'])).resolves.toBe(true);
// The probe is a single qualified catalog query over the served schemas.
expect(query).toHaveBeenCalledTimes(1);
expect(query).toHaveBeenCalledWith(expect.stringContaining("am.amname = 'bm25'"), [['s1', 's2']]);
});

it('is false when the catalog reports none (or an empty result)', async () => {
const q1 = jest.fn().mockResolvedValueOnce({ rows: [{ exists: false }] });
await expect(hasBm25Indexes({ query: q1 } as any, ['s1'])).resolves.toBe(false);
const q2 = jest.fn().mockResolvedValueOnce({ rows: [] });
await expect(hasBm25Indexes({ query: q2 } as any, ['s1'])).resolves.toBe(false);
});
});

describe('stripSchemaHashPrefix', () => {
it('strips a hashed tenant prefix down to the logical schema', () => {
expect(stripSchemaHashPrefix('marketplace-db-tenant1-5e6b13b2-app-public')).toBe(
'app-public'
);
});

it('returns control-plane / non-hashed schema names unchanged', () => {
expect(stripSchemaHashPrefix('services_public')).toBe('services_public');
expect(stripSchemaHashPrefix('metaschema_public')).toBe('metaschema_public');
expect(stripSchemaHashPrefix('app-public')).toBe('app-public');
});

it('handles multi-dash database names in the prefix', () => {
expect(stripSchemaHashPrefix('my-cool-db-name-deadbeef-auth-private')).toBe(
'auth-private'
);
expect(stripSchemaHashPrefix('app-5e6b13b2-public')).toBe('public');
});

it('only strips through the FIRST 8-hex segment, keeping later ones intact', () => {
expect(
stripSchemaHashPrefix('marketplace-db-tenant1-5e6b13b2-app-deadbeef-zone')
).toBe('app-deadbeef-zone');
});

it('does not treat non-hex or wrong-length segments as a hash', () => {
// 'tenant11' is 8 chars but not all hex; 'abcdef1' is 7 hex chars.
expect(stripSchemaHashPrefix('marketplace-tenant11-app-public')).toBe(
'marketplace-tenant11-app-public'
);
expect(stripSchemaHashPrefix('marketplace-abcdef1-app-public')).toBe(
'marketplace-abcdef1-app-public'
);
});
});

describe('computeBlueprintKey', () => {
const base = {
logicalSchemas: ['app-public', 'auth-public'],
shapeFingerprint: 'fingerprint-a',
flags: { a: 1, b: 2 } as Record<string, any>,
apiName: 'customer-api',
mode: 'domain-lookup'
};

it('produces a stable "bp:"-prefixed sha256 hex key', () => {
const key = computeBlueprintKey(base);
expect(key).toMatch(/^bp:[0-9a-f]{64}$/);
expect(computeBlueprintKey(base)).toBe(key);
});

it('is stable across the key order of flags', () => {
const key1 = computeBlueprintKey({ ...base, flags: { a: 1, b: 2 } });
const key2 = computeBlueprintKey({ ...base, flags: { b: 2, a: 1 } });
expect(key1).toBe(key2);
});

it('is stable across the order of logical schemas', () => {
const key1 = computeBlueprintKey({
...base,
logicalSchemas: ['app-public', 'auth-public']
});
const key2 = computeBlueprintKey({
...base,
logicalSchemas: ['auth-public', 'app-public']
});
expect(key1).toBe(key2);
});

it('differs when the shape fingerprint differs', () => {
const key1 = computeBlueprintKey({ ...base, shapeFingerprint: 'fingerprint-a' });
const key2 = computeBlueprintKey({ ...base, shapeFingerprint: 'fingerprint-b' });
expect(key1).not.toBe(key2);
});

it('differs when flag values differ', () => {
const key1 = computeBlueprintKey({ ...base, flags: { a: 1, b: 2 } });
const key2 = computeBlueprintKey({ ...base, flags: { a: 1, b: 3 } });
expect(key1).not.toBe(key2);
});

it('treats undefined flags like an empty flag set', () => {
const key1 = computeBlueprintKey({ ...base, flags: undefined });
const key2 = computeBlueprintKey({ ...base, flags: {} });
expect(key1).toBe(key2);
});

it('differs when the mode or api name differs', () => {
expect(computeBlueprintKey({ ...base, mode: 'domain-lookup' })).not.toBe(
computeBlueprintKey({ ...base, mode: 'api-name-header' })
);
expect(computeBlueprintKey({ ...base, apiName: 'customer-api' })).not.toBe(
computeBlueprintKey({ ...base, apiName: 'other-api' })
);
});

it('treats null and empty api name identically', () => {
expect(computeBlueprintKey({ ...base, apiName: null })).toBe(
computeBlueprintKey({ ...base, apiName: '' })
);
});
});

describe('computeBlueprintKey dbname isolation (W3 fix)', () => {
const { computeBlueprintKey } = require('../blueprint');
const base = {
logicalSchemas: ['app-public'],
shapeFingerprint: 'f'.repeat(64),
flags: undefined as Record<string, any> | undefined,
apiName: 'api',
mode: 'public'
};

it('same-shape tenants in DIFFERENT physical databases get DIFFERENT keys', () => {
const a = computeBlueprintKey({ ...base, dbname: 'db_a' });
const b = computeBlueprintKey({ ...base, dbname: 'db_b' });
expect(a).not.toBe(b);
});

it('same dbname yields a stable key', () => {
expect(computeBlueprintKey({ ...base, dbname: 'db_a' })).toBe(
computeBlueprintKey({ ...base, dbname: 'db_a' })
);
});
});
Loading
Loading