@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.
Files changed (155) hide show
  1. package/dist/index.js +37 -9
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +37 -9
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +9 -6
  6. package/.eslintrc.json +0 -22
  7. package/.turbo/turbo-build.log +0 -46
  8. package/AGENTS.md +0 -112
  9. package/CHANGELOG.md +0 -1190
  10. package/LOGGER_CONFIG.md +0 -135
  11. package/RESUME_STREAM_CONTENT_BASED.md +0 -326
  12. package/RESUME_STREAM_README.md +0 -388
  13. package/jest.config.js +0 -72
  14. package/public/sdk/README.md +0 -695
  15. package/public/sdk/dashboard-engine-skill.md +0 -1122
  16. package/public/sdk/dashboard-single-file-spec.md +0 -357
  17. package/public/sdk/data-query-sdk-skill.md +0 -307
  18. package/public/sdk/data-query-sdk.d.ts +0 -252
  19. package/public/sdk/data-query-sdk.js +0 -970
  20. package/public/sdk/occupancy-dashboard.html +0 -363
  21. package/public/sdk/test-dashboard.html +0 -690
  22. package/scripts/seed-lark-installation.ts +0 -54
  23. package/src/__tests__/__mocks__/e2b.ts +0 -1
  24. package/src/__tests__/a2a.test.ts +0 -428
  25. package/src/__tests__/channel-installations.test.ts +0 -199
  26. package/src/__tests__/data-query.test.ts +0 -77
  27. package/src/__tests__/sandbox-provider-registration.test.ts +0 -74
  28. package/src/__tests__/setup.ts +0 -5
  29. package/src/__tests__/workspace.test.ts +0 -400
  30. package/src/channels/__tests__/routes.test.ts +0 -50
  31. package/src/channels/lark/LarkChannelAdapter.ts +0 -217
  32. package/src/channels/lark/README.md +0 -187
  33. package/src/channels/lark/__tests__/controller.test.ts +0 -136
  34. package/src/channels/lark/__tests__/mapping-service.test.ts +0 -118
  35. package/src/channels/lark/__tests__/parser.test.ts +0 -72
  36. package/src/channels/lark/__tests__/sender.test.ts +0 -37
  37. package/src/channels/lark/__tests__/verification.test.ts +0 -157
  38. package/src/channels/lark/controller.ts +0 -48
  39. package/src/channels/lark/mapping-service.ts +0 -138
  40. package/src/channels/lark/parser.ts +0 -68
  41. package/src/channels/lark/routes.ts +0 -22
  42. package/src/channels/lark/sender.ts +0 -58
  43. package/src/channels/lark/types.ts +0 -20
  44. package/src/channels/lark/verification.ts +0 -32
  45. package/src/channels/registry.ts +0 -20
  46. package/src/channels/routes.ts +0 -30
  47. package/src/channels/wechat/WechatChannelAdapter.ts +0 -294
  48. package/src/channels/wechat/__tests__/context-store.test.ts +0 -148
  49. package/src/channels/wechat/context-store.ts +0 -54
  50. package/src/channels/wechat/controller.ts +0 -59
  51. package/src/channels/wechat/routes.ts +0 -7
  52. package/src/channels/wechat/types.ts +0 -62
  53. package/src/channels/wechat/wechat-client.ts +0 -162
  54. package/src/config/local-a2a-templates.ts +0 -59
  55. package/src/config.ts +0 -124
  56. package/src/controllers/__tests__/assistant-local-a2a.test.ts +0 -147
  57. package/src/controllers/__tests__/local-a2a-templates.test.ts +0 -112
  58. package/src/controllers/__tests__/mime.test.ts +0 -74
  59. package/src/controllers/__tests__/resources.test.ts +0 -122
  60. package/src/controllers/__tests__/run.test.ts +0 -115
  61. package/src/controllers/__tests__/tasks.test.ts +0 -215
  62. package/src/controllers/__tests__/workflow-tracking.test.ts +0 -113
  63. package/src/controllers/agent_task.ts +0 -72
  64. package/src/controllers/assistant.ts +0 -490
  65. package/src/controllers/auth.ts +0 -524
  66. package/src/controllers/channel-bindings.ts +0 -135
  67. package/src/controllers/channel-installations.ts +0 -409
  68. package/src/controllers/collections.ts +0 -176
  69. package/src/controllers/config.ts +0 -126
  70. package/src/controllers/connections.ts +0 -181
  71. package/src/controllers/data-query.ts +0 -236
  72. package/src/controllers/database-configs.ts +0 -440
  73. package/src/controllers/embeddings.ts +0 -22
  74. package/src/controllers/entries.ts +0 -167
  75. package/src/controllers/eval.ts +0 -513
  76. package/src/controllers/export-import.ts +0 -166
  77. package/src/controllers/health.ts +0 -36
  78. package/src/controllers/local-a2a-templates.ts +0 -18
  79. package/src/controllers/local-a2a.ts +0 -149
  80. package/src/controllers/mcp-configs.ts +0 -673
  81. package/src/controllers/memory.ts +0 -257
  82. package/src/controllers/menu-items.ts +0 -114
  83. package/src/controllers/metrics-configs.ts +0 -1061
  84. package/src/controllers/middleware-types.ts +0 -15
  85. package/src/controllers/models.ts +0 -199
  86. package/src/controllers/personal-assistant.ts +0 -191
  87. package/src/controllers/resources.ts +0 -477
  88. package/src/controllers/run.ts +0 -338
  89. package/src/controllers/sandbox.ts +0 -255
  90. package/src/controllers/schedules.ts +0 -336
  91. package/src/controllers/skills.ts +0 -256
  92. package/src/controllers/tasks.ts +0 -213
  93. package/src/controllers/tenants.ts +0 -121
  94. package/src/controllers/thread_status.ts +0 -66
  95. package/src/controllers/threads.ts +0 -260
  96. package/src/controllers/tools.ts +0 -139
  97. package/src/controllers/users.ts +0 -135
  98. package/src/controllers/workflow-tracking.ts +0 -693
  99. package/src/controllers/workspace.ts +0 -879
  100. package/src/export_registrations/a2a-api-key.registration.ts +0 -89
  101. package/src/export_registrations/agent.registration.ts +0 -80
  102. package/src/export_registrations/binding.registration.ts +0 -106
  103. package/src/export_registrations/channel-installation.registration.ts +0 -88
  104. package/src/export_registrations/collection.registration.ts +0 -85
  105. package/src/export_registrations/connection.registration.ts +0 -97
  106. package/src/export_registrations/database-config.registration.ts +0 -94
  107. package/src/export_registrations/eval.registration.ts +0 -334
  108. package/src/export_registrations/index.ts +0 -31
  109. package/src/export_registrations/mcp-config.registration.ts +0 -97
  110. package/src/export_registrations/menu.registration.ts +0 -91
  111. package/src/export_registrations/metrics-config.registration.ts +0 -94
  112. package/src/export_registrations/skill.registration.ts +0 -88
  113. package/src/export_registrations/task.registration.ts +0 -99
  114. package/src/index.ts +0 -497
  115. package/src/logger/Logger.ts +0 -186
  116. package/src/router/MessageContext.ts +0 -14
  117. package/src/router/MessageRouter.ts +0 -490
  118. package/src/router/middlewares/auditLogger.ts +0 -34
  119. package/src/router/middlewares/deduplication.ts +0 -38
  120. package/src/router/middlewares/index.ts +0 -3
  121. package/src/router/middlewares/rateLimit.ts +0 -46
  122. package/src/routes/__tests__/a2a-test-connection.test.ts +0 -177
  123. package/src/routes/a2a-bridge.ts +0 -266
  124. package/src/routes/a2a-test-connection.ts +0 -126
  125. package/src/routes/a2a.ts +0 -851
  126. package/src/routes/channel-bindings.ts +0 -11
  127. package/src/routes/channel-installations.ts +0 -33
  128. package/src/routes/index.ts +0 -640
  129. package/src/routes/menu-items.ts +0 -10
  130. package/src/routes/resource-routes.ts +0 -35
  131. package/src/routes/stt.ts +0 -102
  132. package/src/schemas/data-query.ts +0 -69
  133. package/src/schemas/index.ts +0 -852
  134. package/src/services/ExportImportService.ts +0 -296
  135. package/src/services/__tests__/ExportImportService.test.ts +0 -130
  136. package/src/services/__tests__/eval-runner.test.ts +0 -131
  137. package/src/services/__tests__/local-a2a.test.ts +0 -124
  138. package/src/services/agent_task_consumer.ts +0 -348
  139. package/src/services/collection_service.ts +0 -258
  140. package/src/services/eval-runner.ts +0 -283
  141. package/src/services/local-a2a.ts +0 -354
  142. package/src/services/queue_service.ts +0 -104
  143. package/src/services/sandbox_service.ts +0 -223
  144. package/src/services/skill_service.ts +0 -97
  145. package/src/services/supabase.ts +0 -60
  146. package/src/swagger.ts +0 -77
  147. package/src/types/index.ts +0 -118
  148. package/src/types/koa.d.ts +0 -7
  149. package/src/utils/__tests__/local-a2a-runtime.test.ts +0 -77
  150. package/src/utils/__tests__/path-containment.test.ts +0 -35
  151. package/src/utils/a2a-url.ts +0 -20
  152. package/src/utils/local-a2a-runtime.ts +0 -107
  153. package/src/utils/mime.ts +0 -44
  154. package/src/utils/path-containment.ts +0 -38
  155. 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
- });
@@ -1,5 +0,0 @@
1
- // 全局测试设置文件
2
- // 这个文件会在所有测试运行前执行
3
-
4
- // 设置Node.js环境变量
5
- process.env.NODE_ENV = "test";
@@ -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
- });