Skip to content

Commit fee8c9e

Browse files
committed
e2e: guard full Graph catalog add + serve at real scale
Drives the public API to add every Microsoft Graph workload (the full ~37MB / 16.5k-operation spec) and then list the served catalog. Pins both former OOM sites at real scale: the streaming add persists one binding per operation, and tools/list rebuilds >5000 tools from the persisted bindings plus the content-addressed defs blob, never re-parsing the spec. Proven green on cloud and selfhost.
1 parent 047e2c0 commit fee8c9e

1 file changed

Lines changed: 122 additions & 0 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { randomBytes } from "node:crypto";
2+
3+
import { expect } from "@effect/vitest";
4+
import { Effect } from "effect";
5+
import { composePluginApi } from "@executor-js/api/server";
6+
import {
7+
MICROSOFT_AUTH_TEMPLATE_SLUG,
8+
MICROSOFT_GRAPH_ALL_PRESET_IDS,
9+
MICROSOFT_GRAPH_DELEGATED_DEFAULT_SCOPES,
10+
} from "@executor-js/plugin-microsoft";
11+
import { microsoftHttpPlugin } from "@executor-js/plugin-microsoft/api";
12+
import { AuthTemplateSlug, ConnectionName, IntegrationSlug } from "@executor-js/sdk/shared";
13+
14+
import { scenario } from "../src/scenario";
15+
import { Api, Target } from "../src/services";
16+
17+
const api = composePluginApi([microsoftHttpPlugin()] as const);
18+
19+
type ToolView = {
20+
readonly name: string;
21+
};
22+
23+
const unique = (prefix: string) => `${prefix}_${randomBytes(4).toString("hex")}`;
24+
25+
// Adding *every* Graph workload pulls the full Microsoft Graph OpenAPI document
26+
// (~37MB, ~16.5k operations) and persists a binding per operation. That whole-
27+
// document path used to 503 on the Cloudflare worker: parsing the spec, and
28+
// then re-parsing it on every tools/list, each rebuilt a ~300MB JS tree that
29+
// blew the 128MB isolate. This scenario is the regression guard for both sites
30+
// at real scale: the add streams the compile + persist, and tools/list serves
31+
// the catalog back from the persisted bindings (+ the content-addressed defs
32+
// blob) without ever re-parsing the spec. It drives only the public API, so a
33+
// green run is evidence the full catalog lands and serves end to end.
34+
scenario(
35+
"Microsoft Graph: the full catalog adds and serves without re-parsing the spec",
36+
{ timeout: 300_000 },
37+
Effect.gen(function* () {
38+
const target = yield* Target;
39+
const { client: makeApiClient } = yield* Api;
40+
const identity = yield* target.newIdentity();
41+
const client = yield* makeApiClient(api, identity);
42+
43+
const integration = unique("msgraph_full");
44+
const connection = ConnectionName.make("main");
45+
46+
yield* Effect.ensuring(
47+
Effect.gen(function* () {
48+
// Add path (1st former OOM site): the full spec is fetched and
49+
// stream-compiled into one persisted binding per operation.
50+
const added = yield* client.microsoft.addGraph({
51+
payload: {
52+
presetIds: [...MICROSOFT_GRAPH_ALL_PRESET_IDS],
53+
customScopes: [],
54+
slug: integration,
55+
name: "Microsoft Graph (full)",
56+
},
57+
});
58+
expect(added.slug, "the full Graph source keeps the requested slug").toBe(integration);
59+
expect(
60+
added.toolCount,
61+
"adding every Graph workload extracts the whole catalog (thousands of operations)",
62+
).toBeGreaterThan(5_000);
63+
64+
const config = yield* client.microsoft.getConfig({ params: { slug: integration } });
65+
expect(config?.microsoftGraphPresetIds, "every Graph workload preset is persisted").toEqual(
66+
[...MICROSOFT_GRAPH_ALL_PRESET_IDS],
67+
);
68+
expect(
69+
config?.microsoftGraphCoversFullGraph,
70+
"selecting every workload is recognized as full Graph",
71+
).toBe(true);
72+
expect(
73+
config?.microsoftGraphScopes,
74+
"full Graph delegates the app-registration default scope set",
75+
).toEqual([...MICROSOFT_GRAPH_DELEGATED_DEFAULT_SCOPES]);
76+
77+
yield* client.connections.create({
78+
payload: {
79+
owner: "org",
80+
name: connection,
81+
integration: IntegrationSlug.make(integration),
82+
template: AuthTemplateSlug.make(MICROSOFT_AUTH_TEMPLATE_SLUG),
83+
value: "token-xyz",
84+
},
85+
});
86+
87+
// Serve path (2nd former OOM site): tools/list rebuilds the catalog from
88+
// the persisted bindings. The whole catalog must come back, with real
89+
// descriptions, and without re-parsing the 37MB spec.
90+
const tools = yield* client.tools.list({
91+
query: { integration: IntegrationSlug.make(integration), connection },
92+
});
93+
expect(
94+
tools.length,
95+
"the served catalog returns the whole set of operations, not a re-parse failure",
96+
).toBeGreaterThan(5_000);
97+
98+
const names = tools.map((tool: ToolView) => tool.name);
99+
const messageTools = names.filter((name) => name.toLowerCase().includes("message"));
100+
const siteTools = names.filter((name) => name.toLowerCase().includes("site"));
101+
const userTools = names.filter((name) => name.toLowerCase().includes("user"));
102+
expect(messageTools, "the served catalog spans mail operations").not.toEqual([]);
103+
expect(siteTools, "the served catalog spans SharePoint site operations").not.toEqual([]);
104+
expect(userTools, "the served catalog spans directory user operations").not.toEqual([]);
105+
}),
106+
Effect.gen(function* () {
107+
yield* client.connections
108+
.remove({
109+
params: {
110+
owner: "org",
111+
integration: IntegrationSlug.make(integration),
112+
name: connection,
113+
},
114+
})
115+
.pipe(Effect.ignore);
116+
yield* client.microsoft
117+
.removeGraph({ params: { slug: IntegrationSlug.make(integration) } })
118+
.pipe(Effect.ignore);
119+
}),
120+
);
121+
}),
122+
);

0 commit comments

Comments
 (0)