Add test for multimodal chat file persistence round-trip
Issue 156 requires verifying that chat files containing multimodal messages can be serialized to disk and deserialized correctly when resuming a session. This commit adds test_multimodal_roundtrip.sh which validates: - Chat file with multimodal message contains content as array of ContentPart objects - Chat file with text-only message contains content as string - Resuming with -F correctly deserializes multimodal messages - Resuming with -F correctly deserializes text-only messages - Round-trip serialization preserves all ContentPart data - Image data URIs are preserved in the chat file - Mixed content types (multimodal and text-only) in same history
This commit is contained in:
@@ -65,3 +65,6 @@ add_dr_test(test_image_url)
|
||||
|
||||
# Add multimodal message construction tests
|
||||
add_dr_test(test_multimodal_message)
|
||||
|
||||
# Add multimodal round-trip persistence tests
|
||||
add_dr_test(test_multimodal_roundtrip)
|
||||
|
||||
Executable
+192
@@ -0,0 +1,192 @@
|
||||
#!/bin/bash
|
||||
# test_multimodal_roundtrip.sh - Tests for chat file persistence round-trip with multimodal messages
|
||||
|
||||
# Create a minimal 1x1 PNG file for testing
|
||||
printf '\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x01\x00\x00\x00\x01\x08\x02\x00\x00\x00\x90\x77\x53\xDE\x00\x00\x00\x0C\x49\x44\x41\x54\x08\xD7\x63\xF8\xCF\xC0\x00\x00\x03\x01\x01\x00\x18\xDD\x8D\xB4\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82' > "${TEST_TEMP_DIR}/test.png"
|
||||
|
||||
# =============================================================================
|
||||
# Test 1: Round-trip serialization preserves multimodal content
|
||||
# =============================================================================
|
||||
|
||||
# Step 1: Create a chat with multimodal message
|
||||
echo "$(be_done "First response")" | "${DRHARNESS}" \
|
||||
-F "${TEST_TEMP_DIR}/chat.json" \
|
||||
-n \
|
||||
-d \
|
||||
-i "${TEST_TEMP_DIR}/test.png" \
|
||||
-m "describe this image" 2>/dev/null
|
||||
|
||||
# Verify the multimodal message was serialized correctly
|
||||
CONTENT_TYPE=$(jq -r '.history[0].content | type' "${TEST_TEMP_DIR}/chat.json")
|
||||
if [ "$CONTENT_TYPE" != "array" ]; then
|
||||
echo "FAIL: Expected content to be array, got '${CONTENT_TYPE}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Capture the original image URL for comparison
|
||||
ORIGINAL_IMAGE_URL=$(jq -r '.history[0].content[1].image_url.url' "${TEST_TEMP_DIR}/chat.json")
|
||||
if ! echo "$ORIGINAL_IMAGE_URL" | grep -q "^data:image/png;base64,"; then
|
||||
echo "FAIL: Expected image URL to be PNG data URI"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 2: Resume the chat and continue the conversation
|
||||
echo "$(be_done "Second response")" | "${DRHARNESS}" \
|
||||
-F "${TEST_TEMP_DIR}/chat.json" \
|
||||
-d \
|
||||
-m "continue conversation" 2>/dev/null
|
||||
|
||||
# =============================================================================
|
||||
# Test 2: Verify round-trip preserves multimodal content after resume
|
||||
# =============================================================================
|
||||
|
||||
# The first message (index 0) should still be the user multimodal message
|
||||
ROLE=$(jq -r '.history[0].role' "${TEST_TEMP_DIR}/chat.json")
|
||||
if [ "$ROLE" != "user" ]; then
|
||||
echo "FAIL: Expected first message role to be 'user', got '${ROLE}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Content should still be an array
|
||||
CONTENT_TYPE=$(jq -r '.history[0].content | type' "${TEST_TEMP_DIR}/chat.json")
|
||||
if [ "$CONTENT_TYPE" != "array" ]; then
|
||||
echo "FAIL: After resume, expected content to be array, got '${CONTENT_TYPE}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify first content part is still text type
|
||||
TEXT_TYPE=$(jq -r '.history[0].content[0].type' "${TEST_TEMP_DIR}/chat.json")
|
||||
if [ "$TEXT_TYPE" != "text" ]; then
|
||||
echo "FAIL: Expected first content part type to be 'text', got '${TEXT_TYPE}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify text content is preserved
|
||||
TEXT_CONTENT=$(jq -r '.history[0].content[0].text' "${TEST_TEMP_DIR}/chat.json")
|
||||
if [ "$TEXT_CONTENT" != "describe this image" ]; then
|
||||
echo "FAIL: Expected text content 'describe this image', got '${TEXT_CONTENT}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify second content part is still image_url type
|
||||
IMAGE_TYPE=$(jq -r '.history[0].content[1].type' "${TEST_TEMP_DIR}/chat.json")
|
||||
if [ "$IMAGE_TYPE" != "image_url" ]; then
|
||||
echo "FAIL: Expected second content part type to be 'image_url', got '${IMAGE_TYPE}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Test 3: Image data URI is preserved in round-trip
|
||||
# =============================================================================
|
||||
|
||||
RESUMED_IMAGE_URL=$(jq -r '.history[0].content[1].image_url.url' "${TEST_TEMP_DIR}/chat.json")
|
||||
if [ "$ORIGINAL_IMAGE_URL" != "$RESUMED_IMAGE_URL" ]; then
|
||||
echo "FAIL: Image data URI not preserved in round-trip"
|
||||
echo "Original: $ORIGINAL_IMAGE_URL"
|
||||
echo "Resumed: $RESUMED_IMAGE_URL"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify image detail is preserved
|
||||
IMAGE_DETAIL=$(jq -r '.history[0].content[1].image_url.detail' "${TEST_TEMP_DIR}/chat.json")
|
||||
if [ "$IMAGE_DETAIL" != "auto" ]; then
|
||||
echo "FAIL: Expected image detail 'auto', got '${IMAGE_DETAIL}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Test 4: Text-only message round-trip (backward compatibility)
|
||||
# =============================================================================
|
||||
|
||||
# Create a new chat with text-only message
|
||||
echo "$(be_done "Text response")" | "${DRHARNESS}" \
|
||||
-F "${TEST_TEMP_DIR}/text_chat.json" \
|
||||
-n \
|
||||
-d \
|
||||
-m "text only prompt" 2>/dev/null
|
||||
|
||||
# Verify text-only content is a string
|
||||
TEXT_CONTENT_TYPE=$(jq -r '.history[0].content | type' "${TEST_TEMP_DIR}/text_chat.json")
|
||||
if [ "$TEXT_CONTENT_TYPE" != "string" ]; then
|
||||
echo "FAIL: Expected text-only content to be string, got '${TEXT_CONTENT_TYPE}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Resume and verify text-only message is preserved
|
||||
echo "$(be_done "More text")" | "${DRHARNESS}" \
|
||||
-F "${TEST_TEMP_DIR}/text_chat.json" \
|
||||
-d \
|
||||
-m "second text prompt" 2>/dev/null
|
||||
|
||||
# Verify first message content is still a string
|
||||
RESUMED_TEXT_TYPE=$(jq -r '.history[0].content | type' "${TEST_TEMP_DIR}/text_chat.json")
|
||||
if [ "$RESUMED_TEXT_TYPE" != "string" ]; then
|
||||
echo "FAIL: After resume, expected text-only content to be string, got '${RESUMED_TEXT_TYPE}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RESUMED_TEXT=$(jq -r '.history[0].content' "${TEST_TEMP_DIR}/text_chat.json")
|
||||
if [ "$RESUMED_TEXT" != "text only prompt" ]; then
|
||||
echo "FAIL: Text content not preserved, expected 'text only prompt', got '${RESUMED_TEXT}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Test 5: Mixed content types in same chat history
|
||||
# =============================================================================
|
||||
|
||||
# Create chat with multimodal message first
|
||||
echo "$(be_done "Response to multimodal")" | "${DRHARNESS}" \
|
||||
-F "${TEST_TEMP_DIR}/mixed_chat.json" \
|
||||
-n \
|
||||
-d \
|
||||
-i "${TEST_TEMP_DIR}/test.png" \
|
||||
-m "first multimodal" 2>/dev/null
|
||||
|
||||
# Resume and add text-only message
|
||||
echo "$(be_done "Response to text")" | "${DRHARNESS}" \
|
||||
-F "${TEST_TEMP_DIR}/mixed_chat.json" \
|
||||
-d \
|
||||
-m "second text only" 2>/dev/null
|
||||
|
||||
# Verify first message (multimodal) is array
|
||||
FIRST_CONTENT_TYPE=$(jq -r '.history[0].content | type' "${TEST_TEMP_DIR}/mixed_chat.json")
|
||||
if [ "$FIRST_CONTENT_TYPE" != "array" ]; then
|
||||
echo "FAIL: Mixed chat: first message should be array, got '${FIRST_CONTENT_TYPE}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify third message (user text-only) is string (index 2: user, assistant, user)
|
||||
THIRD_CONTENT_TYPE=$(jq -r '.history[2].content | type' "${TEST_TEMP_DIR}/mixed_chat.json")
|
||||
if [ "$THIRD_CONTENT_TYPE" != "string" ]; then
|
||||
echo "FAIL: Mixed chat: third message should be string, got '${THIRD_CONTENT_TYPE}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Resume again and verify both types still preserved
|
||||
echo "$(be_done "Third response")" | "${DRHARNESS}" \
|
||||
-F "${TEST_TEMP_DIR}/mixed_chat.json" \
|
||||
-d \
|
||||
-m "third prompt" 2>/dev/null
|
||||
|
||||
# Verify after second resume
|
||||
FINAL_FIRST_TYPE=$(jq -r '.history[0].content | type' "${TEST_TEMP_DIR}/mixed_chat.json")
|
||||
if [ "$FINAL_FIRST_TYPE" != "array" ]; then
|
||||
echo "FAIL: After second resume: first message should still be array"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FINAL_THIRD_TYPE=$(jq -r '.history[2].content | type' "${TEST_TEMP_DIR}/mixed_chat.json")
|
||||
if [ "$FINAL_THIRD_TYPE" != "string" ]; then
|
||||
echo "FAIL: After second resume: third message should still be string"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify the multimodal content data is still intact
|
||||
FINAL_IMAGE_URL=$(jq -r '.history[0].content[1].image_url.url' "${TEST_TEMP_DIR}/mixed_chat.json")
|
||||
if ! echo "$FINAL_IMAGE_URL" | grep -q "^data:image/png;base64,"; then
|
||||
echo "FAIL: After second resume: image URL corrupted"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All multimodal round-trip tests passed"
|
||||
Reference in New Issue
Block a user