Skip to content

Commit 236ebaf

Browse files
authored
Add tests for @gel/ai (#1264)
There were many yaks to shave which is why I've held of on this for so long, but after releasing an embarrassing bug into a stable release, it's time to actually tighten up here. 📈
1 parent 1ac54d6 commit 236ebaf

File tree

18 files changed

+934
-13
lines changed

18 files changed

+934
-13
lines changed

packages/ai/jest.config.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export const TS_EXT_TO_TREAT_AS_ESM = [".ts", ".tsx", ".mts"];
2+
export const JS_EXT_TO_TREAT_AS_ESM = [".jsx"];
3+
export const ESM_TS_JS_TRANSFORM_PATTERN = "^.+\\.m?[tj]sx?$";
4+
5+
export default {
6+
testEnvironment: "node",
7+
testPathIgnorePatterns: ["./dist"],
8+
globalSetup: "./test/globalSetup.ts",
9+
globalTeardown: "./test/globalTeardown.ts",
10+
extensionsToTreatAsEsm: [
11+
...JS_EXT_TO_TREAT_AS_ESM,
12+
...TS_EXT_TO_TREAT_AS_ESM,
13+
],
14+
transform: {
15+
[ESM_TS_JS_TRANSFORM_PATTERN]: [
16+
"ts-jest",
17+
{
18+
tsconfig: "./tsconfig.test.json",
19+
useESM: true,
20+
},
21+
],
22+
},
23+
};

packages/ai/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,19 @@
2828
],
2929
"scripts": {
3030
"typecheck": "tsc --project tsconfig.json --noEmit",
31-
"test": "jest --detectOpenHandles --passWithNoTests",
31+
"test": "NODE_OPTIONS='--experimental-global-webcrypto --experimental-vm-modules' jest --detectOpenHandles",
3232
"build": "tsc --project tsconfig.json",
3333
"lint": "eslint --quiet",
3434
"lint:fix": "eslint --fix"
3535
},
3636
"devDependencies": {
37+
"@repo/test-utils": "*",
3738
"@repo/tsconfig": "*",
3839
"@types/jest": "^29.5.12",
3940
"@types/node": "^20.12.13",
4041
"gel": "^2.0.0",
4142
"jest": "29.7.0",
42-
"ts-jest": "29.1.4",
43+
"ts-jest": "^29.1.4",
4344
"typescript": "^5.5.2"
4445
},
4546
"peerDependencies": {

packages/ai/test/core.test.ts

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { type Client } from "gel";
2+
import { createRAGClient } from "../dist/index.js";
3+
import { getClient, waitFor, getAvailableExtensions } from "@repo/test-utils";
4+
import { createMockHttpServer, type MockHttpServer } from "./mockHttpServer";
5+
6+
const availableExtensions = getAvailableExtensions();
7+
8+
if (availableExtensions.has("ai")) {
9+
let mockServer: MockHttpServer;
10+
11+
beforeAll(async () => {
12+
// Start the mock server
13+
mockServer = createMockHttpServer();
14+
15+
const client = getClient();
16+
await client.ensureConnected();
17+
try {
18+
await client.execute(`
19+
create extension pgvector;
20+
create extension ai;
21+
22+
create type TestEmbeddingModel extending ext::ai::EmbeddingModel {
23+
alter annotation ext::ai::model_name := "text-embedding-test";
24+
alter annotation ext::ai::model_provider := "custom::test";
25+
alter annotation ext::ai::embedding_model_max_input_tokens := "8191";
26+
alter annotation ext::ai::embedding_model_max_batch_tokens := "16384";
27+
alter annotation ext::ai::embedding_model_max_output_dimensions := "10";
28+
alter annotation ext::ai::embedding_model_supports_shortening := "true";
29+
};
30+
31+
create type TestTextGenerationModel extending ext::ai::TextGenerationModel {
32+
alter annotation ext::ai::model_name := "text-generation-test";
33+
alter annotation ext::ai::model_provider := "custom::test";
34+
alter annotation ext::ai::text_gen_model_context_window := "16385";
35+
};
36+
37+
create type Astronomy {
38+
create required property content: str;
39+
40+
create deferred index ext::ai::index(embedding_model := "text-embedding-test") on (.content);
41+
};
42+
43+
configure current branch insert ext::ai::CustomProviderConfig {
44+
name := "custom::test",
45+
secret := "dummy-key",
46+
api_url := "${mockServer.url}/v1",
47+
api_style := ext::ai::ProviderAPIStyle.OpenAI,
48+
};
49+
50+
configure current branch set ext::ai::Config::indexer_naptime := <duration>"100ms";
51+
`);
52+
} finally {
53+
await client.close();
54+
}
55+
}, 25_000);
56+
57+
afterAll(async () => {
58+
// Stop the mock server
59+
if (mockServer) {
60+
await mockServer.close();
61+
}
62+
});
63+
64+
describe("@gel/ai", () => {
65+
let client: Client;
66+
beforeEach(() => {
67+
mockServer.resetRequests();
68+
});
69+
70+
afterEach(async () => {
71+
await client?.close();
72+
});
73+
74+
test("RAG query", async () => {
75+
client = getClient({
76+
tlsSecurity: "insecure",
77+
});
78+
await client.execute(`
79+
insert Astronomy { content := 'Skies on Mars are red' };
80+
insert Astronomy { content := 'Skies on Earth are blue' };
81+
`);
82+
await waitFor(async () =>
83+
expect(mockServer.getEmbeddingsRequests().length).toBe(1),
84+
);
85+
86+
const ragClient = createRAGClient(client, {
87+
model: "text-generation-test",
88+
}).withContext({
89+
query: "select Astronomy",
90+
});
91+
92+
const result = await ragClient.queryRag({
93+
prompt: "What color are the skies on Mars?",
94+
});
95+
96+
expect(result).toEqual("This is a mock response.");
97+
98+
const streamedResult = ragClient.streamRag({
99+
prompt: "What color are the skies on Mars?",
100+
});
101+
102+
let streamedResultString = "";
103+
for await (const message of streamedResult) {
104+
if (
105+
message.type === "content_block_start" &&
106+
message.content_block.type === "text"
107+
) {
108+
streamedResultString += message.content_block.text;
109+
} else if (
110+
message.type === "content_block_delta" &&
111+
message.delta.type === "text_delta"
112+
) {
113+
streamedResultString += message.delta.text;
114+
}
115+
}
116+
117+
expect(streamedResultString).toEqual("This is a mock response.");
118+
}, 25_000);
119+
120+
test("embedding request", async () => {
121+
client = getClient({
122+
tlsSecurity: "insecure",
123+
});
124+
const ragClient = createRAGClient(client, {
125+
model: "text-generation-test",
126+
});
127+
128+
const result = await ragClient.generateEmbeddings({
129+
inputs: ["beep boop!"],
130+
model: "text-embedding-test",
131+
});
132+
133+
await waitFor(
134+
async () => expect(mockServer.getEmbeddingsRequests().length).toBe(1),
135+
10_000,
136+
500,
137+
);
138+
139+
expect(result).toEqual(
140+
// The number of occurences of the first ten letters of the alphabet.
141+
// two 'b's and two 'e's.
142+
[0, 2, 0, 0, 2, 0, 0, 0, 0, 0],
143+
);
144+
});
145+
});
146+
}

packages/ai/test/globalSetup.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as process from "node:process";
2+
import {
3+
connectToServer,
4+
generateStatusFileName,
5+
getServerCommand,
6+
getWSLPath,
7+
startServer,
8+
ConnectConfig,
9+
} from "@repo/test-utils";
10+
11+
export default async () => {
12+
// tslint:disable-next-line
13+
console.log("\nStarting Gel test cluster...");
14+
15+
const statusFile = generateStatusFileName("node");
16+
console.log("Node status file:", statusFile);
17+
18+
const { args, availableFeatures } = getServerCommand(getWSLPath(statusFile));
19+
console.log(`Starting server...`);
20+
const { proc, config } = await startServer(args, statusFile);
21+
22+
const { client, version } = await connectToServer(config);
23+
24+
const jestConfig: ConnectConfig = {
25+
...config,
26+
user: version.major >= 6 ? "admin" : "edgedb",
27+
};
28+
29+
// @ts-ignore
30+
global.gelProc = proc;
31+
32+
process.env._JEST_GEL_CONNECT_CONFIG = JSON.stringify(jestConfig);
33+
process.env._JEST_GEL_AVAILABLE_FEATURES = JSON.stringify(availableFeatures);
34+
35+
// @ts-ignore
36+
global.gelConn = client;
37+
process.env._JEST_GEL_VERSION = JSON.stringify(version);
38+
39+
const availableExtensions = (
40+
await client.query<{
41+
name: string;
42+
version: { major: number; minor: number };
43+
}>(`select sys::ExtensionPackage {name, version}`)
44+
).map(({ name, version }) => [name, version]);
45+
process.env._JEST_GEL_AVAILABLE_EXTENSIONS =
46+
JSON.stringify(availableExtensions);
47+
48+
// tslint:disable-next-line
49+
console.log(`Gel test cluster is up [port: ${jestConfig.port}]...`);
50+
};

packages/ai/test/globalTeardown.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { shutdown } from "@repo/test-utils";
2+
3+
export default async () => {
4+
// tslint:disable-next-line
5+
console.log("Shutting down Gel test cluster...");
6+
7+
try {
8+
// @ts-ignore
9+
await shutdown(global.gelProc, global.gelConn);
10+
} finally {
11+
// tslint:disable-next-line
12+
console.log("Gel test cluster is down...");
13+
}
14+
};

0 commit comments

Comments
 (0)