Skip to content

Commit 2ab808c

Browse files
committed
list log files test
1 parent 15d4611 commit 2ab808c

File tree

2 files changed

+690
-0
lines changed

2 files changed

+690
-0
lines changed
Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
1+
import { test, describe, before, after, beforeEach } from 'node:test';
2+
import { strict as assert } from 'node:assert';
3+
import { connect } from 'mcp-conductor';
4+
5+
describe('list_log_files - Full Mode Programmatic Tests', () => {
6+
let client;
7+
8+
before(async () => {
9+
client = await connect('./conductor.config.with-dw.json');
10+
});
11+
12+
after(async () => {
13+
if (client?.connected) {
14+
await client.disconnect();
15+
}
16+
});
17+
18+
beforeEach(() => {
19+
// CRITICAL: Clear all buffers to prevent leaking into next tests
20+
client.clearAllBuffers(); // Recommended - comprehensive protection
21+
});
22+
23+
// Helper functions for common validations
24+
function assertValidMCPResponse(result) {
25+
assert.ok(result.content, 'Should have content');
26+
assert.ok(Array.isArray(result.content), 'Content should be array');
27+
assert.equal(typeof result.isError, 'boolean', 'isError should be boolean');
28+
}
29+
30+
function parseResponseText(text) {
31+
// The response may come wrapped in quotes, so parse if needed
32+
return text.startsWith('"') && text.endsWith('"')
33+
? JSON.parse(text)
34+
: text;
35+
}
36+
37+
function assertTextContent(result, expectedSubstring) {
38+
assertValidMCPResponse(result);
39+
assert.equal(result.content[0].type, 'text');
40+
const actualText = parseResponseText(result.content[0].text);
41+
assert.ok(actualText.includes(expectedSubstring),
42+
`Expected "${expectedSubstring}" in "${actualText}"`);
43+
}
44+
45+
function assertSuccessResponse(result) {
46+
assertValidMCPResponse(result);
47+
assert.equal(result.isError, false, 'Should not be an error response');
48+
assert.equal(result.content[0].type, 'text');
49+
}
50+
51+
function assertLogFileListFormat(result) {
52+
assertSuccessResponse(result);
53+
const text = parseResponseText(result.content[0].text);
54+
55+
// Should start with the expected header
56+
assert.ok(text.includes('Available log files:'),
57+
'Should contain "Available log files:" header');
58+
59+
// Should contain emoji file icons
60+
assert.ok(text.includes('📄'), 'Should contain file emoji icons');
61+
62+
// Should contain log file patterns for all levels
63+
assert.ok(/debug-blade-\d{8}-\d{6}\.log/.test(text),
64+
'Should contain debug log file pattern');
65+
assert.ok(/error-blade-\d{8}-\d{6}\.log/.test(text),
66+
'Should contain error log file pattern');
67+
assert.ok(/info-blade-\d{8}-\d{6}\.log/.test(text),
68+
'Should contain info log file pattern');
69+
assert.ok(/warn-blade-\d{8}-\d{6}\.log/.test(text),
70+
'Should contain warn log file pattern');
71+
72+
// Should contain file metadata
73+
assert.ok(text.includes('Size:'), 'Should contain file size information');
74+
assert.ok(text.includes('Modified:'), 'Should contain modification date information');
75+
76+
// Should contain proper size format (Bytes, KB, MB)
77+
assert.ok(/Size: [\d.,]+ (Bytes|KB|MB)/.test(text),
78+
'Should contain properly formatted file sizes');
79+
80+
// Should contain GMT timestamp format
81+
assert.ok(/Modified: [A-Za-z]{3}, \d{1,2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} GMT/.test(text),
82+
'Should contain GMT timestamp format');
83+
}
84+
85+
function validateLogFileEntry(text, logLevel) {
86+
// Each log file entry should have the expected structure
87+
const actualText = parseResponseText(text);
88+
const pattern = new RegExp(`📄 \\/${logLevel}-blade-\\d{8}-\\d{6}\\.log[\\s\\S]*?Size: [\\d.,]+ (Bytes|KB|MB)[\\s\\S]*?Modified: [A-Za-z]{3}, \\d{1,2} [A-Za-z]{3} \\d{4} \\d{2}:\\d{2}:\\d{2} GMT`);
89+
assert.ok(pattern.test(actualText),
90+
`Should contain properly formatted ${logLevel} log file entry`);
91+
}
92+
93+
// Basic functionality tests
94+
describe('Basic Functionality', () => {
95+
test('should list available log files with metadata', async () => {
96+
const result = await client.callTool('list_log_files', {});
97+
98+
assertLogFileListFormat(result);
99+
});
100+
101+
test('should handle empty parameters object', async () => {
102+
const result = await client.callTool('list_log_files', {});
103+
104+
assertSuccessResponse(result);
105+
assertTextContent(result, 'Available log files:');
106+
});
107+
108+
test('should not require any parameters', async () => {
109+
const result = await client.callTool('list_log_files', {});
110+
111+
assertValidMCPResponse(result);
112+
assert.equal(result.isError, false, 'Should succeed without parameters');
113+
});
114+
});
115+
116+
// Content validation tests
117+
describe('Content Validation', () => {
118+
test('should include all log file types', async () => {
119+
const result = await client.callTool('list_log_files', {});
120+
121+
assertSuccessResponse(result);
122+
123+
// Validate each log file type has proper formatting
124+
validateLogFileEntry(result.content[0].text, 'debug');
125+
validateLogFileEntry(result.content[0].text, 'error');
126+
validateLogFileEntry(result.content[0].text, 'info');
127+
validateLogFileEntry(result.content[0].text, 'warn');
128+
});
129+
130+
test('should include file paths with forward slashes', async () => {
131+
const result = await client.callTool('list_log_files', {});
132+
133+
assertSuccessResponse(result);
134+
const text = parseResponseText(result.content[0].text);
135+
136+
// Should include file paths starting with forward slash
137+
assert.ok(text.includes('/debug-blade-'), 'Should include debug file path with forward slash');
138+
assert.ok(text.includes('/error-blade-'), 'Should include error file path with forward slash');
139+
assert.ok(text.includes('/info-blade-'), 'Should include info file path with forward slash');
140+
assert.ok(text.includes('/warn-blade-'), 'Should include warn file path with forward slash');
141+
});
142+
143+
test('should format file sizes with proper units', async () => {
144+
const result = await client.callTool('list_log_files', {});
145+
146+
assertSuccessResponse(result);
147+
const text = parseResponseText(result.content[0].text);
148+
149+
// Should contain various size formats
150+
const sizeMatches = text.match(/Size: ([\d.,]+) (Bytes|KB|MB)/g);
151+
assert.ok(sizeMatches && sizeMatches.length >= 4,
152+
'Should have at least 4 file size entries');
153+
154+
// Each size should have valid format
155+
sizeMatches.forEach(sizeMatch => {
156+
assert.ok(/Size: [\d.,]+ (Bytes|KB|MB)/.test(sizeMatch),
157+
`Size format should be valid: ${sizeMatch}`);
158+
});
159+
});
160+
161+
test('should include proper modification timestamps', async () => {
162+
const result = await client.callTool('list_log_files', {});
163+
164+
assertSuccessResponse(result);
165+
const text = result.content[0].text;
166+
167+
// Should contain GMT timestamps for all files
168+
const timestampMatches = text.match(/Modified: [A-Za-z]{3}, \d{1,2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} GMT/g);
169+
assert.ok(timestampMatches && timestampMatches.length >= 4,
170+
'Should have at least 4 modification timestamps');
171+
172+
// Each timestamp should be valid format
173+
timestampMatches.forEach(timestamp => {
174+
assert.ok(/Modified: [A-Za-z]{3}, \d{1,2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} GMT/.test(timestamp),
175+
`Timestamp format should be valid: ${timestamp}`);
176+
});
177+
});
178+
});
179+
180+
// Structure validation tests
181+
describe('Structure Validation', () => {
182+
test('should use consistent formatting across all file entries', async () => {
183+
const result = await client.callTool('list_log_files', {});
184+
185+
assertSuccessResponse(result);
186+
const text = result.content[0].text;
187+
188+
// Should have consistent structure for each log file type
189+
const logTypes = ['debug', 'error', 'info', 'warn'];
190+
191+
logTypes.forEach(logType => {
192+
// Each type should have the full structure: emoji, path, size, modified
193+
const typePattern = new RegExp(`📄 \\/${logType}-blade-\\d{8}-\\d{6}\\.log[\\s\\S]*?Size: [\\d.,]+ (Bytes|KB|MB)[\\s\\S]*?Modified: [A-Za-z]{3}, \\d{1,2} [A-Za-z]{3} \\d{4} \\d{2}:\\d{2}:\\d{2} GMT`);
194+
assert.ok(typePattern.test(text),
195+
`Should have consistent structure for ${logType} log files`);
196+
});
197+
});
198+
199+
test('should have proper spacing and line breaks', async () => {
200+
const result = await client.callTool('list_log_files', {});
201+
202+
assertSuccessResponse(result);
203+
const text = result.content[0].text;
204+
205+
// The response comes wrapped in quotes, so let's parse it
206+
const actualText = text.startsWith('"') && text.endsWith('"')
207+
? JSON.parse(text)
208+
: text;
209+
210+
// Should have header followed by double newline
211+
assert.ok(actualText.startsWith('Available log files:\n\n'),
212+
'Should start with header and double newline');
213+
214+
// Should have double newlines between file entries
215+
const doubleNewlineCount = (actualText.match(/\n\n/g) || []).length;
216+
assert.ok(doubleNewlineCount >= 4,
217+
'Should have double newlines separating entries');
218+
});
219+
220+
test('should use emoji file icons consistently', async () => {
221+
const result = await client.callTool('list_log_files', {});
222+
223+
assertSuccessResponse(result);
224+
const text = result.content[0].text;
225+
226+
// Count emoji icons - should match number of log files
227+
const emojiCount = (text.match(/📄/g) || []).length;
228+
assert.ok(emojiCount >= 4, 'Should have emoji icon for each log file');
229+
230+
// Each emoji should be followed by space and file path
231+
const emojiWithPath = text.match(/📄 \/[\w-]+\.log/g);
232+
assert.ok(emojiWithPath && emojiWithPath.length >= 4,
233+
'Each emoji should be followed by space and file path');
234+
});
235+
});
236+
237+
// Error handling tests
238+
describe('Error Handling', () => {
239+
test('should ignore unknown parameters gracefully', async () => {
240+
const result = await client.callTool('list_log_files', {
241+
unknownParam: 'value',
242+
anotherParam: 123
243+
});
244+
245+
assertSuccessResponse(result);
246+
assertTextContent(result, 'Available log files:');
247+
});
248+
249+
test('should handle null parameters', async () => {
250+
const result = await client.callTool('list_log_files', {
251+
param: null
252+
});
253+
254+
assertSuccessResponse(result);
255+
assertTextContent(result, 'Available log files:');
256+
});
257+
258+
test('should handle empty string parameters', async () => {
259+
const result = await client.callTool('list_log_files', {
260+
param: ''
261+
});
262+
263+
assertSuccessResponse(result);
264+
assertTextContent(result, 'Available log files:');
265+
});
266+
});
267+
268+
// Performance and reliability tests
269+
describe('Performance and Reliability', () => {
270+
test('should respond consistently across multiple calls', async () => {
271+
const results = [];
272+
273+
// Make 3 sequential calls
274+
for (let i = 0; i < 3; i++) {
275+
const result = await client.callTool('list_log_files', {});
276+
results.push(result);
277+
assertSuccessResponse(result);
278+
}
279+
280+
// All results should be consistent in structure
281+
results.forEach((result, index) => {
282+
assertLogFileListFormat(result);
283+
assert.ok(result.content[0].text.includes('debug-blade-'),
284+
`Call ${index + 1} should include debug log files`);
285+
assert.ok(result.content[0].text.includes('error-blade-'),
286+
`Call ${index + 1} should include error log files`);
287+
});
288+
});
289+
290+
test('should handle tool execution without stderr output', async () => {
291+
// Clear any existing stderr before the test
292+
client.clearStderr();
293+
294+
const result = await client.callTool('list_log_files', {});
295+
296+
assertSuccessResponse(result);
297+
298+
// Should not generate stderr output
299+
const stderr = client.getStderr();
300+
assert.equal(stderr.trim(), '', 'Should not generate stderr output');
301+
});
302+
303+
test('should return results within reasonable time', async () => {
304+
const startTime = Date.now();
305+
306+
const result = await client.callTool('list_log_files', {});
307+
308+
const endTime = Date.now();
309+
const duration = endTime - startTime;
310+
311+
assertSuccessResponse(result);
312+
313+
// Should complete within 5 seconds (generous for CI environments)
314+
assert.ok(duration < 5000,
315+
`Should complete within 5 seconds, took ${duration}ms`);
316+
});
317+
});
318+
319+
// Integration validation tests
320+
describe('Integration Validation', () => {
321+
test('should return file information that matches SFCC log patterns', async () => {
322+
const result = await client.callTool('list_log_files', {});
323+
324+
assertSuccessResponse(result);
325+
const text = result.content[0].text;
326+
327+
// Should match SFCC's blade log naming convention
328+
assert.ok(/blade-\d{8}-\d{6}\.log/.test(text),
329+
'Should match SFCC blade log naming convention');
330+
331+
// Should include all standard SFCC log levels
332+
assert.ok(text.includes('debug-blade-'), 'Should include debug logs');
333+
assert.ok(text.includes('error-blade-'), 'Should include error logs');
334+
assert.ok(text.includes('info-blade-'), 'Should include info logs');
335+
assert.ok(text.includes('warn-blade-'), 'Should include warn logs');
336+
});
337+
338+
test('should provide file metadata useful for log analysis', async () => {
339+
const result = await client.callTool('list_log_files', {});
340+
341+
assertSuccessResponse(result);
342+
const text = result.content[0].text;
343+
344+
// File sizes should help determine which logs have content
345+
const sizeMatches = text.match(/Size: ([\d.,]+) (Bytes|KB|MB)/g);
346+
assert.ok(sizeMatches && sizeMatches.length >= 4,
347+
'Should provide size information for all log files');
348+
349+
// Modification times should help identify recent activity
350+
const timeMatches = text.match(/Modified: [A-Za-z]{3}, \d{1,2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} GMT/g);
351+
assert.ok(timeMatches && timeMatches.length >= 4,
352+
'Should provide modification times for all log files');
353+
});
354+
355+
test('should format output suitable for AI analysis', async () => {
356+
const result = await client.callTool('list_log_files', {});
357+
358+
assertSuccessResponse(result);
359+
const text = result.content[0].text;
360+
361+
// The response comes wrapped in quotes, so let's parse it
362+
const actualText = text.startsWith('"') && text.endsWith('"')
363+
? JSON.parse(text)
364+
: text;
365+
366+
// Should be structured and readable
367+
assert.ok(actualText.includes('Available log files:'), 'Should have clear header');
368+
assert.ok(actualText.includes('📄'), 'Should use visual indicators');
369+
assert.ok(/Size: [\d.,]+ (Bytes|KB|MB)/.test(actualText), 'Should have parseable size info');
370+
assert.ok(/Modified: .+ GMT/.test(actualText), 'Should have parseable timestamp info');
371+
372+
// Should be consistently formatted for parsing
373+
const lines = actualText.split('\n');
374+
const fileLines = lines.filter(line => line.includes('📄'));
375+
assert.ok(fileLines.length >= 4, 'Should have clearly identifiable file lines');
376+
});
377+
});
378+
});

0 commit comments

Comments
 (0)