mirror of
https://github.com/langgenius/dify.git
synced 2026-09-05 16:55:14 +08:00
130 lines
4.1 KiB
TypeScript
130 lines
4.1 KiB
TypeScript
import type { Source } from "@knowledge/core";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { createApiDatasourceInvocationClient } from "./datasource-runtime-options";
|
|
|
|
const DIFY_SOURCE: Source = {
|
|
createdAt: "2026-07-03T00:00:00.000Z",
|
|
id: "00000000-0000-4000-8000-000000000001",
|
|
knowledgeSpaceId: "10000000-0000-4000-8000-000000000001",
|
|
metadata: {
|
|
credentialId: "dify-credential-1",
|
|
datasource: "notion_datasource",
|
|
pluginId: "langgenius/notion_datasource",
|
|
provider: "notion_datasource",
|
|
providerKind: "online-document",
|
|
},
|
|
name: "Notion",
|
|
permissionScope: [],
|
|
status: "active",
|
|
type: "connector",
|
|
updatedAt: "2026-07-03T00:00:00.000Z",
|
|
uri: "notion://workspace",
|
|
version: 1,
|
|
};
|
|
|
|
afterEach(() => vi.unstubAllGlobals());
|
|
|
|
describe("createApiDatasourceInvocationClient", () => {
|
|
it("always selects Dify even before Workspace rollout activation", async () => {
|
|
const requests: { readonly init?: RequestInit; readonly input: RequestInfo | URL }[] = [];
|
|
const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
requests.push({ input, ...(init ? { init } : {}) });
|
|
return responseFromBytes(frame({ data: { result: [] }, error: "" }));
|
|
});
|
|
vi.stubGlobal("fetch", fetchMock);
|
|
const env = {
|
|
DIFY_INNER_API_KEY: "inner-key",
|
|
DIFY_INNER_API_URL: "http://api:5001",
|
|
KNOWLEDGE_INTEGRATED_MODE_ENABLED: "false",
|
|
// A direct plugin-daemon branch must never be constructed.
|
|
PLUGIN_DAEMON_MAX_RESPONSE_BYTES: "invalid",
|
|
};
|
|
const client = createApiDatasourceInvocationClient(env);
|
|
|
|
await collect(
|
|
client.dispatch({
|
|
operation: "get_online_document_pages",
|
|
source: DIFY_SOURCE,
|
|
tenantId: "tenant-1",
|
|
}),
|
|
);
|
|
|
|
expect(fetchMock).toHaveBeenCalledOnce();
|
|
expect(requests[0]?.input).toBe("http://api:5001/inner/api/invoke/datasource");
|
|
const body = JSON.parse(String(requests[0]?.init?.body)) as Record<string, unknown>;
|
|
expect(body).toMatchObject({
|
|
credential_id: "dify-credential-1",
|
|
operation: "get_online_document_pages",
|
|
tenant_id: "tenant-1",
|
|
});
|
|
expect(body).not.toHaveProperty("credentials");
|
|
});
|
|
|
|
it("rejects inline source credentials before invoking Dify", async () => {
|
|
const fetchMock = vi.fn(async () =>
|
|
responseFromBytes(frame({ data: { result: [] }, error: "" })),
|
|
);
|
|
vi.stubGlobal("fetch", fetchMock);
|
|
const source: Source = {
|
|
...DIFY_SOURCE,
|
|
metadata: {
|
|
credentialId: "dify-credential-1",
|
|
credentials: { token: "must-not-cross" },
|
|
datasource: "notion_datasource",
|
|
pluginId: "langgenius/notion_datasource",
|
|
provider: "notion_datasource",
|
|
providerKind: "online-document",
|
|
},
|
|
};
|
|
const env = {
|
|
DIFY_INNER_API_KEY: "inner-key",
|
|
DIFY_INNER_API_URL: "http://api:5001",
|
|
PLUGIN_DAEMON_KEY: "must-not-be-used",
|
|
PLUGIN_DAEMON_URL: "http://plugin-daemon:5002",
|
|
};
|
|
const client = createApiDatasourceInvocationClient(env);
|
|
|
|
await expect(
|
|
collect(
|
|
client.dispatch({
|
|
operation: "get_online_document_pages",
|
|
source,
|
|
tenantId: "tenant-1",
|
|
}),
|
|
),
|
|
).rejects.toThrow("Inline datasource credentials are forbidden");
|
|
|
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
async function collect(input: AsyncIterable<unknown>): Promise<unknown[]> {
|
|
const values: unknown[] = [];
|
|
for await (const value of input) values.push(value);
|
|
return values;
|
|
}
|
|
|
|
function frame(value: unknown): Uint8Array {
|
|
const data = new TextEncoder().encode(JSON.stringify(value));
|
|
const result = new Uint8Array(14 + data.byteLength);
|
|
const view = new DataView(result.buffer);
|
|
view.setUint8(0, 0x0f);
|
|
view.setUint16(2, 0x0a, true);
|
|
view.setUint32(4, data.byteLength, true);
|
|
result.set(data, 14);
|
|
return result;
|
|
}
|
|
|
|
function responseFromBytes(bytes: Uint8Array): Response {
|
|
return new Response(
|
|
new ReadableStream<Uint8Array>({
|
|
start(controller) {
|
|
controller.enqueue(bytes);
|
|
controller.close();
|
|
},
|
|
}),
|
|
{ status: 200 },
|
|
);
|
|
}
|