Skip to content

Commit 6e8811a

Browse files
Merge pull request #29 from Patrick-Ehimen/feat/dataset-management
feat: implement comprehensive dataset management functionality
2 parents 7d1475d + f484e2a commit 6e8811a

18 files changed

+2332
-41
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
/**
2+
* Dataset Tools Tests
3+
*/
4+
5+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
6+
import { Logger } from "@lighthouse-tooling/shared";
7+
import { MockLighthouseService } from "../services/MockLighthouseService.js";
8+
import {
9+
LighthouseCreateDatasetTool,
10+
LighthouseListDatasetsTool,
11+
LighthouseGetDatasetTool,
12+
LighthouseUpdateDatasetTool,
13+
} from "../tools/index.js";
14+
15+
describe("Dataset Tools", () => {
16+
let mockService: MockLighthouseService;
17+
let logger: Logger;
18+
19+
beforeEach(() => {
20+
mockService = new MockLighthouseService();
21+
logger = Logger.getInstance({ level: "error", component: "test" });
22+
});
23+
24+
afterEach(() => {
25+
mockService.clear();
26+
});
27+
28+
describe("LighthouseCreateDatasetTool", () => {
29+
let tool: LighthouseCreateDatasetTool;
30+
31+
beforeEach(() => {
32+
tool = new LighthouseCreateDatasetTool(mockService, logger);
33+
});
34+
35+
it("should have correct tool definition", () => {
36+
const definition = LighthouseCreateDatasetTool.getDefinition();
37+
expect(definition.name).toBe("lighthouse_create_dataset");
38+
expect(definition.description).toContain("Create a new dataset");
39+
expect(definition.requiresAuth).toBe(true);
40+
});
41+
42+
it("should validate required parameters", async () => {
43+
const result = await tool.execute({});
44+
expect(result.success).toBe(false);
45+
expect(result.error).toContain("name is required");
46+
});
47+
48+
it("should validate filePaths parameter", async () => {
49+
const result = await tool.execute({
50+
name: "Test Dataset",
51+
filePaths: [],
52+
});
53+
expect(result.success).toBe(false);
54+
expect(result.error).toContain("filePaths is required and must be a non-empty array");
55+
});
56+
57+
it("should create dataset successfully with valid parameters", async () => {
58+
// Create test files first
59+
const testFiles = ["/tmp/test1.txt", "/tmp/test2.txt"];
60+
61+
// Mock FileUtils to avoid actual file operations
62+
const { FileUtils } = await import("@lighthouse-tooling/shared");
63+
vi.spyOn(FileUtils, "fileExists").mockResolvedValue(true);
64+
vi.spyOn(FileUtils, "getFileInfo").mockResolvedValue({
65+
path: "/tmp/test.txt",
66+
name: "test.txt",
67+
extension: ".txt",
68+
size: 1024,
69+
lastModified: new Date(),
70+
hash: "mock-hash",
71+
});
72+
73+
const result = await tool.execute({
74+
name: "Test Dataset",
75+
description: "A test dataset",
76+
filePaths: testFiles,
77+
encrypt: false,
78+
tags: ["test", "dataset"],
79+
});
80+
81+
expect(result.success).toBe(true);
82+
expect(result.data).toBeDefined();
83+
expect(result.data.dataset.name).toBe("Test Dataset");
84+
expect(result.data.dataset.fileCount).toBe(2);
85+
});
86+
87+
it("should handle file validation errors", async () => {
88+
// Mock FileUtils to simulate file not found
89+
const { FileUtils } = await import("@lighthouse-tooling/shared");
90+
vi.spyOn(FileUtils, "fileExists").mockResolvedValue(false);
91+
92+
const result = await tool.execute({
93+
name: "Test Dataset",
94+
filePaths: ["/nonexistent/file.txt"],
95+
});
96+
97+
expect(result.success).toBe(false);
98+
expect(result.error).toContain("File not found");
99+
});
100+
});
101+
102+
describe("LighthouseListDatasetsTool", () => {
103+
let tool: LighthouseListDatasetsTool;
104+
105+
beforeEach(() => {
106+
tool = new LighthouseListDatasetsTool(mockService, logger);
107+
});
108+
109+
it("should have correct tool definition", () => {
110+
const definition = LighthouseListDatasetsTool.getDefinition();
111+
expect(definition.name).toBe("lighthouse_list_datasets");
112+
expect(definition.description).toContain("List all datasets");
113+
expect(definition.requiresAuth).toBe(true);
114+
});
115+
116+
it("should list datasets with default parameters", async () => {
117+
const result = await tool.execute({});
118+
expect(result.success).toBe(true);
119+
expect(result.data).toBeDefined();
120+
expect(result.data.datasets).toBeInstanceOf(Array);
121+
expect(result.data.pagination).toBeDefined();
122+
});
123+
124+
it("should validate limit parameter", async () => {
125+
const result = await tool.execute({
126+
limit: 150, // exceeds max of 100
127+
});
128+
expect(result.success).toBe(false);
129+
expect(result.error).toContain("limit must be between 1 and 100");
130+
});
131+
132+
it("should validate offset parameter", async () => {
133+
const result = await tool.execute({
134+
offset: -1,
135+
});
136+
expect(result.success).toBe(false);
137+
expect(result.error).toContain("offset must be 0 or greater");
138+
});
139+
});
140+
141+
describe("LighthouseGetDatasetTool", () => {
142+
let tool: LighthouseGetDatasetTool;
143+
144+
beforeEach(() => {
145+
tool = new LighthouseGetDatasetTool(mockService, logger);
146+
});
147+
148+
it("should have correct tool definition", () => {
149+
const definition = LighthouseGetDatasetTool.getDefinition();
150+
expect(definition.name).toBe("lighthouse_get_dataset");
151+
expect(definition.description).toContain("Retrieve detailed information");
152+
expect(definition.requiresAuth).toBe(true);
153+
});
154+
155+
it("should validate datasetId parameter", async () => {
156+
const result = await tool.execute({});
157+
expect(result.success).toBe(false);
158+
expect(result.error).toContain("datasetId is required");
159+
});
160+
161+
it("should retrieve dataset successfully", async () => {
162+
// First create a dataset
163+
await mockService.createDataset({
164+
name: "Test Dataset",
165+
description: "Test description",
166+
filePaths: [],
167+
encrypt: false,
168+
});
169+
170+
// Get the created dataset ID (it will be the first one)
171+
const datasets = await mockService.listDatasets();
172+
const datasetId = datasets.datasets[0].id;
173+
174+
const result = await tool.execute({
175+
datasetId,
176+
});
177+
expect(result.success).toBe(true);
178+
expect(result.data).toBeDefined();
179+
expect(result.data.dataset.id).toBe(datasetId);
180+
});
181+
182+
it("should handle non-existent dataset", async () => {
183+
const result = await tool.execute({
184+
datasetId: "nonexistent",
185+
});
186+
expect(result.success).toBe(false);
187+
expect(result.error).toContain("Dataset not found");
188+
});
189+
});
190+
191+
describe("LighthouseUpdateDatasetTool", () => {
192+
let tool: LighthouseUpdateDatasetTool;
193+
194+
beforeEach(() => {
195+
tool = new LighthouseUpdateDatasetTool(mockService, logger);
196+
});
197+
198+
it("should have correct tool definition", () => {
199+
const definition = LighthouseUpdateDatasetTool.getDefinition();
200+
expect(definition.name).toBe("lighthouse_update_dataset");
201+
expect(definition.description).toContain("Update an existing dataset");
202+
expect(definition.requiresAuth).toBe(true);
203+
});
204+
205+
it("should validate datasetId parameter", async () => {
206+
const result = await tool.execute({});
207+
expect(result.success).toBe(false);
208+
expect(result.error).toContain("datasetId is required");
209+
});
210+
211+
it("should require at least one update operation", async () => {
212+
const result = await tool.execute({
213+
datasetId: "dataset_1",
214+
});
215+
expect(result.success).toBe(false);
216+
expect(result.error).toContain("At least one update operation must be specified");
217+
});
218+
219+
it("should update dataset with new description", async () => {
220+
// First create a dataset
221+
await mockService.createDataset({
222+
name: "Test Dataset",
223+
description: "Original description",
224+
filePaths: [],
225+
encrypt: false,
226+
});
227+
228+
// Get the created dataset ID
229+
const datasets = await mockService.listDatasets();
230+
const datasetId = datasets.datasets[0].id;
231+
232+
const result = await tool.execute({
233+
datasetId,
234+
description: "Updated description",
235+
});
236+
expect(result.success).toBe(true);
237+
expect(result.data).toBeDefined();
238+
expect(result.data.dataset.description).toBe("Updated description");
239+
});
240+
241+
it("should validate addFiles parameter", async () => {
242+
// Mock FileUtils to simulate file not found
243+
const { FileUtils } = await import("@lighthouse-tooling/shared");
244+
vi.spyOn(FileUtils, "fileExists").mockResolvedValue(false);
245+
246+
const result = await tool.execute({
247+
datasetId: "dataset_1",
248+
addFiles: ["/nonexistent/file.txt"],
249+
});
250+
expect(result.success).toBe(false);
251+
expect(result.error).toContain("File not found");
252+
});
253+
});
254+
});

apps/mcp-server/src/server.ts

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import { MockDatasetService } from "./services/MockDatasetService.js";
1919
import {
2020
LighthouseUploadFileTool,
2121
LighthouseFetchFileTool,
22+
LighthouseCreateDatasetTool,
23+
LighthouseListDatasetsTool,
24+
LighthouseGetDatasetTool,
25+
LighthouseUpdateDatasetTool,
2226
LighthouseGenerateKeyTool,
2327
LighthouseSetupAccessControlTool,
2428
} from "./tools/index.js";
@@ -125,56 +129,59 @@ export class LighthouseMCPServer {
125129
// Create tool instances with service dependencies
126130
const uploadFileTool = new LighthouseUploadFileTool(this.lighthouseService, this.logger);
127131
const fetchFileTool = new LighthouseFetchFileTool(this.lighthouseService, this.logger);
132+
const createDatasetTool = new LighthouseCreateDatasetTool(this.lighthouseService, this.logger);
133+
const listDatasetsTool = new LighthouseListDatasetsTool(this.lighthouseService, this.logger);
134+
const getDatasetTool = new LighthouseGetDatasetTool(this.lighthouseService, this.logger);
135+
const updateDatasetTool = new LighthouseUpdateDatasetTool(this.lighthouseService, this.logger);
128136
const generateKeyTool = new LighthouseGenerateKeyTool(this.lighthouseService, this.logger);
129137
const setupAccessControlTool = new LighthouseSetupAccessControlTool(
130138
this.lighthouseService,
131139
this.logger,
132140
);
133141

134-
// Register lighthouse_upload_file tool
142+
// Register file operation tools
135143
this.registry.register(
136144
LighthouseUploadFileTool.getDefinition(),
137145
async (args) => await uploadFileTool.execute(args),
138146
);
139147

140-
// Register lighthouse_fetch_file tool
141148
this.registry.register(
142149
LighthouseFetchFileTool.getDefinition(),
143150
async (args) => await fetchFileTool.execute(args),
144151
);
145152

146-
// Register lighthouse_generate_key tool
153+
// Register dataset management tools
154+
this.registry.register(
155+
LighthouseCreateDatasetTool.getDefinition(),
156+
async (args) => await createDatasetTool.execute(args),
157+
);
158+
159+
this.registry.register(
160+
LighthouseListDatasetsTool.getDefinition(),
161+
async (args) => await listDatasetsTool.execute(args),
162+
);
163+
164+
this.registry.register(
165+
LighthouseGetDatasetTool.getDefinition(),
166+
async (args) => await getDatasetTool.execute(args),
167+
);
168+
169+
this.registry.register(
170+
LighthouseUpdateDatasetTool.getDefinition(),
171+
async (args) => await updateDatasetTool.execute(args),
172+
);
173+
174+
// Register encryption tools
147175
this.registry.register(
148176
LighthouseGenerateKeyTool.getDefinition(),
149177
async (args) => await generateKeyTool.execute(args),
150178
);
151179

152-
// Register lighthouse_setup_access_control tool
153180
this.registry.register(
154181
LighthouseSetupAccessControlTool.getDefinition(),
155182
async (args) => await setupAccessControlTool.execute(args),
156183
);
157184

158-
// Register lighthouse_create_dataset tool (keeping existing implementation)
159-
const datasetTool = LIGHTHOUSE_MCP_TOOLS.find((t) => t.name === "lighthouse_create_dataset");
160-
if (datasetTool) {
161-
this.registry.register(datasetTool, async (args) => {
162-
const result = await this.datasetService.createDataset({
163-
name: args.name as string,
164-
description: args.description as string | undefined,
165-
files: args.files as string[],
166-
metadata: args.metadata as Record<string, unknown> | undefined,
167-
encrypt: args.encrypt as boolean | undefined,
168-
});
169-
170-
return {
171-
success: true,
172-
data: result,
173-
executionTime: 0,
174-
};
175-
});
176-
}
177-
178185
const registeredTools = this.registry.listTools();
179186
const registrationTime = Date.now() - startTime;
180187

0 commit comments

Comments
 (0)