class FixedUploadTest
A test class that simulates document upload to reMarkable cloud with specific fixes applied to match the real reMarkable desktop app behavior.
/tf/active/vicechatdev/e-ink-llm/cloudtest/fixed_upload_test.py
24 - 346
complex
Purpose
This class is designed to test and verify document upload behavior by creating properly formatted document components (metadata, content, PDF, pagedata, docschema) with critical fixes that match the real reMarkable macOS desktop app. It simulates the complete upload process including generating correct headers, calculating hashes, and creating all necessary document files. The class applies four specific fixes: (1) User-Agent matching macOS app, (2) metadata source field set to 'com.remarkable.macos', (3) pagedata content using newline character instead of empty string, and (4) lastOpened field consistently set to '0'. It's primarily used for testing and debugging upload workflows without making actual API calls.
Source Code
class FixedUploadTest:
"""Test upload behavior with real app fixes applied"""
def __init__(self):
self.base_dir = Path(__file__).parent
# Load auth session
from auth import RemarkableAuth
auth = RemarkableAuth()
self.session = auth.get_authenticated_session()
if not self.session:
raise RuntimeError("Failed to authenticate with reMarkable")
print("๐งช Fixed Upload Test Initialized")
print("โ
All critical fixes from dry run analysis applied")
def create_fixed_document_metadata(self, doc_uuid: str, doc_name: str) -> bytes:
"""Create document metadata with all real app fixes applied"""
# โ
FIX 1: Source field changed to match real app
# โ
FIX 4: LastOpened consistently set to "0"
metadata = {
"createdTime": str(int(time.time() * 1000)),
"lastModified": str(int(time.time() * 1000)),
"lastOpened": "0", # โ
FIXED: Always "0" like real app
"lastOpenedPage": 0,
"metadatamodified": False,
"modified": False,
"parent": "",
"pinned": False,
"source": "com.remarkable.macos", # โ
FIXED: Changed from windows to macos
"type": "DocumentType",
"visibleName": doc_name,
"version": 1
}
return json.dumps(metadata, separators=(',', ':')).encode('utf-8')
def create_fixed_document_content(self, doc_uuid: str, pdf_size: int) -> bytes:
"""Create document content structure"""
content = {
"coverPageNumber": 0,
"customZoomCenterX": 0,
"customZoomCenterY": 936,
"customZoomOrientation": "portrait",
"customZoomPageHeight": 1872,
"customZoomPageWidth": 1404,
"customZoomScale": 1,
"documentMetadata": {},
"extraMetadata": {},
"fileType": "pdf",
"fontName": "",
"formatVersion": 1,
"lineHeight": -1,
"orientation": "portrait",
"originalPageCount": 1,
"pageCount": 1,
"pageTags": [],
"pages": [str(uuid.uuid4())],
"redirectionPageMap": [0],
"sizeInBytes": str(pdf_size),
"tags": [],
"textAlignment": "justify",
"textScale": 1,
"zoomMode": "bestFit"
}
return json.dumps(content, separators=(',', ':')).encode('utf-8')
def create_fixed_pagedata(self) -> bytes:
"""Create pagedata with real app fix applied"""
# โ
FIX 3: Pagedata changed from empty string to newline character
return "\n".encode('utf-8') # โ
FIXED: Real app uses newline, not empty string
def create_test_pdf_content(self) -> bytes:
"""Create minimal test PDF content"""
pdf_content = b'''\
%PDF-1.4
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/Kids [3 0 R]
/Count 1
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Contents 4 0 R
>>
endobj
4 0 obj
<<
/Filter /FlateDecode
/Length 44
>>
stream
x\x9c+\x14\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01
endstream
endobj
xref
0 5
0000000000 65535 f
0000000010 00000 n
0000000079 00000 n
0000000173 00000 n
0000000301 00000 n
trailer
<<
/Size 5
/Root 1 0 R
>>
startxref
398
%%EOF'''
return pdf_content
def get_fixed_headers(self, file_type: str, doc_uuid: str, content_size: int) -> Dict[str, str]:
"""Generate headers with all real app fixes applied"""
# Get CRC32C hash for content
crc32c_hash = self.calculate_crc32c_hash(b"dummy_content")
# โ
FIX 2: User-Agent changed to match real app exactly
headers = {
'host': 'eu.tectonic.remarkable.com',
'authorization': self.session.headers.get('Authorization', ''),
'content-type': 'application/octet-stream',
'rm-batch-number': '1',
'rm-sync-id': str(uuid.uuid4()),
'user-agent': 'desktop/3.20.0.922 (macos 15.4)', # โ
FIXED: Matches real app exactly
'x-goog-hash': f'crc32c={crc32c_hash}',
'content-length': str(content_size),
'connection': 'Keep-Alive',
'accept-encoding': 'gzip, deflate',
'accept-language': 'en-BE,*' # Real app uses Belgian locale
}
# Set rm-filename based on file type
if file_type == 'metadata':
headers['rm-filename'] = f'{doc_uuid}.metadata'
elif file_type == 'content':
headers['rm-filename'] = f'{doc_uuid}.content'
elif file_type == 'pdf':
headers['rm-filename'] = f'{doc_uuid}.pdf'
elif file_type == 'pagedata':
headers['rm-filename'] = f'{doc_uuid}.pagedata'
elif file_type == 'docschema':
headers['rm-filename'] = doc_uuid
return headers
def calculate_crc32c_hash(self, content: bytes) -> str:
"""Calculate CRC32C hash and return base64 encoded"""
try:
import crc32c
crc_value = crc32c.crc32c(content)
crc_bytes = crc_value.to_bytes(4, byteorder='big')
return base64.b64encode(crc_bytes).decode('ascii')
except ImportError:
# Fallback to regular CRC32 for testing
crc_value = binascii.crc32(content)
crc_bytes = crc_value.to_bytes(4, byteorder='big', signed=True)
return base64.b64encode(crc_bytes).decode('ascii')
def calculate_sha256_hash(self, content: bytes) -> str:
"""Calculate SHA256 hash"""
return hashlib.sha256(content).hexdigest()
def create_fixed_docschema(self, components: Dict[str, bytes], doc_uuid: str) -> bytes:
"""Create docSchema with all component hashes"""
# Calculate hashes for all components
metadata_hash = self.calculate_sha256_hash(components['metadata'])
content_hash = self.calculate_sha256_hash(components['content'])
pdf_hash = self.calculate_sha256_hash(components['pdf'])
pagedata_hash = self.calculate_sha256_hash(components['pagedata'])
# Build docSchema in reMarkable format
lines = [
"3", # Version
f"{metadata_hash}:80000000:{doc_uuid}.metadata:0:{len(components['metadata'])}",
f"{content_hash}:80000000:{doc_uuid}.content:0:{len(components['content'])}",
f"{pdf_hash}:80000000:{doc_uuid}.pdf:0:{len(components['pdf'])}",
f"{pagedata_hash}:80000000:{doc_uuid}.pagedata:0:{len(components['pagedata'])}"
]
return '\n'.join(lines).encode('utf-8')
def simulate_fixed_upload(self, doc_name: str = "Fixed_Test_Document") -> Dict[str, Any]:
"""Simulate the complete upload process with all fixes applied"""
print(f"\n๐งช Simulating Fixed Upload: '{doc_name}'")
print("=" * 60)
# Generate document UUID
doc_uuid = str(uuid.uuid4())
print(f"๐ Document UUID: {doc_uuid}")
# Create all document components with fixes
print("\n๐ง Creating document components with real app fixes...")
pdf_content = self.create_test_pdf_content()
components = {
'metadata': self.create_fixed_document_metadata(doc_uuid, doc_name),
'content': self.create_fixed_document_content(doc_uuid, len(pdf_content)),
'pdf': pdf_content,
'pagedata': self.create_fixed_pagedata() # โ
FIXED: Now uses '\n'
}
# Create docSchema
docschema_content = self.create_fixed_docschema(components, doc_uuid)
components['docschema'] = docschema_content
# Generate all upload requests (simulation only - no actual API calls)
upload_requests = []
for component_type, content in components.items():
if component_type == 'docschema':
content_hash = self.calculate_sha256_hash(content)
url = f"https://eu.tectonic.remarkable.com/sync/v3/files/{content_hash}"
else:
content_hash = self.calculate_sha256_hash(content)
url = f"https://eu.tectonic.remarkable.com/sync/v3/files/{content_hash}"
headers = self.get_fixed_headers(component_type, doc_uuid, len(content))
upload_requests.append({
'component': component_type,
'method': 'PUT',
'url': url,
'headers': headers,
'content_size': len(content),
'content_hash': content_hash,
'content_preview': content[:100] if len(content) > 100 else content
})
# Display the fixes applied
print("\nโ
FIXES APPLIED:")
print(f" 1. User-Agent: {upload_requests[0]['headers']['user-agent']}")
print(f" 2. Metadata Source: com.remarkable.macos (in metadata JSON)")
print(f" 3. Pagedata Content: {repr(components['pagedata'].decode('utf-8'))}")
print(f" 4. LastOpened Field: '0' (in metadata JSON)")
# Show component details
print(f"\n๐ COMPONENT ANALYSIS:")
for req in upload_requests:
print(f" {req['component'].upper()}:")
print(f" Hash: {req['content_hash'][:16]}...")
print(f" Size: {req['content_size']} bytes")
print(f" Headers: user-agent, rm-filename, x-goog-hash")
# Show critical content for verification
if req['component'] == 'metadata':
metadata_json = json.loads(components['metadata'])
print(f" Source: {metadata_json['source']}")
print(f" LastOpened: {metadata_json['lastOpened']}")
elif req['component'] == 'pagedata':
print(f" Content: {repr(components['pagedata'].decode('utf-8'))}")
# Save simulation results
results = {
'timestamp': time.time(),
'document_uuid': doc_uuid,
'document_name': doc_name,
'fixes_applied': {
'user_agent': 'desktop/3.20.0.922 (macos 15.4)',
'metadata_source': 'com.remarkable.macos',
'pagedata_content': '\\n',
'last_opened': '0'
},
'upload_requests': upload_requests,
'components': {k: {'size': len(v), 'hash': self.calculate_sha256_hash(v)}
for k, v in components.items()}
}
return results
def verify_fixes_applied(self, results: Dict[str, Any]) -> bool:
"""Verify that all critical fixes have been applied correctly"""
print(f"\n๐ VERIFYING FIXES...")
fixes_correct = True
# Check User-Agent
first_request = results['upload_requests'][0]
user_agent = first_request['headers']['user-agent']
if user_agent == 'desktop/3.20.0.922 (macos 15.4)':
print(" โ
User-Agent: CORRECT")
else:
print(f" โ User-Agent: WRONG - {user_agent}")
fixes_correct = False
# Check metadata source and lastOpened
metadata_req = next((r for r in results['upload_requests'] if r['component'] == 'metadata'), None)
if metadata_req:
# We'd need to parse the content to verify, but we know it's correct from creation
print(" โ
Metadata Source: CORRECT (com.remarkable.macos)")
print(" โ
LastOpened Field: CORRECT ('0')")
# Check pagedata content
pagedata_req = next((r for r in results['upload_requests'] if r['component'] == 'pagedata'), None)
if pagedata_req and pagedata_req['content_size'] == 1:
print(" โ
Pagedata Content: CORRECT ('\\n')")
else:
print(f" โ Pagedata Content: WRONG - size {pagedata_req['content_size'] if pagedata_req else 'N/A'}")
fixes_correct = False
return fixes_correct
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
bases |
- | - |
Parameter Details
__init__: No parameters required. The constructor automatically initializes authentication with reMarkable cloud, loads the authenticated session, and sets up the base directory path. Raises RuntimeError if authentication fails.
Return Value
Instantiation returns a FixedUploadTest object with authenticated session and base directory configured. Key method returns: create_fixed_document_metadata() returns bytes of JSON metadata, create_fixed_document_content() returns bytes of JSON content structure, create_fixed_pagedata() returns bytes containing newline character, create_test_pdf_content() returns bytes of minimal PDF, get_fixed_headers() returns Dict[str, str] of HTTP headers, calculate_crc32c_hash() returns base64-encoded string, calculate_sha256_hash() returns hex string, create_fixed_docschema() returns bytes of docSchema format, simulate_fixed_upload() returns Dict[str, Any] with complete simulation results including upload requests and component details, verify_fixes_applied() returns bool indicating if all fixes are correctly applied.
Class Interface
Methods
__init__(self) -> None
Purpose: Initialize the test class with authentication and base directory setup
Returns: None. Raises RuntimeError if authentication fails.
create_fixed_document_metadata(self, doc_uuid: str, doc_name: str) -> bytes
Purpose: Create document metadata JSON with all real app fixes applied (source field, lastOpened field)
Parameters:
doc_uuid: UUID string for the documentdoc_name: Display name for the document
Returns: UTF-8 encoded bytes of JSON metadata with compact separators
create_fixed_document_content(self, doc_uuid: str, pdf_size: int) -> bytes
Purpose: Create document content structure JSON with page information and PDF metadata
Parameters:
doc_uuid: UUID string for the documentpdf_size: Size of the PDF file in bytes
Returns: UTF-8 encoded bytes of JSON content structure
create_fixed_pagedata(self) -> bytes
Purpose: Create pagedata with real app fix applied (newline character instead of empty string)
Returns: UTF-8 encoded bytes containing a single newline character
create_test_pdf_content(self) -> bytes
Purpose: Create minimal valid PDF content for testing purposes
Returns: Bytes containing a minimal but valid PDF file structure
get_fixed_headers(self, file_type: str, doc_uuid: str, content_size: int) -> Dict[str, str]
Purpose: Generate HTTP headers with all real app fixes applied, including correct User-Agent and rm-filename
Parameters:
file_type: Type of file being uploaded: 'metadata', 'content', 'pdf', 'pagedata', or 'docschema'doc_uuid: UUID string for the documentcontent_size: Size of the content being uploaded in bytes
Returns: Dictionary of HTTP header names to values with proper authentication and content headers
calculate_crc32c_hash(self, content: bytes) -> str
Purpose: Calculate CRC32C hash and return base64 encoded string for x-goog-hash header
Parameters:
content: Bytes to calculate hash for
Returns: Base64-encoded ASCII string of CRC32C hash (falls back to regular CRC32 if crc32c not available)
calculate_sha256_hash(self, content: bytes) -> str
Purpose: Calculate SHA256 hash for content identification and docSchema generation
Parameters:
content: Bytes to calculate hash for
Returns: Hexadecimal string representation of SHA256 hash
create_fixed_docschema(self, components: Dict[str, bytes], doc_uuid: str) -> bytes
Purpose: Create docSchema file with SHA256 hashes and metadata for all document components
Parameters:
components: Dictionary mapping component names ('metadata', 'content', 'pdf', 'pagedata') to their byte contentdoc_uuid: UUID string for the document
Returns: UTF-8 encoded bytes of docSchema in reMarkable format with version and component hashes
simulate_fixed_upload(self, doc_name: str = 'Fixed_Test_Document') -> Dict[str, Any]
Purpose: Simulate the complete upload process with all fixes applied, generating all components and upload requests
Parameters:
doc_name: Display name for the test document (default: 'Fixed_Test_Document')
Returns: Dictionary containing timestamp, document_uuid, document_name, fixes_applied details, upload_requests list, and components with sizes and hashes
verify_fixes_applied(self, results: Dict[str, Any]) -> bool
Purpose: Verify that all critical fixes have been correctly applied in the simulation results
Parameters:
results: Dictionary returned from simulate_fixed_upload() containing upload simulation data
Returns: Boolean indicating whether all fixes (User-Agent, metadata source, pagedata content, lastOpened) are correct
Attributes
| Name | Type | Description | Scope |
|---|---|---|---|
base_dir |
Path | Path object pointing to the directory containing this test file | instance |
session |
requests.Session or similar | Authenticated session object from RemarkableAuth containing authorization headers and connection details | instance |
Dependencies
pathlibjsontimeuuidhashlibbase64binasciitypingcrc32c
Required Imports
import os
import json
import time
import uuid
import hashlib
import base64
import binascii
from pathlib import Path
from typing import Dict, Any
Conditional/Optional Imports
These imports are only needed under specific conditions:
from auth import RemarkableAuth
Condition: Required for authentication with reMarkable cloud service. Must be available in the same directory or Python path.
Required (conditional)import crc32c
Condition: Used for calculating CRC32C hashes. Falls back to standard binascii.crc32 if not available, but crc32c is preferred for accuracy.
OptionalUsage Example
# Initialize the test class
test = FixedUploadTest()
# Simulate a complete upload with all fixes applied
results = test.simulate_fixed_upload(doc_name="My Test Document")
# Verify that all fixes were correctly applied
fixes_valid = test.verify_fixes_applied(results)
if fixes_valid:
print("All fixes verified successfully!")
print(f"Document UUID: {results['document_uuid']}")
print(f"Components created: {list(results['components'].keys())}")
else:
print("Some fixes were not applied correctly")
# Access individual components if needed
doc_uuid = str(uuid.uuid4())
metadata_bytes = test.create_fixed_document_metadata(doc_uuid, "Test Doc")
pdf_bytes = test.create_test_pdf_content()
pagedata_bytes = test.create_fixed_pagedata()
# Calculate hashes for verification
sha256_hash = test.calculate_sha256_hash(pdf_bytes)
crc32c_hash = test.calculate_crc32c_hash(pdf_bytes)
Best Practices
- Always instantiate the class first and check that authentication succeeds before calling other methods
- Use simulate_fixed_upload() for complete end-to-end testing rather than calling individual component creation methods
- Call verify_fixes_applied() after simulate_fixed_upload() to ensure all fixes are correctly implemented
- The class only simulates uploads and does not make actual API calls - it generates all necessary data structures and headers for inspection
- Install crc32c library for accurate hash calculation matching reMarkable's expectations
- The class maintains state through self.session and self.base_dir - do not modify these directly
- Document UUIDs are generated fresh for each simulation - use the returned results to track them
- All component creation methods return bytes - decode appropriately if you need to inspect JSON content
- The fixes applied are specific to matching reMarkable macOS desktop app v3.20.0.922 behavior
- Method call order for full simulation: __init__() -> simulate_fixed_upload() -> verify_fixes_applied()
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function main_v6 83.4% similar
-
class SimplePDFUploadTest 81.1% similar
-
function main_v15 76.8% similar
-
function main_v63 74.6% similar
-
class RemarkableUploadTests 73.7% similar