@axiom-lattice/gateway 3.0.2 → 3.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +37 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +37 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -6
- package/.eslintrc.json +0 -22
- package/.turbo/turbo-build.log +0 -46
- package/AGENTS.md +0 -112
- package/CHANGELOG.md +0 -1190
- package/LOGGER_CONFIG.md +0 -135
- package/RESUME_STREAM_CONTENT_BASED.md +0 -326
- package/RESUME_STREAM_README.md +0 -388
- package/jest.config.js +0 -72
- package/public/sdk/README.md +0 -695
- package/public/sdk/dashboard-engine-skill.md +0 -1122
- package/public/sdk/dashboard-single-file-spec.md +0 -357
- package/public/sdk/data-query-sdk-skill.md +0 -307
- package/public/sdk/data-query-sdk.d.ts +0 -252
- package/public/sdk/data-query-sdk.js +0 -970
- package/public/sdk/occupancy-dashboard.html +0 -363
- package/public/sdk/test-dashboard.html +0 -690
- package/scripts/seed-lark-installation.ts +0 -54
- package/src/__tests__/__mocks__/e2b.ts +0 -1
- package/src/__tests__/a2a.test.ts +0 -428
- package/src/__tests__/channel-installations.test.ts +0 -199
- package/src/__tests__/data-query.test.ts +0 -77
- package/src/__tests__/sandbox-provider-registration.test.ts +0 -74
- package/src/__tests__/setup.ts +0 -5
- package/src/__tests__/workspace.test.ts +0 -400
- package/src/channels/__tests__/routes.test.ts +0 -50
- package/src/channels/lark/LarkChannelAdapter.ts +0 -217
- package/src/channels/lark/README.md +0 -187
- package/src/channels/lark/__tests__/controller.test.ts +0 -136
- package/src/channels/lark/__tests__/mapping-service.test.ts +0 -118
- package/src/channels/lark/__tests__/parser.test.ts +0 -72
- package/src/channels/lark/__tests__/sender.test.ts +0 -37
- package/src/channels/lark/__tests__/verification.test.ts +0 -157
- package/src/channels/lark/controller.ts +0 -48
- package/src/channels/lark/mapping-service.ts +0 -138
- package/src/channels/lark/parser.ts +0 -68
- package/src/channels/lark/routes.ts +0 -22
- package/src/channels/lark/sender.ts +0 -58
- package/src/channels/lark/types.ts +0 -20
- package/src/channels/lark/verification.ts +0 -32
- package/src/channels/registry.ts +0 -20
- package/src/channels/routes.ts +0 -30
- package/src/channels/wechat/WechatChannelAdapter.ts +0 -294
- package/src/channels/wechat/__tests__/context-store.test.ts +0 -148
- package/src/channels/wechat/context-store.ts +0 -54
- package/src/channels/wechat/controller.ts +0 -59
- package/src/channels/wechat/routes.ts +0 -7
- package/src/channels/wechat/types.ts +0 -62
- package/src/channels/wechat/wechat-client.ts +0 -162
- package/src/config/local-a2a-templates.ts +0 -59
- package/src/config.ts +0 -124
- package/src/controllers/__tests__/assistant-local-a2a.test.ts +0 -147
- package/src/controllers/__tests__/local-a2a-templates.test.ts +0 -112
- package/src/controllers/__tests__/mime.test.ts +0 -74
- package/src/controllers/__tests__/resources.test.ts +0 -122
- package/src/controllers/__tests__/run.test.ts +0 -115
- package/src/controllers/__tests__/tasks.test.ts +0 -215
- package/src/controllers/__tests__/workflow-tracking.test.ts +0 -113
- package/src/controllers/agent_task.ts +0 -72
- package/src/controllers/assistant.ts +0 -490
- package/src/controllers/auth.ts +0 -524
- package/src/controllers/channel-bindings.ts +0 -135
- package/src/controllers/channel-installations.ts +0 -409
- package/src/controllers/collections.ts +0 -176
- package/src/controllers/config.ts +0 -126
- package/src/controllers/connections.ts +0 -181
- package/src/controllers/data-query.ts +0 -236
- package/src/controllers/database-configs.ts +0 -440
- package/src/controllers/embeddings.ts +0 -22
- package/src/controllers/entries.ts +0 -167
- package/src/controllers/eval.ts +0 -513
- package/src/controllers/export-import.ts +0 -166
- package/src/controllers/health.ts +0 -36
- package/src/controllers/local-a2a-templates.ts +0 -18
- package/src/controllers/local-a2a.ts +0 -149
- package/src/controllers/mcp-configs.ts +0 -673
- package/src/controllers/memory.ts +0 -257
- package/src/controllers/menu-items.ts +0 -114
- package/src/controllers/metrics-configs.ts +0 -1061
- package/src/controllers/middleware-types.ts +0 -15
- package/src/controllers/models.ts +0 -199
- package/src/controllers/personal-assistant.ts +0 -191
- package/src/controllers/resources.ts +0 -477
- package/src/controllers/run.ts +0 -338
- package/src/controllers/sandbox.ts +0 -255
- package/src/controllers/schedules.ts +0 -336
- package/src/controllers/skills.ts +0 -256
- package/src/controllers/tasks.ts +0 -213
- package/src/controllers/tenants.ts +0 -121
- package/src/controllers/thread_status.ts +0 -66
- package/src/controllers/threads.ts +0 -260
- package/src/controllers/tools.ts +0 -139
- package/src/controllers/users.ts +0 -135
- package/src/controllers/workflow-tracking.ts +0 -693
- package/src/controllers/workspace.ts +0 -879
- package/src/export_registrations/a2a-api-key.registration.ts +0 -89
- package/src/export_registrations/agent.registration.ts +0 -80
- package/src/export_registrations/binding.registration.ts +0 -106
- package/src/export_registrations/channel-installation.registration.ts +0 -88
- package/src/export_registrations/collection.registration.ts +0 -85
- package/src/export_registrations/connection.registration.ts +0 -97
- package/src/export_registrations/database-config.registration.ts +0 -94
- package/src/export_registrations/eval.registration.ts +0 -334
- package/src/export_registrations/index.ts +0 -31
- package/src/export_registrations/mcp-config.registration.ts +0 -97
- package/src/export_registrations/menu.registration.ts +0 -91
- package/src/export_registrations/metrics-config.registration.ts +0 -94
- package/src/export_registrations/skill.registration.ts +0 -88
- package/src/export_registrations/task.registration.ts +0 -99
- package/src/index.ts +0 -497
- package/src/logger/Logger.ts +0 -186
- package/src/router/MessageContext.ts +0 -14
- package/src/router/MessageRouter.ts +0 -490
- package/src/router/middlewares/auditLogger.ts +0 -34
- package/src/router/middlewares/deduplication.ts +0 -38
- package/src/router/middlewares/index.ts +0 -3
- package/src/router/middlewares/rateLimit.ts +0 -46
- package/src/routes/__tests__/a2a-test-connection.test.ts +0 -177
- package/src/routes/a2a-bridge.ts +0 -266
- package/src/routes/a2a-test-connection.ts +0 -126
- package/src/routes/a2a.ts +0 -851
- package/src/routes/channel-bindings.ts +0 -11
- package/src/routes/channel-installations.ts +0 -33
- package/src/routes/index.ts +0 -640
- package/src/routes/menu-items.ts +0 -10
- package/src/routes/resource-routes.ts +0 -35
- package/src/routes/stt.ts +0 -102
- package/src/schemas/data-query.ts +0 -69
- package/src/schemas/index.ts +0 -852
- package/src/services/ExportImportService.ts +0 -296
- package/src/services/__tests__/ExportImportService.test.ts +0 -130
- package/src/services/__tests__/eval-runner.test.ts +0 -131
- package/src/services/__tests__/local-a2a.test.ts +0 -124
- package/src/services/agent_task_consumer.ts +0 -348
- package/src/services/collection_service.ts +0 -258
- package/src/services/eval-runner.ts +0 -283
- package/src/services/local-a2a.ts +0 -354
- package/src/services/queue_service.ts +0 -104
- package/src/services/sandbox_service.ts +0 -223
- package/src/services/skill_service.ts +0 -97
- package/src/services/supabase.ts +0 -60
- package/src/swagger.ts +0 -77
- package/src/types/index.ts +0 -118
- package/src/types/koa.d.ts +0 -7
- package/src/utils/__tests__/local-a2a-runtime.test.ts +0 -77
- package/src/utils/__tests__/path-containment.test.ts +0 -35
- package/src/utils/a2a-url.ts +0 -20
- package/src/utils/local-a2a-runtime.ts +0 -107
- package/src/utils/mime.ts +0 -44
- package/src/utils/path-containment.ts +0 -38
- package/tsconfig.json +0 -26
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, jest } from "@jest/globals";
|
|
2
|
-
import { FastifyRequest, FastifyReply } from "fastify";
|
|
3
|
-
import { executeDataQuery } from "../controllers/data-query";
|
|
4
|
-
|
|
5
|
-
// Mock @axiom-lattice/core
|
|
6
|
-
jest.mock("@axiom-lattice/core", () => ({
|
|
7
|
-
getStoreLattice: jest.fn(),
|
|
8
|
-
metricsServerManager: {
|
|
9
|
-
hasServer: jest.fn(),
|
|
10
|
-
registerServer: jest.fn(),
|
|
11
|
-
},
|
|
12
|
-
SemanticMetricsClient: jest.fn(),
|
|
13
|
-
}));
|
|
14
|
-
|
|
15
|
-
describe("Data Query Controller", () => {
|
|
16
|
-
let mockRequest: Partial<FastifyRequest>;
|
|
17
|
-
let mockReply: Partial<FastifyReply>;
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
mockRequest = {
|
|
21
|
-
headers: { "x-tenant-id": "test-tenant" },
|
|
22
|
-
body: {},
|
|
23
|
-
};
|
|
24
|
-
mockReply = {
|
|
25
|
-
code: jest.fn().mockReturnThis(),
|
|
26
|
-
};
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe("Request validation", () => {
|
|
30
|
-
it("should return 400 if neither serverKey nor datasourceId is provided", async () => {
|
|
31
|
-
mockRequest.body = {};
|
|
32
|
-
|
|
33
|
-
const result = await executeDataQuery(
|
|
34
|
-
mockRequest as FastifyRequest,
|
|
35
|
-
mockReply as FastifyReply
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
expect(mockReply.code).toHaveBeenCalledWith(400);
|
|
39
|
-
expect(result.success).toBe(false);
|
|
40
|
-
expect(result.message).toContain("serverKey or datasourceId");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("should return 400 if neither metrics nor customSql is provided", async () => {
|
|
44
|
-
mockRequest.body = {
|
|
45
|
-
serverKey: "test-server",
|
|
46
|
-
datasourceId: "1",
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const result = await executeDataQuery(
|
|
50
|
-
mockRequest as FastifyRequest,
|
|
51
|
-
mockReply as FastifyReply
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
expect(mockReply.code).toHaveBeenCalledWith(400);
|
|
55
|
-
expect(result.success).toBe(false);
|
|
56
|
-
expect(result.message).toContain("metrics (for semantic query) or customSql (for SQL query)");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("should return 400 if both metrics and customSql are provided", async () => {
|
|
60
|
-
mockRequest.body = {
|
|
61
|
-
serverKey: "test-server",
|
|
62
|
-
datasourceId: "1",
|
|
63
|
-
metrics: ["test_metric"],
|
|
64
|
-
customSql: "SELECT * FROM test",
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const result = await executeDataQuery(
|
|
68
|
-
mockRequest as FastifyRequest,
|
|
69
|
-
mockReply as FastifyReply
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
expect(mockReply.code).toHaveBeenCalledWith(400);
|
|
73
|
-
expect(result.success).toBe(false);
|
|
74
|
-
expect(result.message).toContain("Cannot provide both");
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
});
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
|
2
|
-
|
|
3
|
-
const mockCreateSandboxProvider = jest.fn();
|
|
4
|
-
const mockRegisterSandboxLattice = jest.fn();
|
|
5
|
-
const mockHasSandboxLattice = jest.fn();
|
|
6
|
-
const mockAppListen = jest.fn();
|
|
7
|
-
const mockAppClose = jest.fn();
|
|
8
|
-
const mockProcessExit = jest.spyOn(process, "exit").mockImplementation((() => undefined) as never);
|
|
9
|
-
|
|
10
|
-
jest.mock("../routes", () => ({
|
|
11
|
-
registerLatticeRoutes: jest.fn(),
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
jest.mock("@axiom-lattice/core", () => ({
|
|
15
|
-
createSandboxProvider: mockCreateSandboxProvider,
|
|
16
|
-
registerLoggerLattice: jest.fn(),
|
|
17
|
-
getLoggerLattice: jest.fn(() => ({
|
|
18
|
-
client: {
|
|
19
|
-
info: jest.fn(),
|
|
20
|
-
warn: jest.fn(),
|
|
21
|
-
error: jest.fn(),
|
|
22
|
-
},
|
|
23
|
-
updateContext: jest.fn(),
|
|
24
|
-
})),
|
|
25
|
-
loggerLatticeManager: {
|
|
26
|
-
hasLattice: jest.fn(() => false),
|
|
27
|
-
removeLattice: jest.fn(),
|
|
28
|
-
},
|
|
29
|
-
sandboxLatticeManager: {
|
|
30
|
-
hasLattice: mockHasSandboxLattice,
|
|
31
|
-
registerLattice: mockRegisterSandboxLattice,
|
|
32
|
-
},
|
|
33
|
-
sqlDatabaseManager: {
|
|
34
|
-
setConfigStore: jest.fn(),
|
|
35
|
-
},
|
|
36
|
-
getStoreLattice: jest.fn(() => {
|
|
37
|
-
throw new Error("database store unavailable in test");
|
|
38
|
-
}),
|
|
39
|
-
agentInstanceManager: {
|
|
40
|
-
restore: jest.fn().mockResolvedValue({ restored: 0, errors: 0 }),
|
|
41
|
-
},
|
|
42
|
-
}));
|
|
43
|
-
|
|
44
|
-
describe("gateway sandbox provider registration", () => {
|
|
45
|
-
beforeEach(() => {
|
|
46
|
-
jest.resetModules();
|
|
47
|
-
jest.clearAllMocks();
|
|
48
|
-
mockHasSandboxLattice.mockReturnValue(false);
|
|
49
|
-
mockCreateSandboxProvider.mockReturnValue({ provider: "mock" });
|
|
50
|
-
process.env.SANDBOX_PROVIDER_TYPE = "microsandbox-remote";
|
|
51
|
-
process.env.MICROSANDBOX_SERVICE_BASE_URL = "http://sandbox-host:4002";
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it("uses microsandbox-remote when configured", async () => {
|
|
55
|
-
const { LatticeGateway } = await import("../index");
|
|
56
|
-
|
|
57
|
-
jest.spyOn(LatticeGateway.app, "listen").mockImplementation(mockAppListen);
|
|
58
|
-
jest.spyOn(LatticeGateway.app, "close").mockImplementation(mockAppClose);
|
|
59
|
-
|
|
60
|
-
await LatticeGateway.startAsHttpEndpoint({ port: 0 });
|
|
61
|
-
|
|
62
|
-
expect(mockCreateSandboxProvider).toHaveBeenCalledWith(
|
|
63
|
-
expect.objectContaining({
|
|
64
|
-
type: "microsandbox-remote",
|
|
65
|
-
microsandboxServiceBaseURL: "http://sandbox-host:4002",
|
|
66
|
-
})
|
|
67
|
-
);
|
|
68
|
-
expect(mockRegisterSandboxLattice).toHaveBeenCalledWith("default", {
|
|
69
|
-
provider: "mock",
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
await LatticeGateway.app.close();
|
|
73
|
-
});
|
|
74
|
-
});
|
package/src/__tests__/setup.ts
DELETED
|
@@ -1,400 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WorkspaceController Tenant Isolation Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests to verify that file operations in WorkspaceController
|
|
5
|
-
* properly use tenant-specific paths.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect, beforeEach, jest } from "@jest/globals";
|
|
9
|
-
import type { FastifyRequest, FastifyReply } from "fastify";
|
|
10
|
-
|
|
11
|
-
// Mock store lattice
|
|
12
|
-
const mockGetWorkspaceById = jest.fn();
|
|
13
|
-
const mockGetAllWorkspaces = jest.fn();
|
|
14
|
-
const mockCreateWorkspace = jest.fn();
|
|
15
|
-
const mockUpdateWorkspace = jest.fn();
|
|
16
|
-
const mockDeleteWorkspace = jest.fn();
|
|
17
|
-
const mockGetProjectsByWorkspace = jest.fn();
|
|
18
|
-
const mockCreateProject = jest.fn();
|
|
19
|
-
const mockGetProjectById = jest.fn();
|
|
20
|
-
const mockUpdateProject = jest.fn();
|
|
21
|
-
const mockDeleteProject = jest.fn();
|
|
22
|
-
const mockGetVolumeBackendForPath = jest.fn().mockResolvedValue(null);
|
|
23
|
-
|
|
24
|
-
const mockStoreLattice = {
|
|
25
|
-
store: {
|
|
26
|
-
getAllWorkspaces: mockGetAllWorkspaces,
|
|
27
|
-
createWorkspace: mockCreateWorkspace,
|
|
28
|
-
getWorkspaceById: mockGetWorkspaceById,
|
|
29
|
-
updateWorkspace: mockUpdateWorkspace,
|
|
30
|
-
deleteWorkspace: mockDeleteWorkspace,
|
|
31
|
-
getProjectsByWorkspace: mockGetProjectsByWorkspace,
|
|
32
|
-
createProject: mockCreateProject,
|
|
33
|
-
getProjectById: mockGetProjectById,
|
|
34
|
-
updateProject: mockUpdateProject,
|
|
35
|
-
deleteProject: mockDeleteProject,
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const mockSandbox = {
|
|
40
|
-
file: {
|
|
41
|
-
uploadFile: jest.fn(),
|
|
42
|
-
downloadFile: jest.fn(),
|
|
43
|
-
readFile: jest.fn(),
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// Mock the dependencies
|
|
48
|
-
jest.mock("@axiom-lattice/core", () => ({
|
|
49
|
-
...jest.requireActual("@axiom-lattice/core"),
|
|
50
|
-
getStoreLattice: jest.fn().mockReturnValue(mockStoreLattice),
|
|
51
|
-
SandboxFilesystem: jest.fn().mockImplementation(() => ({
|
|
52
|
-
lsInfo: jest.fn(),
|
|
53
|
-
read: jest.fn(),
|
|
54
|
-
})),
|
|
55
|
-
FilesystemBackend: jest.fn().mockImplementation(() => ({
|
|
56
|
-
lsInfo: jest.fn().mockResolvedValue([]),
|
|
57
|
-
read: jest.fn(),
|
|
58
|
-
})),
|
|
59
|
-
getSandBoxManager: jest.fn(() => ({
|
|
60
|
-
getVolumeBackendForPath: mockGetVolumeBackendForPath,
|
|
61
|
-
getSandboxFromConfig: jest.fn().mockResolvedValue(mockSandbox),
|
|
62
|
-
})),
|
|
63
|
-
}));
|
|
64
|
-
|
|
65
|
-
// Import after mocks are set up
|
|
66
|
-
import { WorkspaceController } from "../controllers/workspace";
|
|
67
|
-
|
|
68
|
-
describe("WorkspaceController - Tenant Isolation", () => {
|
|
69
|
-
let controller: WorkspaceController;
|
|
70
|
-
const mockTenantId = "test-tenant-123";
|
|
71
|
-
const mockWorkspaceId = "workspace-456";
|
|
72
|
-
const mockProjectId = "project-789";
|
|
73
|
-
|
|
74
|
-
// Helper to create mock request with tenant
|
|
75
|
-
const createMockRequest = (overrides: any = {}) => ({
|
|
76
|
-
params: { workspaceId: mockWorkspaceId, projectId: mockProjectId },
|
|
77
|
-
query: {},
|
|
78
|
-
headers: { "x-tenant-id": mockTenantId },
|
|
79
|
-
...overrides,
|
|
80
|
-
}) as unknown as FastifyRequest;
|
|
81
|
-
|
|
82
|
-
beforeEach(async () => {
|
|
83
|
-
jest.clearAllMocks();
|
|
84
|
-
mockGetProjectById.mockReset();
|
|
85
|
-
mockGetProjectById.mockResolvedValue(undefined);
|
|
86
|
-
mockGetVolumeBackendForPath.mockResolvedValue(null);
|
|
87
|
-
const { FilesystemBackend } = await import("@axiom-lattice/core");
|
|
88
|
-
(FilesystemBackend as jest.Mock).mockImplementation(() => ({
|
|
89
|
-
lsInfo: jest.fn().mockResolvedValue([]),
|
|
90
|
-
read: jest.fn(),
|
|
91
|
-
}));
|
|
92
|
-
controller = new WorkspaceController();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe("getBackend path construction", () => {
|
|
96
|
-
it("should use tenant-specific path for filesystem storage", async () => {
|
|
97
|
-
const { FilesystemBackend } = await import("@axiom-lattice/core");
|
|
98
|
-
|
|
99
|
-
const mockWorkspace = {
|
|
100
|
-
id: mockWorkspaceId,
|
|
101
|
-
tenantId: mockTenantId,
|
|
102
|
-
storageType: "filesystem",
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
mockGetWorkspaceById.mockResolvedValue(mockWorkspace);
|
|
106
|
-
|
|
107
|
-
// Access private method for testing
|
|
108
|
-
const getBackend = (controller as any).getBackend.bind(controller);
|
|
109
|
-
await getBackend(mockTenantId, mockWorkspaceId, mockProjectId);
|
|
110
|
-
|
|
111
|
-
// Verify FilesystemBackend was called with tenant-specific path
|
|
112
|
-
expect(FilesystemBackend).toHaveBeenCalledWith({
|
|
113
|
-
rootDir: `/lattice_store/tenants/${mockTenantId}/workspaces/${mockWorkspaceId}/${mockProjectId}`,
|
|
114
|
-
virtualMode: true,
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("should use tenant-specific path for sandbox storage", async () => {
|
|
119
|
-
const { getSandBoxManager } = await import("@axiom-lattice/core");
|
|
120
|
-
|
|
121
|
-
const mockWorkspace = {
|
|
122
|
-
id: mockWorkspaceId,
|
|
123
|
-
tenantId: mockTenantId,
|
|
124
|
-
storageType: "sandbox",
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
mockGetWorkspaceById.mockResolvedValue(mockWorkspace);
|
|
128
|
-
|
|
129
|
-
const getBackend = (controller as any).getBackend.bind(controller);
|
|
130
|
-
await getBackend(mockTenantId, mockWorkspaceId, mockProjectId);
|
|
131
|
-
|
|
132
|
-
// Verify getSandBoxManager was called (uses default key, sandbox manager is not tenant-specific)
|
|
133
|
-
expect(getSandBoxManager).toHaveBeenCalled();
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("should pass tenantId to workspace store lookup", async () => {
|
|
137
|
-
const mockWorkspace = {
|
|
138
|
-
id: mockWorkspaceId,
|
|
139
|
-
tenantId: mockTenantId,
|
|
140
|
-
storageType: "filesystem",
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
mockGetWorkspaceById.mockResolvedValue(mockWorkspace);
|
|
144
|
-
|
|
145
|
-
const getBackend = (controller as any).getBackend.bind(controller);
|
|
146
|
-
await getBackend(mockTenantId, mockWorkspaceId, mockProjectId);
|
|
147
|
-
|
|
148
|
-
// Verify workspace lookup used correct tenantId
|
|
149
|
-
expect(mockGetWorkspaceById).toHaveBeenCalledWith(mockTenantId, mockWorkspaceId);
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
describe("listPath tenant isolation", () => {
|
|
154
|
-
it("should extract tenantId from request headers", async () => {
|
|
155
|
-
const { FilesystemBackend } = await import("@axiom-lattice/core");
|
|
156
|
-
|
|
157
|
-
const mockWorkspace = {
|
|
158
|
-
id: mockWorkspaceId,
|
|
159
|
-
tenantId: mockTenantId,
|
|
160
|
-
storageType: "filesystem",
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
mockGetWorkspaceById.mockResolvedValue(mockWorkspace);
|
|
164
|
-
|
|
165
|
-
const request = createMockRequest();
|
|
166
|
-
await controller.listPath(request);
|
|
167
|
-
|
|
168
|
-
// Verify tenant-specific path was used
|
|
169
|
-
expect(FilesystemBackend).toHaveBeenCalledWith({
|
|
170
|
-
rootDir: `/lattice_store/tenants/${mockTenantId}/workspaces/${mockWorkspaceId}/${mockProjectId}`,
|
|
171
|
-
virtualMode: true,
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it("should use different paths for different tenants", async () => {
|
|
176
|
-
const { FilesystemBackend } = await import("@axiom-lattice/core");
|
|
177
|
-
|
|
178
|
-
const tenantA = "tenant-a";
|
|
179
|
-
const tenantB = "tenant-b";
|
|
180
|
-
|
|
181
|
-
const mockWorkspaceA = {
|
|
182
|
-
id: mockWorkspaceId,
|
|
183
|
-
tenantId: tenantA,
|
|
184
|
-
storageType: "filesystem",
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const mockWorkspaceB = {
|
|
188
|
-
id: mockWorkspaceId,
|
|
189
|
-
tenantId: tenantB,
|
|
190
|
-
storageType: "filesystem",
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
// First call with tenant A
|
|
194
|
-
mockGetWorkspaceById.mockResolvedValue(mockWorkspaceA);
|
|
195
|
-
await controller.listPath(createMockRequest({ headers: { "x-tenant-id": tenantA } }));
|
|
196
|
-
|
|
197
|
-
expect(FilesystemBackend).toHaveBeenCalledWith({
|
|
198
|
-
rootDir: `/lattice_store/tenants/${tenantA}/workspaces/${mockWorkspaceId}/${mockProjectId}`,
|
|
199
|
-
virtualMode: true,
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// Second call with tenant B
|
|
203
|
-
mockGetWorkspaceById.mockResolvedValue(mockWorkspaceB);
|
|
204
|
-
await controller.listPath(createMockRequest({ headers: { "x-tenant-id": tenantB } }));
|
|
205
|
-
|
|
206
|
-
expect(FilesystemBackend).toHaveBeenCalledWith({
|
|
207
|
-
rootDir: `/lattice_store/tenants/${tenantB}/workspaces/${mockWorkspaceId}/${mockProjectId}`,
|
|
208
|
-
virtualMode: true,
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
describe("readFile tenant isolation", () => {
|
|
214
|
-
it("should use tenant-specific backend for reading files", async () => {
|
|
215
|
-
const { FilesystemBackend } = await import("@axiom-lattice/core");
|
|
216
|
-
|
|
217
|
-
const mockWorkspace = {
|
|
218
|
-
id: mockWorkspaceId,
|
|
219
|
-
tenantId: mockTenantId,
|
|
220
|
-
storageType: "filesystem",
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
mockGetWorkspaceById.mockResolvedValue(mockWorkspace);
|
|
224
|
-
|
|
225
|
-
const request = createMockRequest({
|
|
226
|
-
query: { path: "/test.txt", offset: 0, limit: 100 },
|
|
227
|
-
});
|
|
228
|
-
await controller.readFile(request);
|
|
229
|
-
|
|
230
|
-
// Verify tenant-specific path was used
|
|
231
|
-
expect(FilesystemBackend).toHaveBeenCalledWith({
|
|
232
|
-
rootDir: `/lattice_store/tenants/${mockTenantId}/workspaces/${mockWorkspaceId}/${mockProjectId}`,
|
|
233
|
-
virtualMode: true,
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
describe("sandbox storage path passthrough", () => {
|
|
239
|
-
const mockWorkspaceSandbox = {
|
|
240
|
-
id: mockWorkspaceId,
|
|
241
|
-
tenantId: mockTenantId,
|
|
242
|
-
storageType: "sandbox",
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
it("uploadFile should pass path directly to sandbox without baseDir prefix", async () => {
|
|
246
|
-
mockGetWorkspaceById.mockResolvedValue(mockWorkspaceSandbox);
|
|
247
|
-
|
|
248
|
-
// Mock Fastify multipart file
|
|
249
|
-
const mockFileData = {
|
|
250
|
-
toBuffer: jest.fn().mockResolvedValue(Buffer.from("test content")),
|
|
251
|
-
filename: "test.txt",
|
|
252
|
-
fields: {
|
|
253
|
-
path: { value: "~/project" },
|
|
254
|
-
},
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
const request = createMockRequest({
|
|
258
|
-
file: jest.fn().mockResolvedValue(mockFileData),
|
|
259
|
-
});
|
|
260
|
-
const reply = {
|
|
261
|
-
status: jest.fn().mockReturnThis(),
|
|
262
|
-
send: jest.fn().mockReturnThis(),
|
|
263
|
-
} as unknown as FastifyReply;
|
|
264
|
-
|
|
265
|
-
await controller.uploadFile(request, reply);
|
|
266
|
-
|
|
267
|
-
// Should use frontend-provided path directly, not /tenants/... baseDir
|
|
268
|
-
expect(mockSandbox.file.uploadFile).toHaveBeenCalledWith({
|
|
269
|
-
file: "~/project/test.txt",
|
|
270
|
-
data: expect.any(Buffer),
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it("uploadFile should allow ~ in path validation", async () => {
|
|
275
|
-
mockGetWorkspaceById.mockResolvedValue(mockWorkspaceSandbox);
|
|
276
|
-
|
|
277
|
-
const mockFileData = {
|
|
278
|
-
toBuffer: jest.fn().mockResolvedValue(Buffer.from("test content")),
|
|
279
|
-
filename: "test.txt",
|
|
280
|
-
fields: {
|
|
281
|
-
path: { value: "/project/docs" },
|
|
282
|
-
},
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
const request = createMockRequest({
|
|
286
|
-
file: jest.fn().mockResolvedValue(mockFileData),
|
|
287
|
-
});
|
|
288
|
-
const reply = {
|
|
289
|
-
status: jest.fn().mockReturnThis(),
|
|
290
|
-
send: jest.fn().mockReturnThis(),
|
|
291
|
-
} as unknown as FastifyReply;
|
|
292
|
-
|
|
293
|
-
await controller.uploadFile(request, reply);
|
|
294
|
-
|
|
295
|
-
// Should succeed with ~ in path
|
|
296
|
-
expect(mockSandbox.file.uploadFile).toHaveBeenCalledWith({
|
|
297
|
-
file: "/project/docs/test.txt",
|
|
298
|
-
data: expect.any(Buffer),
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
it("downloadFile should pass path directly to sandbox without baseDir prefix", async () => {
|
|
303
|
-
mockGetWorkspaceById.mockResolvedValue(mockWorkspaceSandbox);
|
|
304
|
-
mockSandbox.file.downloadFile.mockResolvedValue(Buffer.from("content"));
|
|
305
|
-
|
|
306
|
-
const request = createMockRequest({
|
|
307
|
-
query: { path: "/agent/AGENTS.md" },
|
|
308
|
-
});
|
|
309
|
-
const reply = {
|
|
310
|
-
status: jest.fn().mockReturnThis(),
|
|
311
|
-
type: jest.fn().mockReturnThis(),
|
|
312
|
-
header: jest.fn().mockReturnThis(),
|
|
313
|
-
send: jest.fn().mockReturnThis(),
|
|
314
|
-
} as unknown as FastifyReply;
|
|
315
|
-
|
|
316
|
-
await controller.downloadFile(request, reply);
|
|
317
|
-
|
|
318
|
-
expect(mockSandbox.file.downloadFile).toHaveBeenCalledWith({
|
|
319
|
-
file: "/agent/AGENTS.md",
|
|
320
|
-
});
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
it("viewFile should pass path directly to sandbox without baseDir prefix", async () => {
|
|
324
|
-
mockGetWorkspaceById.mockResolvedValue(mockWorkspaceSandbox);
|
|
325
|
-
mockSandbox.file.downloadFile.mockResolvedValue(Buffer.from("content"));
|
|
326
|
-
|
|
327
|
-
const request = createMockRequest({
|
|
328
|
-
query: { path: "/project/index.html" },
|
|
329
|
-
});
|
|
330
|
-
const reply = {
|
|
331
|
-
status: jest.fn().mockReturnThis(),
|
|
332
|
-
type: jest.fn().mockReturnThis(),
|
|
333
|
-
header: jest.fn().mockReturnThis(),
|
|
334
|
-
send: jest.fn().mockReturnThis(),
|
|
335
|
-
} as unknown as FastifyReply;
|
|
336
|
-
|
|
337
|
-
await controller.viewFile(request, reply);
|
|
338
|
-
|
|
339
|
-
expect(mockSandbox.file.downloadFile).toHaveBeenCalledWith({
|
|
340
|
-
file: "/project/index.html",
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
describe("filesystem storage ~/ path passthrough", () => {
|
|
346
|
-
it("readFile should pass ~/ paths to the filesystem backend unchanged", async () => {
|
|
347
|
-
const { FilesystemBackend } = await import("@axiom-lattice/core");
|
|
348
|
-
|
|
349
|
-
const backendRead = jest.fn().mockResolvedValue("1: hello");
|
|
350
|
-
(FilesystemBackend as jest.Mock).mockImplementation(() => ({
|
|
351
|
-
lsInfo: jest.fn(),
|
|
352
|
-
read: backendRead,
|
|
353
|
-
}));
|
|
354
|
-
|
|
355
|
-
mockGetWorkspaceById.mockResolvedValue({
|
|
356
|
-
id: mockWorkspaceId,
|
|
357
|
-
tenantId: mockTenantId,
|
|
358
|
-
storageType: "filesystem",
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
const request = createMockRequest({
|
|
362
|
-
query: { path: "/project/test.txt", offset: 0, limit: 50 },
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
await controller.readFile(request);
|
|
366
|
-
|
|
367
|
-
expect(backendRead).toHaveBeenCalledWith("/project/test.txt", 0, 50);
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
describe("getTenantId helper", () => {
|
|
372
|
-
it("should extract tenantId from user context when available", () => {
|
|
373
|
-
const request = {
|
|
374
|
-
user: { tenantId: "user-context-tenant" },
|
|
375
|
-
headers: {},
|
|
376
|
-
} as unknown as FastifyRequest;
|
|
377
|
-
|
|
378
|
-
const tenantId = (controller as any).getTenantId(request);
|
|
379
|
-
expect(tenantId).toBe("user-context-tenant");
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
it("should fallback to x-tenant-id header when no user context", () => {
|
|
383
|
-
const request = {
|
|
384
|
-
headers: { "x-tenant-id": "header-tenant" },
|
|
385
|
-
} as unknown as FastifyRequest;
|
|
386
|
-
|
|
387
|
-
const tenantId = (controller as any).getTenantId(request);
|
|
388
|
-
expect(tenantId).toBe("header-tenant");
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
it("should use default tenant when no context or header", () => {
|
|
392
|
-
const request = {
|
|
393
|
-
headers: {},
|
|
394
|
-
} as unknown as FastifyRequest;
|
|
395
|
-
|
|
396
|
-
const tenantId = (controller as any).getTenantId(request);
|
|
397
|
-
expect(tenantId).toBe("default");
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
});
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import fastify from "fastify";
|
|
2
|
-
import { registerChannelRoutes } from "../routes";
|
|
3
|
-
|
|
4
|
-
describe("registerChannelRoutes", () => {
|
|
5
|
-
it("skips registration when no dependencies are provided", async () => {
|
|
6
|
-
const app = fastify();
|
|
7
|
-
registerChannelRoutes(app);
|
|
8
|
-
|
|
9
|
-
const response = await app.inject({
|
|
10
|
-
method: "POST",
|
|
11
|
-
url: "/api/channels/lark/installations/install-1/events",
|
|
12
|
-
payload: {},
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
expect(response.statusCode).toBe(404);
|
|
16
|
-
|
|
17
|
-
await app.close();
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("registers the lark route when dependencies are provided", async () => {
|
|
21
|
-
const app = fastify();
|
|
22
|
-
registerChannelRoutes(app, {
|
|
23
|
-
router: {
|
|
24
|
-
dispatch: jest.fn().mockResolvedValue({ success: true }),
|
|
25
|
-
} as any,
|
|
26
|
-
installationStore: {
|
|
27
|
-
getInstallationById: jest.fn().mockResolvedValue({
|
|
28
|
-
id: "install-1",
|
|
29
|
-
tenantId: "tenant-a",
|
|
30
|
-
channel: "lark",
|
|
31
|
-
config: { appId: "cli_app_1", appSecret: "secret" },
|
|
32
|
-
enabled: true,
|
|
33
|
-
rejectWhenNoBinding: false,
|
|
34
|
-
createdAt: new Date(),
|
|
35
|
-
updatedAt: new Date(),
|
|
36
|
-
}),
|
|
37
|
-
} as any,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const response = await app.inject({
|
|
41
|
-
method: "POST",
|
|
42
|
-
url: "/api/channels/lark/installations/install-1/events",
|
|
43
|
-
payload: {},
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
expect(response.statusCode).toBe(200);
|
|
47
|
-
|
|
48
|
-
await app.close();
|
|
49
|
-
});
|
|
50
|
-
});
|