@chen-rmag/core-infra 1.0.0

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 (71) hide show
  1. package/README.md +46 -0
  2. package/dist/ProjectContextManager.d.ts +30 -0
  3. package/dist/ProjectContextManager.js +41 -0
  4. package/dist/directory-validator.d.ts +28 -0
  5. package/dist/directory-validator.js +90 -0
  6. package/dist/index.d.ts +14 -0
  7. package/dist/index.js +44 -0
  8. package/dist/mcp/file-mcp-manager.d.ts +13 -0
  9. package/dist/mcp/file-mcp-manager.js +45 -0
  10. package/dist/mcp/index.d.ts +20 -0
  11. package/dist/mcp/index.js +16 -0
  12. package/dist/mcp/mcp-client.d.ts +127 -0
  13. package/dist/mcp/mcp-client.js +165 -0
  14. package/dist/mcp/mcp-manager.d.ts +20 -0
  15. package/dist/mcp/mcp-manager.js +98 -0
  16. package/dist/mcp/playwright-mcp-manager.d.ts +18 -0
  17. package/dist/mcp/playwright-mcp-manager.js +115 -0
  18. package/dist/model.d.ts +10 -0
  19. package/dist/model.js +207 -0
  20. package/dist/repositories/BaseRepository.d.ts +68 -0
  21. package/dist/repositories/BaseRepository.js +212 -0
  22. package/dist/repositories/DirectoryRepository.d.ts +69 -0
  23. package/dist/repositories/DirectoryRepository.js +335 -0
  24. package/dist/repositories/ExplorationRepository.d.ts +33 -0
  25. package/dist/repositories/ExplorationRepository.js +53 -0
  26. package/dist/repositories/FileRepository.d.ts +55 -0
  27. package/dist/repositories/FileRepository.js +131 -0
  28. package/dist/repositories/ModelConfigRepository.d.ts +33 -0
  29. package/dist/repositories/ModelConfigRepository.js +51 -0
  30. package/dist/repositories/ProjectRepository.d.ts +31 -0
  31. package/dist/repositories/ProjectRepository.js +66 -0
  32. package/dist/repositories/SettingsRepository.d.ts +18 -0
  33. package/dist/repositories/SettingsRepository.js +71 -0
  34. package/dist/repositories/TableDataRepository.d.ts +21 -0
  35. package/dist/repositories/TableDataRepository.js +32 -0
  36. package/dist/repositories/TestCaseRepository.d.ts +120 -0
  37. package/dist/repositories/TestCaseRepository.js +463 -0
  38. package/dist/repositories/TestPlanRepository.d.ts +34 -0
  39. package/dist/repositories/TestPlanRepository.js +79 -0
  40. package/dist/repositories/TestResultRepository.d.ts +29 -0
  41. package/dist/repositories/TestResultRepository.js +53 -0
  42. package/dist/repositories/index.d.ts +16 -0
  43. package/dist/repositories/index.js +30 -0
  44. package/dist/storageService.d.ts +129 -0
  45. package/dist/storageService.js +297 -0
  46. package/dist/types.d.ts +217 -0
  47. package/dist/types.js +2 -0
  48. package/package.json +32 -0
  49. package/src/directory-validator.ts +98 -0
  50. package/src/index.ts +26 -0
  51. package/src/mcp/file-mcp-manager.ts +50 -0
  52. package/src/mcp/index.ts +35 -0
  53. package/src/mcp/mcp-client.ts +209 -0
  54. package/src/mcp/mcp-manager.ts +118 -0
  55. package/src/mcp/playwright-mcp-manager.ts +127 -0
  56. package/src/model.ts +234 -0
  57. package/src/repositories/BaseRepository.ts +193 -0
  58. package/src/repositories/DirectoryRepository.ts +393 -0
  59. package/src/repositories/ExplorationRepository.ts +57 -0
  60. package/src/repositories/FileRepository.ts +153 -0
  61. package/src/repositories/ModelConfigRepository.ts +55 -0
  62. package/src/repositories/ProjectRepository.ts +70 -0
  63. package/src/repositories/SettingsRepository.ts +38 -0
  64. package/src/repositories/TableDataRepository.ts +33 -0
  65. package/src/repositories/TestCaseRepository.ts +521 -0
  66. package/src/repositories/TestPlanRepository.ts +89 -0
  67. package/src/repositories/TestResultRepository.ts +56 -0
  68. package/src/repositories/index.ts +17 -0
  69. package/src/storageService.ts +404 -0
  70. package/src/types.ts +246 -0
  71. package/tsconfig.json +19 -0
@@ -0,0 +1,89 @@
1
+ import { BaseRepository } from './BaseRepository';
2
+ import { TestPlan } from '../types';
3
+ import { join } from 'path';
4
+ import { existsSync, readdirSync } from 'fs';
5
+
6
+ /**
7
+ * TestPlanRepository handles all storage operations for test plans
8
+ * Responsible for managing test plan data
9
+ * Data stored in: ~/.test_agent/projects/{projectId}/test_plans/
10
+ */
11
+ export class TestPlanRepository extends BaseRepository {
12
+ constructor(projectId: string) {
13
+ super(join('projects', projectId, 'test_plans'));
14
+ }
15
+
16
+ /**
17
+ * Save a test plan
18
+ */
19
+ saveTestPlan(testPlan: TestPlan): void {
20
+ this.saveSync(testPlan.id, testPlan);
21
+ }
22
+
23
+ /**
24
+ * Load a test plan by ID
25
+ */
26
+ loadTestPlan(testPlanId: string): TestPlan | null {
27
+ return this.loadSync<TestPlan>(testPlanId);
28
+ }
29
+
30
+ /**
31
+ * List all test plans
32
+ */
33
+ listTestPlans(): TestPlan[] {
34
+ const baseDir = this.getBaseDir();
35
+ if (!existsSync(baseDir)) {
36
+ return [];
37
+ }
38
+
39
+ const files = readdirSync(baseDir);
40
+ const testPlans: TestPlan[] = [];
41
+
42
+ for (const file of files) {
43
+ if (file.endsWith('.json')) {
44
+ const testPlanId = file.replace('.json', '');
45
+ const testPlan = this.loadTestPlan(testPlanId);
46
+ if (testPlan) {
47
+ testPlans.push(testPlan);
48
+ }
49
+ }
50
+ }
51
+
52
+ // Sort by updatedAt descending
53
+ testPlans.sort((a, b) => b.updatedAt - a.updatedAt);
54
+
55
+ return testPlans;
56
+ }
57
+
58
+ /**
59
+ * Update a test plan
60
+ */
61
+ updateTestPlan(testPlan: TestPlan): void {
62
+ const existing = this.loadTestPlan(testPlan.id);
63
+ if (!existing) {
64
+ throw new Error(`Test plan with ID ${testPlan.id} not found`);
65
+ }
66
+
67
+ const updated: TestPlan = {
68
+ ...existing,
69
+ ...testPlan,
70
+ updatedAt: Date.now(),
71
+ };
72
+
73
+ this.saveSync(updated.id, updated);
74
+ }
75
+
76
+ /**
77
+ * Delete a test plan
78
+ */
79
+ deleteTestPlan(testPlanId: string): void {
80
+ this.deleteSync(testPlanId);
81
+ }
82
+
83
+ /**
84
+ * Check if a test plan exists
85
+ */
86
+ exists(testPlanId: string): boolean {
87
+ return this.existsSync(testPlanId);
88
+ }
89
+ }
@@ -0,0 +1,56 @@
1
+ import { join } from 'path';
2
+ import { BaseRepository } from './BaseRepository';
3
+ import { TestResult } from '../types';
4
+
5
+ /**
6
+ * TestResultRepository handles all storage operations for test results
7
+ * Responsible for managing test execution results and history
8
+ */
9
+ export class TestResultRepository extends BaseRepository {
10
+ constructor(projectId: string) {
11
+ super(join("projects", projectId, 'results'));
12
+ }
13
+
14
+ /**
15
+ * Save a test result
16
+ */
17
+ async saveTestResult(result: TestResult): Promise<void> {
18
+ await this.save(result.id, result, 'json');
19
+ }
20
+
21
+ /**
22
+ * Load a test result by ID
23
+ */
24
+ async loadTestResult(resultId: string): Promise<TestResult | null> {
25
+ return this.load<TestResult>(resultId, 'json');
26
+ }
27
+
28
+ /**
29
+ * Load all results for a specific test case
30
+ */
31
+ async loadTestResults(testId: string): Promise<TestResult[]> {
32
+ const results = await this.listAll<TestResult>();
33
+ return results
34
+ .filter(r => r.testId === testId)
35
+ .sort((a, b) => b.executedAt - a.executedAt);
36
+ }
37
+
38
+ /**
39
+ * Delete a test result
40
+ */
41
+ async deleteTestResult(resultId: string): Promise<void> {
42
+ await this.delete(resultId, 'json');
43
+ }
44
+
45
+ /**
46
+ * Delete all results for a specific test case
47
+ */
48
+ async deleteTestResults(testId: string): Promise<void> {
49
+ const results = await this.listAll<TestResult>();
50
+ for (const result of results) {
51
+ if (result.testId === testId) {
52
+ await this.delete(result.id, 'json');
53
+ }
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Repository layer
3
+ * Provides separated data access concerns for different business entities
4
+ * Following Repository Pattern to decouple business logic from data access
5
+ */
6
+
7
+ export { BaseRepository } from './BaseRepository';
8
+ export { TestCaseRepository } from './TestCaseRepository';
9
+ export { TestResultRepository } from './TestResultRepository';
10
+ export { ModelConfigRepository } from './ModelConfigRepository';
11
+ export { ExplorationRepository } from './ExplorationRepository';
12
+ export { SettingsRepository } from './SettingsRepository';
13
+ export { ProjectRepository } from './ProjectRepository';
14
+ export { DirectoryRepository } from './DirectoryRepository';
15
+ export { TestPlanRepository } from './TestPlanRepository';
16
+ export { TableDataRepository } from './TableDataRepository';
17
+ export { FileRepository } from './FileRepository';
@@ -0,0 +1,404 @@
1
+ import { existsSync, mkdirSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ import {
5
+ TestCaseRepository,
6
+ TestResultRepository,
7
+ ModelConfigRepository,
8
+ ExplorationRepository,
9
+ SettingsRepository,
10
+ ProjectRepository,
11
+ DirectoryRepository,
12
+ TestPlanRepository,
13
+ TableDataRepository,
14
+ FileRepository,
15
+ } from './repositories';
16
+ import type { TestCase, TestResult, AppSettings, ModelConfig, TestStep, ExplorationRecord, Project, DirectoryInfo, TestDirectory, TestPlan, TableData } from './types';
17
+
18
+
19
+ const STORAGE_DIR = join(homedir(), '.test_agent');
20
+
21
+ let isInit = false;
22
+
23
+ /**
24
+ * StorageService acts as a facade for all data storage operations
25
+ * Coordinates between different repositories and provides a unified interface
26
+ * This decouples business logic from repository implementation details
27
+ *
28
+ * Benefits:
29
+ * - Single point of entry for all storage operations
30
+ * - Encapsulates repository instantiation
31
+ * - Easy to add cross-cutting concerns (logging, caching, etc.)
32
+ * - Simplifies unit testing by allowing mock repositories
33
+ */
34
+ export class StorageService {
35
+ private static instance: StorageService;
36
+ private testCaseRepo: TestCaseRepository;
37
+ private testResultRepo: TestResultRepository;
38
+ private modelConfigRepo: ModelConfigRepository;
39
+ private explorationRepo: ExplorationRepository;
40
+ private settingsRepo: SettingsRepository;
41
+ private projectRepo: ProjectRepository;
42
+ private directoryRepo: DirectoryRepository;
43
+ private testPlanRepo: TestPlanRepository;
44
+ private tableDataRepo: TableDataRepository;
45
+ private fileRepo: FileRepository;
46
+
47
+ private constructor(projectId: string) {
48
+ this.testCaseRepo = new TestCaseRepository(projectId);
49
+ this.testResultRepo = new TestResultRepository(projectId);
50
+ this.modelConfigRepo = new ModelConfigRepository();
51
+ this.explorationRepo = new ExplorationRepository(projectId);
52
+ this.settingsRepo = new SettingsRepository();
53
+ this.projectRepo = new ProjectRepository();
54
+ this.directoryRepo = new DirectoryRepository(projectId);
55
+ this.testPlanRepo = new TestPlanRepository(projectId);
56
+ this.tableDataRepo = new TableDataRepository(projectId);
57
+ this.fileRepo = new FileRepository(projectId);
58
+ }
59
+
60
+ private static instances: Map<string, StorageService> = new Map();
61
+
62
+ /**
63
+ * Get singleton instance of StorageService
64
+ */
65
+ static getInstance(projectId: string): StorageService {
66
+ if (isInit === false) {
67
+ isInit = true;
68
+ StorageService.createDirs(["models", "settings"]);
69
+ }
70
+
71
+ let instance = StorageService.instances.get(projectId);
72
+ if (!instance) {
73
+ instance = new StorageService(projectId);
74
+ StorageService.instances.set(projectId, instance);
75
+ }
76
+ return instance;
77
+ }
78
+
79
+ /**
80
+ * Reset instance (useful for testing)
81
+ */
82
+ static resetInstance(): void {
83
+ StorageService.instances.clear();
84
+ isInit = false;
85
+ }
86
+
87
+ /**
88
+ * Initialize storage directories
89
+ */
90
+ private static createDirs(dirs: string[]) {
91
+ for (const dir of dirs) {
92
+ const dirPath = join(STORAGE_DIR, dir);
93
+ if (!existsSync(dirPath)) {
94
+ mkdirSync(dirPath, { recursive: true });
95
+ }
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Get storage root directory
101
+ */
102
+ getStorageDir(): string {
103
+ return STORAGE_DIR;
104
+ }
105
+
106
+ // ==================== TestCase Operations ====================
107
+
108
+ saveTestCase(testCase: TestCase): void {
109
+ this.testCaseRepo.saveTestCase(testCase);
110
+ }
111
+
112
+ loadTestCase(testId: string): TestCase | null {
113
+ return this.testCaseRepo.loadTestCase(testId);
114
+ }
115
+
116
+ listTestCases(): TestCase[] {
117
+ return this.testCaseRepo.listTestCases();
118
+ }
119
+
120
+ listResuableTestCases(currentId: string): TestCase[] {
121
+ const list = this.testCaseRepo.listTestCases();
122
+ return list.filter(test => test.reusable && test.id !== currentId && test.status === 'completed');
123
+ }
124
+
125
+ deleteTestCase(testId: string): void {
126
+ this.testCaseRepo.deleteTestCase(testId);
127
+ try {
128
+ this.tableDataRepo.deleteTableData(testId);
129
+ } catch (err) {
130
+ console.error(`Failed to delete table data for test case ${testId}:`, err);
131
+ }
132
+ try {
133
+ this.fileRepo.deleteAllFiles(testId);
134
+ } catch (err) {
135
+ console.error(`Failed to delete files for test case ${testId}:`, err);
136
+ }
137
+ }
138
+
139
+ listTestCasesRecursively(dirPath: string): TestCase[] {
140
+ return this.testCaseRepo.listTestCasesRecursively(dirPath);
141
+ }
142
+
143
+ saveTestScript(testId: string, script: string): void {
144
+ this.testCaseRepo.saveTestScript(testId, script);
145
+ }
146
+
147
+ hasTestScript(testId: string): boolean {
148
+ return this.testCaseRepo.hasTestScript(testId);
149
+ }
150
+
151
+ loadTestScript(testId: string): string | undefined {
152
+ return this.testCaseRepo.loadTestScript(testId);
153
+ }
154
+
155
+ saveTestSteps(testId: string, steps: TestStep[]): void {
156
+ this.testCaseRepo.saveTestSteps(testId, steps);
157
+ }
158
+
159
+ loadTestSteps(testId: string): TestStep[] {
160
+ return this.testCaseRepo.loadTestSteps(testId);
161
+ }
162
+
163
+ saveTestStep(
164
+ testId: string,
165
+ step: { id: string; description?: string; isBreakpoint?: boolean; isAI?: boolean; reusedFromTestId?: string },
166
+ afterStepId?: string
167
+ ): boolean {
168
+ return this.testCaseRepo.saveTestStep(testId, step, afterStepId);
169
+ }
170
+
171
+ insertStep(testId: string, afterStepId: string | null | undefined, newStep: TestStep): void {
172
+ this.testCaseRepo.insertStep(testId, afterStepId, newStep);
173
+ }
174
+
175
+ deleteStep(testId: string, stepId: string): boolean {
176
+ return this.testCaseRepo.deleteStep(testId, stepId);
177
+ }
178
+
179
+ buildTestDirectoryTree(): TestDirectory {
180
+ return this.testCaseRepo.buildDirectoryTree();
181
+ }
182
+
183
+ // ==================== Directory Operations ====================
184
+
185
+ createDirectory(path: string, info: Omit<DirectoryInfo, 'id' | 'path'>): DirectoryInfo {
186
+ return this.directoryRepo.createDirectory(path, info);
187
+ }
188
+
189
+ getDirectoryInfo(path: string): DirectoryInfo | null {
190
+ return this.directoryRepo.getDirectoryInfo(path);
191
+ }
192
+
193
+ updateDirectoryInfo(path: string, updates: Partial<Omit<DirectoryInfo, 'id' | 'path'>>): DirectoryInfo {
194
+ return this.directoryRepo.updateDirectoryInfo(path, updates);
195
+ }
196
+
197
+ deleteDirectory(path: string): void {
198
+ return this.directoryRepo.deleteDirectory(path);
199
+ }
200
+
201
+ renameDirectory(oldPath: string, newName: string): DirectoryInfo {
202
+ return this.directoryRepo.renameDirectory(oldPath, newName);
203
+ }
204
+
205
+ getDirectoriesAtPath(path?: string): DirectoryInfo[] {
206
+ return this.directoryRepo.getDirectoriesAtPath(path);
207
+ }
208
+
209
+ isValidDirectory(path: string): boolean {
210
+ return this.directoryRepo.isValidDirectory(path);
211
+ }
212
+
213
+ moveDirectory(sourcePath: string, targetParentPath: string): DirectoryInfo {
214
+ return this.directoryRepo.moveDirectory(sourcePath, targetParentPath);
215
+ }
216
+
217
+ moveTestCase(testId: string, sourceDir: string, targetDir: string): boolean {
218
+ return this.directoryRepo.moveTestCase(testId, sourceDir, targetDir);
219
+ }
220
+
221
+ // ==================== TestResult Operations ====================
222
+
223
+ async saveTestResult(result: TestResult): Promise<void> {
224
+ await this.testResultRepo.saveTestResult(result);
225
+ }
226
+
227
+ async loadTestResult(resultId: string): Promise<TestResult | null> {
228
+ return this.testResultRepo.loadTestResult(resultId);
229
+ }
230
+
231
+ async loadTestResults(testId: string): Promise<TestResult[]> {
232
+ return this.testResultRepo.loadTestResults(testId);
233
+ }
234
+
235
+ async deleteTestResult(resultId: string): Promise<void> {
236
+ await this.testResultRepo.deleteTestResult(resultId);
237
+ }
238
+
239
+ async deleteTestResults(testId: string): Promise<void> {
240
+ await this.testResultRepo.deleteTestResults(testId);
241
+ }
242
+
243
+ // ==================== ModelConfig Operations ====================
244
+
245
+ async saveModelConfig(config: ModelConfig): Promise<void> {
246
+ await this.modelConfigRepo.saveModelConfig(config);
247
+ }
248
+
249
+ async loadModelConfig(configId: string): Promise<ModelConfig | null> {
250
+ return this.modelConfigRepo.loadModelConfig(configId);
251
+ }
252
+
253
+ async listModelConfigs(): Promise<ModelConfig[]> {
254
+ return this.modelConfigRepo.listModelConfigs();
255
+ }
256
+
257
+ async updateModelConfig(config: ModelConfig): Promise<void> {
258
+ await this.modelConfigRepo.updateModelConfig(config);
259
+ }
260
+
261
+ async deleteModelConfig(configId: string): Promise<void> {
262
+ await this.modelConfigRepo.deleteModelConfig(configId);
263
+ }
264
+
265
+ async modelConfigExists(configId: string): Promise<boolean> {
266
+ return this.modelConfigRepo.exists(configId);
267
+ }
268
+
269
+ // ==================== ExplorationRecord Operations ====================
270
+
271
+ async saveExplorationRecord(record: ExplorationRecord): Promise<void> {
272
+ await this.explorationRepo.saveExplorationRecord(record);
273
+ }
274
+
275
+ async loadExplorationRecord(recordId: string): Promise<ExplorationRecord | null> {
276
+ return this.explorationRepo.loadExplorationRecord(recordId);
277
+ }
278
+
279
+ async listExplorationRecords(): Promise<ExplorationRecord[]> {
280
+ return this.explorationRepo.listExplorationRecords();
281
+ }
282
+
283
+ async updateExplorationRecord(record: ExplorationRecord): Promise<void> {
284
+ await this.explorationRepo.updateExplorationRecord(record);
285
+ }
286
+
287
+ async deleteExplorationRecord(recordId: string): Promise<void> {
288
+ await this.explorationRepo.deleteExplorationRecord(recordId);
289
+ }
290
+
291
+ async getExplorationRecordsByUrl(url: string): Promise<ExplorationRecord[]> {
292
+ return this.explorationRepo.getRecordsByUrl(url);
293
+ }
294
+
295
+ // ==================== Settings Operations ====================
296
+
297
+ async saveSettings(settings: AppSettings): Promise<void> {
298
+ await this.settingsRepo.saveSettings(settings);
299
+ }
300
+
301
+ async loadSettings(): Promise<AppSettings | null> {
302
+ return this.settingsRepo.loadSettings();
303
+ }
304
+
305
+ // ==================== Project Operations ====================
306
+
307
+ saveProject(project: Project): void {
308
+ this.projectRepo.saveProject(project);
309
+ }
310
+
311
+ loadProject(projectId: string): Project | null {
312
+ return this.projectRepo.loadProject(projectId);
313
+ }
314
+
315
+ listProjects(): Project[] {
316
+ return this.projectRepo.listProjects();
317
+ }
318
+
319
+ deleteProject(projectId: string): void {
320
+ this.projectRepo.deleteProject(projectId);
321
+ }
322
+
323
+ async projectExists(projectId: string): Promise<boolean> {
324
+ return this.projectRepo.exists(projectId);
325
+ }
326
+
327
+ // ==================== TestPlan Operations ====================
328
+
329
+ saveTestPlan(testPlan: TestPlan): void {
330
+ this.testPlanRepo.saveTestPlan(testPlan);
331
+ }
332
+
333
+ loadTestPlan(testPlanId: string): TestPlan | null {
334
+ return this.testPlanRepo.loadTestPlan(testPlanId);
335
+ }
336
+
337
+ listTestPlans(): TestPlan[] {
338
+ return this.testPlanRepo.listTestPlans();
339
+ }
340
+
341
+ updateTestPlan(testPlan: TestPlan): void {
342
+ this.testPlanRepo.updateTestPlan(testPlan);
343
+ }
344
+
345
+ deleteTestPlan(testPlanId: string): void {
346
+ this.testPlanRepo.deleteTestPlan(testPlanId);
347
+ }
348
+
349
+ testPlanExists(testPlanId: string): boolean {
350
+ return this.testPlanRepo.exists(testPlanId);
351
+ }
352
+
353
+ // ==================== TableData Operations ====================
354
+
355
+ async saveTableData(tableData: TableData): Promise<void> {
356
+ await this.tableDataRepo.saveTableData(tableData);
357
+ }
358
+
359
+ async loadTableData(testId: string): Promise<TableData | null> {
360
+ const data = await this.tableDataRepo.loadTableData(testId);
361
+ if (data && data.rows.length > 0) {
362
+ return data;
363
+ }
364
+ return null;
365
+ }
366
+
367
+ async deleteTableData(testId: string): Promise<void> {
368
+ await this.tableDataRepo.deleteTableData(testId);
369
+ }
370
+
371
+ // ==================== File Operations ====================
372
+
373
+ getFileDir(testId: string): string {
374
+ return this.fileRepo.getTestFilesDir(testId);
375
+ }
376
+
377
+ saveFile(testId: string, fileName: string, content: Buffer): { name: string; size: number; uploadedAt: string; path: string } {
378
+ return this.fileRepo.saveFile(testId, fileName, content);
379
+ }
380
+
381
+ loadFile(testId: string, fileName: string): Buffer | null {
382
+ return this.fileRepo.loadFile(testId, fileName);
383
+ }
384
+
385
+ listFiles(testId: string): { name: string; size: number; uploadedAt: string; path: string }[] {
386
+ return this.fileRepo.listFiles(testId);
387
+ }
388
+
389
+ deleteFile(testId: string, fileName: string): boolean {
390
+ return this.fileRepo.deleteFile(testId, fileName);
391
+ }
392
+
393
+ deleteAllFiles(testId: string): void {
394
+ this.fileRepo.deleteAllFiles(testId);
395
+ }
396
+
397
+ fileExists(testId: string, fileName: string): boolean {
398
+ return this.fileRepo.fileExists(testId, fileName);
399
+ }
400
+
401
+ getFileInfo(testId: string, fileName: string): { name: string; size: number; uploadedAt: string; path: string } | null {
402
+ return this.fileRepo.getFileInfo(testId, fileName);
403
+ }
404
+ }