/** * Copyright (c) 2026, Salesforce, Inc., * All rights reserved. * For full license text, see the LICENSE.txt file */ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { buildSchema } from "graphql"; import { describe, expect, it } from "vitest"; import { makeObjectInfo } from "../../__tests__/helpers/object-info.js"; describe("lib/prime-schema", () => { it("atomic-write: temp file is renamed to final path, no .tmp left behind", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-prime-")); const finalPath = path.join(tmpRoot, "abc123.json"); const { atomicWriteJson } = await import("../fs-utils.js"); atomicWriteJson(finalPath, { hello: "world" }); expect(fs.existsSync(finalPath)).toBe(true); expect(JSON.parse(fs.readFileSync(finalPath, "utf-8"))).toEqual({ hello: "world" }); const leftovers = fs.readdirSync(tmpRoot).filter((f) => f.includes(".tmp")); expect(leftovers).toEqual([]); fs.rmSync(tmpRoot, { recursive: true, force: true }); }); it("atomic-write: atomicWriteText renames into place with no .tmp left behind", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-prime-text-")); const finalPath = path.join(tmpRoot, "schema.graphql"); const { atomicWriteText } = await import("../fs-utils.js"); atomicWriteText(finalPath, "type Query { a: Int }"); expect(fs.existsSync(finalPath)).toBe(true); expect(fs.readFileSync(finalPath, "utf-8")).toBe("type Query { a: Int }"); const leftovers = fs.readdirSync(tmpRoot).filter((f) => f.includes(".tmp")); expect(leftovers).toEqual([]); fs.rmSync(tmpRoot, { recursive: true, force: true }); }); it("atomic-write: parallel calls in the same process do not collide", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-prime-parallel-")); const finalPath = path.join(tmpRoot, "p.json"); const { atomicWriteJson } = await import("../fs-utils.js"); // 16 parallel writers — would surface a temp-name collision quickly. await Promise.all( Array.from({ length: 16 }, (_, i) => Promise.resolve().then(() => atomicWriteJson(finalPath, { i })), ), ); expect(fs.existsSync(finalPath)).toBe(true); const leftovers = fs.readdirSync(tmpRoot).filter((f) => f.includes(".tmp")); expect(leftovers).toEqual([]); const contents = JSON.parse(fs.readFileSync(finalPath, "utf-8")); expect(typeof contents.i).toBe("number"); fs.rmSync(tmpRoot, { recursive: true, force: true }); }); it("lock: holder runs work, lock dir cleaned up after", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-lock-")); const finalPath = path.join(tmpRoot, "x.json"); const { withSchemaLock } = await import("../prime-schema.js"); let ran = false; await withSchemaLock(finalPath, async () => { ran = true; expect(fs.existsSync(`${finalPath}.lock`)).toBe(true); }); expect(ran).toBe(true); expect(fs.existsSync(`${finalPath}.lock`)).toBe(false); fs.rmSync(tmpRoot, { recursive: true, force: true }); }); it("lock: cleanup tolerates stray files inside the lock dir (e.g. .DS_Store)", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-lock-stray-")); const finalPath = path.join(tmpRoot, "stray.json"); const { withSchemaLock } = await import("../prime-schema.js"); await withSchemaLock(finalPath, async () => { // Simulate a stray file appearing inside the lock dir mid-work // (macOS Finder, an editor swap, an OS indexer, etc.). fs.writeFileSync(path.join(`${finalPath}.lock`, ".DS_Store"), "stray"); }); expect(fs.existsSync(`${finalPath}.lock`)).toBe(false); fs.rmSync(tmpRoot, { recursive: true, force: true }); }); it("lock: lock survives errors in the work function", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-lock-err-")); const finalPath = path.join(tmpRoot, "y.json"); const { withSchemaLock } = await import("../prime-schema.js"); await expect( withSchemaLock(finalPath, async () => { throw new Error("boom"); }), ).rejects.toThrow(/boom/); expect(fs.existsSync(`${finalPath}.lock`)).toBe(false); fs.rmSync(tmpRoot, { recursive: true, force: true }); }); it("lock: second waiter short-circuits when first holder primed the cache", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-lock-race-")); const finalPath = path.join(tmpRoot, "z.json"); const { withSchemaLock } = await import("../prime-schema.js"); const { atomicWriteJson } = await import("../fs-utils.js"); let firstStarted = false; let firstFinishedWriting = false; let secondWorkRan = false; const first = withSchemaLock(finalPath, async () => { firstStarted = true; await new Promise((r) => setTimeout(r, 200)); atomicWriteJson(finalPath, { primed: true }); firstFinishedWriting = true; }); // Wait for first to acquire before launching second. while (!firstStarted) await new Promise((r) => setTimeout(r, 5)); const second = withSchemaLock(finalPath, async () => { secondWorkRan = true; return "ran" as const; }); const [, secondResult] = await Promise.all([first, second]); expect(firstFinishedWriting).toBe(true); expect(secondWorkRan).toBe(false); expect(secondResult).toBeUndefined(); fs.rmSync(tmpRoot, { recursive: true, force: true }); }); it("lock: stale lock dir older than STALE_LOCK_MS is reclaimed", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-lock-stale-")); const finalPath = path.join(tmpRoot, "w.json"); const lockPath = `${finalPath}.lock`; fs.mkdirSync(lockPath); // Backdate the lock dir's mtime to well past the 7-min stale threshold. const past = new Date(Date.now() - 10 * 60_000); fs.utimesSync(lockPath, past, past); const { withSchemaLock } = await import("../prime-schema.js"); let ran = false; await withSchemaLock(finalPath, async () => { ran = true; }); expect(ran).toBe(true); fs.rmSync(tmpRoot, { recursive: true, force: true }); }); it("primeSchemaWithLock: returns metadata + duration on first prime", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-prime-pub-")); process.env.GRAPHITI_HOME = tmpRoot; try { const { primeSchemaWithLock } = await import("../prime-schema.js"); let downloadCalls = 0; const stubDeps = { getOrgAuth: async () => ({ alias: "test-org", instanceUrl: "https://example.my.salesforce.com", accessToken: "fake-token", username: "user@example.com", orgId: "00Dxx0000000000", }), downloadSchema: async () => { downloadCalls++; // downloadSchema's real impl writes the cache file. Simulate that. const { schemaCacheKeyForInstanceUrl } = await import("../introspect.js"); const { atomicWriteJson } = await import("../fs-utils.js"); const cacheKey = schemaCacheKeyForInstanceUrl("https://example.my.salesforce.com"); atomicWriteJson(path.join(tmpRoot, "schemas", `${cacheKey}.json`), { data: { __schema: { types: [] } }, __graphiti: { instanceUrl: "https://example.my.salesforce.com", alias: "test-org", cachedAt: new Date().toISOString(), }, }); return { cacheKey, instanceUrl: "https://example.my.salesforce.com", typeCount: 0, downloadedAt: new Date().toISOString(), filePath: path.join(tmpRoot, "schemas", `${cacheKey}.json`), }; }, }; const result = await primeSchemaWithLock("test-org", stubDeps); expect(result.cached).toBe(false); expect(typeof result.durationMs).toBe("number"); expect(result.durationMs).toBeGreaterThanOrEqual(0); expect(result.filePath).toMatch(/\.json$/); expect(fs.existsSync(result.filePath)).toBe(true); expect(downloadCalls).toBe(1); // Second call must be a cache hit, not a re-introspection. const second = await primeSchemaWithLock("test-org", stubDeps); expect(second.cached).toBe(true); expect(second.durationMs).toBe(0); expect(downloadCalls).toBe(1); expect(second.instanceUrl).toBe("https://example.my.salesforce.com"); } finally { delete process.env.GRAPHITI_HOME; fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); it("primeSchemaWithLock: surfaces auth-missing error verbatim", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-prime-noauth-")); process.env.GRAPHITI_HOME = tmpRoot; try { const { primeSchemaWithLock } = await import("../prime-schema.js"); const stubDeps = { getOrgAuth: async () => { throw new Error( 'Unknown org "nonexistent-org". Run `sf org login web -a nonexistent-org`.', ); }, downloadSchema: async () => { throw new Error("should not be called"); }, }; await expect(primeSchemaWithLock("nonexistent-org", stubDeps)).rejects.toThrow( /unknown org|sf org login/i, ); } finally { delete process.env.GRAPHITI_HOME; fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); // ── forceRefresh (W-22845606) ────────────────────────────────────────────── const INSTANCE_URL = "https://example.my.salesforce.com"; async function schemaFilePath(tmpRoot: string): Promise { const { schemaCacheKeyForInstanceUrl } = await import("../introspect.js"); return path.join(tmpRoot, "schemas", `${schemaCacheKeyForInstanceUrl(INSTANCE_URL)}.json`); } async function writeSchemaFile(tmpRoot: string): Promise { const { atomicWriteJson } = await import("../fs-utils.js"); const fp = await schemaFilePath(tmpRoot); atomicWriteJson(fp, { data: { __schema: { types: [] } }, __graphiti: { instanceUrl: INSTANCE_URL, alias: "test-org", cachedAt: new Date().toISOString(), }, }); return fp; } // Build stub PrimeDeps whose downloadSchema increments a counter, optionally // throws (per the `throwOn` map), and otherwise writes the cache file. function makeStubDeps( tmpRoot: string, opts: { throwOn?: Record; delayMs?: number } = {}, ) { let calls = 0; const deps = { getOrgAuth: async () => ({ alias: "test-org", instanceUrl: INSTANCE_URL, accessToken: "fake-token", username: "user@example.com", orgId: "00Dxx0000000000", }), downloadSchema: async () => { calls++; const toThrow = opts.throwOn?.[calls]; if (toThrow) throw toThrow; if (opts.delayMs) await new Promise((r) => setTimeout(r, opts.delayMs)); await writeSchemaFile(tmpRoot); const { schemaCacheKeyForInstanceUrl } = await import("../introspect.js"); return { cacheKey: schemaCacheKeyForInstanceUrl(INSTANCE_URL), instanceUrl: INSTANCE_URL, typeCount: 0, downloadedAt: new Date().toISOString(), filePath: await schemaFilePath(tmpRoot), }; }, }; return { deps, calls: () => calls }; } it("withSchemaLock: honors a custom skipIf predicate", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-skipif-")); const finalPath = path.join(tmpRoot, "s.json"); try { const { withSchemaLock } = await import("../prime-schema.js"); let ran = false; const skipResult = await withSchemaLock(finalPath, async () => (ran = true), { skipIf: () => true, }); expect(ran).toBe(false); expect(skipResult).toBeUndefined(); await withSchemaLock(finalPath, async () => (ran = true), { skipIf: () => false }); expect(ran).toBe(true); } finally { fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); it("primeSchemaWithLock: forceRefresh re-downloads even though the file exists", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-refresh-")); process.env.GRAPHITI_HOME = tmpRoot; try { const { primeSchemaWithLock } = await import("../prime-schema.js"); const { deps, calls } = makeStubDeps(tmpRoot); const first = await primeSchemaWithLock("test-org", deps); expect(first.cached).toBe(false); expect(first.refreshed).toBe(false); expect(calls()).toBe(1); // Without forceRefresh, a second call is a pure cache hit. const hit = await primeSchemaWithLock("test-org", deps); expect(hit.cached).toBe(true); expect(hit.refreshed).toBe(false); expect(calls()).toBe(1); // With forceRefresh, it re-downloads despite the existing file. const refreshed = await primeSchemaWithLock("test-org", deps, { forceRefresh: true }); expect(refreshed.refreshed).toBe(true); expect(refreshed.cached).toBe(false); expect(calls()).toBe(2); } finally { delete process.env.GRAPHITI_HOME; fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); it("primeSchemaWithLock: forceRefresh evicts the in-memory parsed-schema cache (cache #2)", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-refresh-schemacache-")); process.env.GRAPHITI_HOME = tmpRoot; try { const { primeSchemaWithLock } = await import("../prime-schema.js"); const { primeSchemaCache, getSchema } = await import("../walker.js"); // Seed the in-memory parsed-schema cache under the instance URL key. const sentinel = buildSchema("type Query { sentinel: Int }"); primeSchemaCache(INSTANCE_URL, sentinel); expect(getSchema(INSTANCE_URL)).toBe(sentinel); // A forced refresh must route clearSchemaCacheByUrl with the resolved // instance URL, evicting the sentinel. After eviction, getSchema misses // the cache and rebuilds from the freshly-written introspection on disk // — so it returns a NEW schema object, never the seeded sentinel. await primeSchemaWithLock("test-org", makeStubDeps(tmpRoot).deps, { forceRefresh: true }); expect(getSchema(INSTANCE_URL)).not.toBe(sentinel); } finally { delete process.env.GRAPHITI_HOME; fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); it("primeSchemaWithLock: forceRefresh clears the ObjectInfo cache on success", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-refresh-oi-")); process.env.GRAPHITI_HOME = tmpRoot; try { const { primeSchemaWithLock } = await import("../prime-schema.js"); const { setCachedObjectInfo, getCachedObjectInfo } = await import("../object-info.js"); const { deps } = makeStubDeps(tmpRoot); await primeSchemaWithLock("test-org", deps); // prime first setCachedObjectInfo("test-org", "Account", makeObjectInfo()); expect(getCachedObjectInfo("test-org", "Account")).not.toBeNull(); await primeSchemaWithLock("test-org", deps, { forceRefresh: true }); expect(getCachedObjectInfo("test-org", "Account")).toBeNull(); } finally { delete process.env.GRAPHITI_HOME; fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); it("primeSchemaWithLock: concurrent forceRefresh coalesces into a single download", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-refresh-coalesce-")); process.env.GRAPHITI_HOME = tmpRoot; try { const { primeSchemaWithLock } = await import("../prime-schema.js"); // Pre-existing cache, backdated so the new download's mtime is strictly newer. const fp = await writeSchemaFile(tmpRoot); const past = new Date(Date.now() - 5_000); fs.utimesSync(fp, past, past); const { deps, calls } = makeStubDeps(tmpRoot, { delayMs: 200 }); const results = await Promise.all([ primeSchemaWithLock("test-org", deps, { forceRefresh: true }), primeSchemaWithLock("test-org", deps, { forceRefresh: true }), ]); // This test owns the *single-introspection* guarantee. That the coalesced // (loser) caller still clears ITS OWN stale caches is proven separately by // the R4 test below — here both callers share one alias, so a cache // assertion couldn't distinguish the loser's clear from the winner's. expect(calls()).toBe(1); // single introspection const refreshedCount = results.filter((r) => r.refreshed).length; expect(refreshedCount).toBe(1); // exactly one performed the refresh } finally { delete process.env.GRAPHITI_HOME; fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); it("primeSchemaWithLock: sequential forceRefresh each re-download", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-refresh-seq-")); process.env.GRAPHITI_HOME = tmpRoot; try { const { primeSchemaWithLock } = await import("../prime-schema.js"); const { deps, calls } = makeStubDeps(tmpRoot); await primeSchemaWithLock("test-org", deps); // 1 await primeSchemaWithLock("test-org", deps, { forceRefresh: true }); // 2 await primeSchemaWithLock("test-org", deps, { forceRefresh: true }); // 3 expect(calls()).toBe(3); } finally { delete process.env.GRAPHITI_HOME; fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); it("primeSchemaWithLock: a failed forceRefresh keeps the old caches and throws SchemaRefreshError", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-refresh-fail-")); process.env.GRAPHITI_HOME = tmpRoot; try { const { primeSchemaWithLock, SchemaRefreshError } = await import("../prime-schema.js"); const fp = await writeSchemaFile(tmpRoot); const originalContent = fs.readFileSync(fp, "utf-8"); const past = new Date(Date.now() - 5_000); fs.utimesSync(fp, past, past); // downloadSchema throws after the connection layer exhausts its // timeout/retry. A forced refresh must keep every cache intact and // surface a staleness-aware SchemaRefreshError. const { deps, calls } = makeStubDeps(tmpRoot, { throwOn: { 1: new Error("introspection failed") }, }); const err = await primeSchemaWithLock("test-org", deps, { forceRefresh: true }).catch( (e) => e, ); expect(err).toBeInstanceOf(SchemaRefreshError); expect(err.staleSince).toBeTruthy(); // surviving cache age anchor expect(err.instanceUrl).toBe(INSTANCE_URL); expect(calls()).toBe(1); // primeSchemaWithLock calls downloadSchema once expect(fs.readFileSync(fp, "utf-8")).toBe(originalContent); // old cache intact } finally { delete process.env.GRAPHITI_HOME; fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); it("primeSchemaWithLock: a failed refresh with NO prior cache has no staleSince", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-refresh-nocache-")); process.env.GRAPHITI_HOME = tmpRoot; try { const { primeSchemaWithLock, SchemaRefreshError } = await import("../prime-schema.js"); const { deps } = makeStubDeps(tmpRoot, { throwOn: { 1: new Error("introspection failed") }, }); const err = await primeSchemaWithLock("test-org", deps, { forceRefresh: true }).catch( (e) => e, ); expect(err).toBeInstanceOf(SchemaRefreshError); expect(err.staleSince).toBeUndefined(); } finally { delete process.env.GRAPHITI_HOME; fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); // ── 401/403 introspection auth reclassification (W-23335328) ─────────────── // // A 401/403 from the introspection POST is an AUTH failure (expired/unauthorized // session), not a schema/cache problem. It must surface as AuthError (→ `Auth:`), // on BOTH the lazy-prime and forced-refresh paths, keyed off the cause shape. // A real jsforce HttpApiError carries a string errorCode/name (ERROR_HTTP_401 or // the body code INVALID_SESSION_ID) and NO numeric statusCode. function jsforce401(errorCode = "ERROR_HTTP_401"): Error { // Mirror jsforce-node's HttpApiError: name === errorCode, no numeric statusCode. return Object.assign(new Error("Session expired or invalid"), { name: errorCode, errorCode, }); } it("primeSchemaWithLock: a lazy prime that 401s throws AuthError, not SchemaError", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-prime-401-")); process.env.GRAPHITI_HOME = tmpRoot; try { const { primeSchemaWithLock } = await import("../prime-schema.js"); const { AuthError } = await import("../errors.js"); const { deps } = makeStubDeps(tmpRoot, { throwOn: { 1: jsforce401() } }); const err = await primeSchemaWithLock("test-org", deps).catch((e) => e); expect(err).toBeInstanceOf(AuthError); expect(err.message).toMatch(/re-?authenticate/i); } finally { delete process.env.GRAPHITI_HOME; fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); it("primeSchemaWithLock: a forced refresh WITH a surviving cache that 401s throws AuthError (not a soft SchemaRefreshError)", async () => { // N3 regression: even though a usable cached schema survives on disk, a dead // session must hard-fail as AuthError — NOT degrade to the soft-warning // SchemaRefreshError path (which buildConnect would turn into a non-erroring // success). The reclassification is keyed off the cause, not cache survival. const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-refresh-401-")); process.env.GRAPHITI_HOME = tmpRoot; try { const { primeSchemaWithLock, SchemaRefreshError } = await import("../prime-schema.js"); const { AuthError } = await import("../errors.js"); const fp = await writeSchemaFile(tmpRoot); const originalContent = fs.readFileSync(fp, "utf-8"); const past = new Date(Date.now() - 5_000); fs.utimesSync(fp, past, past); const { deps } = makeStubDeps(tmpRoot, { throwOn: { 1: jsforce401("INVALID_SESSION_ID") } }); const err = await primeSchemaWithLock("test-org", deps, { forceRefresh: true }).catch( (e) => e, ); expect(err).toBeInstanceOf(AuthError); expect(err).not.toBeInstanceOf(SchemaRefreshError); // The surviving cache is left untouched — clears only happen after a // successful download, and the throw precedes any clear. expect(fs.readFileSync(fp, "utf-8")).toBe(originalContent); } finally { delete process.env.GRAPHITI_HOME; fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); it("primeSchemaWithLock: a forced refresh with NO prior cache that 401s throws AuthError", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-refresh-401-nocache-")); process.env.GRAPHITI_HOME = tmpRoot; try { const { primeSchemaWithLock } = await import("../prime-schema.js"); const { AuthError } = await import("../errors.js"); const { deps } = makeStubDeps(tmpRoot, { throwOn: { 1: jsforce401("ERROR_HTTP_403") } }); const err = await primeSchemaWithLock("test-org", deps, { forceRefresh: true }).catch( (e) => e, ); expect(err).toBeInstanceOf(AuthError); } finally { delete process.env.GRAPHITI_HOME; fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); it("primeSchemaWithLock: a non-auth 4xx refresh failure STILL yields the soft SchemaRefreshError (no over-broadening)", async () => { // Guard the boundary: only 401/403 reclassify. A 404 (or any other cause) // with a surviving cache keeps the existing soft-warning SchemaRefreshError path. const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-refresh-404-")); process.env.GRAPHITI_HOME = tmpRoot; try { const { primeSchemaWithLock, SchemaRefreshError } = await import("../prime-schema.js"); const fp = await writeSchemaFile(tmpRoot); const past = new Date(Date.now() - 5_000); fs.utimesSync(fp, past, past); const { deps } = makeStubDeps(tmpRoot, { throwOn: { 1: Object.assign(new Error("not found"), { statusCode: 404 }) }, }); const err = await primeSchemaWithLock("test-org", deps, { forceRefresh: true }).catch( (e) => e, ); expect(err).toBeInstanceOf(SchemaRefreshError); expect(err.staleSince).toBeTruthy(); } finally { delete process.env.GRAPHITI_HOME; fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); it("primeSchemaWithLock: a coalesced refresh clears its OWN stale caches (R4, isolated from the winner)", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-refresh-r4-")); process.env.GRAPHITI_HOME = tmpRoot; try { const { primeSchemaWithLock } = await import("../prime-schema.js"); const { setCachedObjectInfo, getCachedObjectInfo } = await import("../object-info.js"); // Pre-existing cache, backdated so the winner's download is strictly newer. const fp = await writeSchemaFile(tmpRoot); const past = new Date(Date.now() - 5_000); fs.utimesSync(fp, past, past); // Two distinct aliases for the SAME org (instanceUrl). Seed ObjectInfo // ONLY under the loser's alias: the winner's clearObjectInfoCache(winner) // can never touch "loser:Account", so if it ends up null it was the // loser's post-lock R4 block that cleared it — not the winner's success path. setCachedObjectInfo("loser", "Account", makeObjectInfo()); let winnerDownloading = false; const winnerDeps = { getOrgAuth: async () => ({ alias: "winner", instanceUrl: INSTANCE_URL, accessToken: "t", username: "u@e.com", orgId: "00D", }), downloadSchema: async () => { winnerDownloading = true; await new Promise((r) => setTimeout(r, 200)); await writeSchemaFile(tmpRoot); const { schemaCacheKeyForInstanceUrl } = await import("../introspect.js"); return { cacheKey: schemaCacheKeyForInstanceUrl(INSTANCE_URL), instanceUrl: INSTANCE_URL, typeCount: 0, downloadedAt: new Date().toISOString(), filePath: fp, }; }, }; let loserDownloads = 0; const loserDeps = { getOrgAuth: async () => ({ alias: "loser", instanceUrl: INSTANCE_URL, accessToken: "t", username: "u@e.com", orgId: "00D", }), downloadSchema: async () => { loserDownloads++; throw new Error("loser must coalesce, not download"); }, }; const winner = primeSchemaWithLock("winner", winnerDeps, { forceRefresh: true }); while (!winnerDownloading) await new Promise((r) => setTimeout(r, 5)); const loser = primeSchemaWithLock("loser", loserDeps, { forceRefresh: true }); const [, loserResult] = await Promise.all([winner, loser]); expect(loserResult.refreshed).toBe(false); // coalesced expect(loserResult.cached).toBe(true); expect(loserDownloads).toBe(0); // never downloaded expect(getCachedObjectInfo("loser", "Account")).toBeNull(); // cleared by R4, not the winner } finally { delete process.env.GRAPHITI_HOME; fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); it("primeSchemaWithLock: awaits an async getOrgAuth and returns its instanceUrl", async () => { const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "graphiti-prime-async-")); process.env.GRAPHITI_HOME = tmpRoot; try { const { primeSchemaWithLock } = await import("../prime-schema.js"); const stubDeps = { getOrgAuth: async () => ({ alias: "o", username: "u", instanceUrl: "https://o.my.salesforce.com", accessToken: "t", orgId: "00D", }), downloadSchema: async () => { const { schemaCacheKeyForInstanceUrl } = await import("../introspect.js"); const { atomicWriteJson } = await import("../fs-utils.js"); const cacheKey = schemaCacheKeyForInstanceUrl("https://o.my.salesforce.com"); const filePath = path.join(tmpRoot, "schemas", `${cacheKey}.json`); atomicWriteJson(filePath, { data: { __schema: { types: [] } } }); return { cacheKey, instanceUrl: "https://o.my.salesforce.com", typeCount: 0, downloadedAt: new Date().toISOString(), filePath, }; }, }; const result = await primeSchemaWithLock("o", stubDeps); expect(result.instanceUrl).toBe("https://o.my.salesforce.com"); } finally { delete process.env.GRAPHITI_HOME; fs.rmSync(tmpRoot, { recursive: true, force: true }); } }); });