Skip to content

Commit 2491b68

Browse files
authored
test: add tests for large payload storage and session isolation (#593)
2 parents b8500b9 + ef67c2e commit 2491b68

2 files changed

Lines changed: 960 additions & 0 deletions

File tree

internal/middleware/jqschema_test.go

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,264 @@ func TestWrapToolHandler_LongPayload(t *testing.T) {
226226
assert.True(t, strings.HasSuffix(preview, "..."), "Preview should end with '...'")
227227
}
228228

229+
// TestPayloadStorage_SessionIsolation verifies that payloads are stored in session-specific directories
230+
func TestPayloadStorage_SessionIsolation(t *testing.T) {
231+
// Create temporary directory for test
232+
baseDir := t.TempDir()
233+
234+
// Define two different session IDs
235+
session1 := "session-alpha-123"
236+
session2 := "session-beta-456"
237+
238+
// Create a mock handler
239+
mockHandler := func(ctx context.Context, req *sdk.CallToolRequest, args interface{}) (*sdk.CallToolResult, interface{}, error) {
240+
return &sdk.CallToolResult{IsError: false}, map[string]interface{}{
241+
"data": "test-payload",
242+
}, nil
243+
}
244+
245+
// Create session ID getters for each session
246+
getSession1 := func(ctx context.Context) string { return session1 }
247+
getSession2 := func(ctx context.Context) string { return session2 }
248+
249+
// Call handler for session 1
250+
wrapped1 := WrapToolHandler(mockHandler, "test_tool", baseDir, getSession1)
251+
_, data1, err := wrapped1(context.Background(), &sdk.CallToolRequest{}, map[string]interface{}{})
252+
require.NoError(t, err)
253+
254+
// Call handler for session 2
255+
wrapped2 := WrapToolHandler(mockHandler, "test_tool", baseDir, getSession2)
256+
_, data2, err := wrapped2(context.Background(), &sdk.CallToolRequest{}, map[string]interface{}{})
257+
require.NoError(t, err)
258+
259+
// Extract payload paths
260+
dataMap1 := data1.(map[string]interface{})
261+
dataMap2 := data2.(map[string]interface{})
262+
263+
payloadPath1 := dataMap1["payloadPath"].(string)
264+
payloadPath2 := dataMap2["payloadPath"].(string)
265+
266+
// Verify paths are different
267+
assert.NotEqual(t, payloadPath1, payloadPath2, "Different sessions should have different payload paths")
268+
269+
// Verify session directories exist and are isolated
270+
session1Dir := filepath.Join(baseDir, session1)
271+
session2Dir := filepath.Join(baseDir, session2)
272+
273+
assert.DirExists(t, session1Dir, "Session 1 directory should exist")
274+
assert.DirExists(t, session2Dir, "Session 2 directory should exist")
275+
276+
// Verify payload paths contain respective session IDs
277+
assert.Contains(t, payloadPath1, session1, "Payload path 1 should contain session 1 ID")
278+
assert.Contains(t, payloadPath2, session2, "Payload path 2 should contain session 2 ID")
279+
280+
// Verify payloads are not in each other's directories
281+
assert.NotContains(t, payloadPath1, session2, "Payload 1 should not be in session 2 directory")
282+
assert.NotContains(t, payloadPath2, session1, "Payload 2 should not be in session 1 directory")
283+
284+
// Verify files exist at the correct paths
285+
assert.FileExists(t, payloadPath1, "Payload file 1 should exist")
286+
assert.FileExists(t, payloadPath2, "Payload file 2 should exist")
287+
}
288+
289+
// TestPayloadStorage_LargePayloadPreserved verifies that the complete large payload is stored to disk
290+
func TestPayloadStorage_LargePayloadPreserved(t *testing.T) {
291+
baseDir := t.TempDir()
292+
293+
// Create a large payload (> 500 chars to trigger truncation)
294+
largeContent := strings.Repeat("This is a large payload content. ", 100) // ~3400 chars
295+
largePayload := map[string]interface{}{
296+
"total_count": 1000,
297+
"items": []interface{}{
298+
map[string]interface{}{
299+
"id": 12345,
300+
"name": "test-item",
301+
"description": largeContent,
302+
"metadata": map[string]interface{}{
303+
"author": "test-author",
304+
"tags": []interface{}{"tag1", "tag2", "tag3"},
305+
},
306+
},
307+
},
308+
}
309+
310+
mockHandler := func(ctx context.Context, req *sdk.CallToolRequest, args interface{}) (*sdk.CallToolResult, interface{}, error) {
311+
return &sdk.CallToolResult{IsError: false}, largePayload, nil
312+
}
313+
314+
wrapped := WrapToolHandler(mockHandler, "test_tool", baseDir, testGetSessionID)
315+
_, data, err := wrapped(context.Background(), &sdk.CallToolRequest{}, map[string]interface{}{})
316+
require.NoError(t, err)
317+
318+
dataMap := data.(map[string]interface{})
319+
320+
// Verify truncation occurred
321+
assert.True(t, dataMap["truncated"].(bool), "Large payload should be truncated")
322+
323+
// Get the payload path
324+
payloadPath := dataMap["payloadPath"].(string)
325+
assert.FileExists(t, payloadPath, "Payload file should exist")
326+
327+
// Read the stored payload
328+
storedContent, err := os.ReadFile(payloadPath)
329+
require.NoError(t, err, "Should be able to read stored payload")
330+
331+
// Verify stored payload is valid JSON
332+
var storedPayload map[string]interface{}
333+
err = json.Unmarshal(storedContent, &storedPayload)
334+
require.NoError(t, err, "Stored payload should be valid JSON")
335+
336+
// Verify complete payload structure is preserved
337+
assert.Equal(t, float64(1000), storedPayload["total_count"], "total_count should be preserved")
338+
339+
items := storedPayload["items"].([]interface{})
340+
require.Len(t, items, 1, "Should have 1 item")
341+
342+
item := items[0].(map[string]interface{})
343+
assert.Equal(t, float64(12345), item["id"], "Item ID should be preserved")
344+
assert.Equal(t, "test-item", item["name"], "Item name should be preserved")
345+
assert.Equal(t, largeContent, item["description"], "Complete large description should be preserved")
346+
347+
metadata := item["metadata"].(map[string]interface{})
348+
assert.Equal(t, "test-author", metadata["author"], "Metadata author should be preserved")
349+
350+
// Verify originalSize matches stored content size
351+
originalSize := dataMap["originalSize"].(int)
352+
assert.Equal(t, len(storedContent), originalSize, "originalSize should match stored content size")
353+
}
354+
355+
// TestPayloadStorage_DirectoryStructure verifies the directory structure {baseDir}/{sessionID}/{queryID}/payload.json
356+
func TestPayloadStorage_DirectoryStructure(t *testing.T) {
357+
baseDir := t.TempDir()
358+
sessionID := "test-session-dir-check"
359+
360+
getSessionID := func(ctx context.Context) string { return sessionID }
361+
362+
mockHandler := func(ctx context.Context, req *sdk.CallToolRequest, args interface{}) (*sdk.CallToolResult, interface{}, error) {
363+
return &sdk.CallToolResult{IsError: false}, map[string]interface{}{"test": "data"}, nil
364+
}
365+
366+
wrapped := WrapToolHandler(mockHandler, "test_tool", baseDir, getSessionID)
367+
_, data, err := wrapped(context.Background(), &sdk.CallToolRequest{}, map[string]interface{}{})
368+
require.NoError(t, err)
369+
370+
dataMap := data.(map[string]interface{})
371+
372+
payloadPath := dataMap["payloadPath"].(string)
373+
queryID := dataMap["queryID"].(string)
374+
375+
// Verify the expected directory structure
376+
expectedDir := filepath.Join(baseDir, sessionID, queryID)
377+
expectedPath := filepath.Join(expectedDir, "payload.json")
378+
379+
assert.Equal(t, expectedPath, payloadPath, "Payload path should match expected structure")
380+
assert.DirExists(t, expectedDir, "Query directory should exist")
381+
382+
// Verify the file is named payload.json
383+
assert.True(t, strings.HasSuffix(payloadPath, "payload.json"), "File should be named payload.json")
384+
385+
// Verify queryID is a valid hex string (32 chars = 16 bytes in hex)
386+
assert.Len(t, queryID, 32, "Query ID should be 32 hex characters")
387+
}
388+
389+
// TestPayloadStorage_MultipleRequestsSameSession verifies that multiple requests from the same session
390+
// create separate query directories
391+
func TestPayloadStorage_MultipleRequestsSameSession(t *testing.T) {
392+
baseDir := t.TempDir()
393+
sessionID := "same-session"
394+
395+
getSessionID := func(ctx context.Context) string { return sessionID }
396+
397+
mockHandler := func(ctx context.Context, req *sdk.CallToolRequest, args interface{}) (*sdk.CallToolResult, interface{}, error) {
398+
return &sdk.CallToolResult{IsError: false}, map[string]interface{}{"request": "data"}, nil
399+
}
400+
401+
wrapped := WrapToolHandler(mockHandler, "test_tool", baseDir, getSessionID)
402+
403+
// Make multiple requests
404+
var payloadPaths []string
405+
var queryIDs []string
406+
407+
for i := 0; i < 5; i++ {
408+
_, data, err := wrapped(context.Background(), &sdk.CallToolRequest{}, map[string]interface{}{})
409+
require.NoError(t, err)
410+
411+
dataMap := data.(map[string]interface{})
412+
payloadPaths = append(payloadPaths, dataMap["payloadPath"].(string))
413+
queryIDs = append(queryIDs, dataMap["queryID"].(string))
414+
}
415+
416+
// Verify all payload paths are unique
417+
pathSet := make(map[string]bool)
418+
for _, path := range payloadPaths {
419+
assert.False(t, pathSet[path], "Payload paths should be unique")
420+
pathSet[path] = true
421+
}
422+
423+
// Verify all query IDs are unique
424+
idSet := make(map[string]bool)
425+
for _, id := range queryIDs {
426+
assert.False(t, idSet[id], "Query IDs should be unique")
427+
idSet[id] = true
428+
}
429+
430+
// Verify all files exist
431+
for _, path := range payloadPaths {
432+
assert.FileExists(t, path, "Each payload file should exist")
433+
}
434+
435+
// Verify all are in the same session directory but different query directories
436+
sessionDir := filepath.Join(baseDir, sessionID)
437+
for _, path := range payloadPaths {
438+
assert.True(t, strings.HasPrefix(path, sessionDir), "All payloads should be in session directory")
439+
}
440+
}
441+
442+
// TestPayloadStorage_FilePermissions verifies that payload directories and files have secure permissions
443+
func TestPayloadStorage_FilePermissions(t *testing.T) {
444+
baseDir := t.TempDir()
445+
sessionID := "test-permissions"
446+
queryID := "test-query-perms"
447+
payload := []byte(`{"secure": "data"}`)
448+
449+
filePath, err := savePayload(baseDir, sessionID, queryID, payload)
450+
require.NoError(t, err)
451+
452+
// Check directory permissions
453+
dirPath := filepath.Dir(filePath)
454+
dirInfo, err := os.Stat(dirPath)
455+
require.NoError(t, err)
456+
assert.Equal(t, os.FileMode(0700), dirInfo.Mode().Perm(), "Directory should have 0700 permissions")
457+
458+
// Check file permissions
459+
fileInfo, err := os.Stat(filePath)
460+
require.NoError(t, err)
461+
assert.Equal(t, os.FileMode(0600), fileInfo.Mode().Perm(), "File should have 0600 permissions")
462+
}
463+
464+
// TestPayloadStorage_DefaultSessionID verifies behavior when session ID is empty
465+
func TestPayloadStorage_DefaultSessionID(t *testing.T) {
466+
baseDir := t.TempDir()
467+
468+
// Return empty string to trigger default behavior
469+
getEmptySessionID := func(ctx context.Context) string { return "" }
470+
471+
mockHandler := func(ctx context.Context, req *sdk.CallToolRequest, args interface{}) (*sdk.CallToolResult, interface{}, error) {
472+
return &sdk.CallToolResult{IsError: false}, map[string]interface{}{"test": "data"}, nil
473+
}
474+
475+
wrapped := WrapToolHandler(mockHandler, "test_tool", baseDir, getEmptySessionID)
476+
_, data, err := wrapped(context.Background(), &sdk.CallToolRequest{}, map[string]interface{}{})
477+
require.NoError(t, err)
478+
479+
dataMap := data.(map[string]interface{})
480+
payloadPath := dataMap["payloadPath"].(string)
481+
482+
// Verify the payload is stored in "default" session directory
483+
assert.Contains(t, payloadPath, "default", "Empty session should use 'default' directory")
484+
assert.FileExists(t, payloadPath, "Payload file should exist")
485+
}
486+
229487
func TestShouldApplyMiddleware(t *testing.T) {
230488
tests := []struct {
231489
name string

0 commit comments

Comments
 (0)