Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "socketdev"
version = "3.0.16"
version = "3.0.17"
requires-python = ">= 3.9"
dependencies = [
'requests',
Expand Down
76 changes: 75 additions & 1 deletion socketdev/uploadmanifests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,79 @@ class UploadManifests:
def __init__(self, api):
self.api = api

def _calculate_key_name(self, file_path: str, workspace: Optional[str] = None, base_path: Optional[str] = None, base_paths: Optional[List[str]] = None) -> str:
"""
Calculate the key name for a file using the same logic as load_files_for_sending_lazy.
This ensures consistency between lazy and non-lazy loading modes.
"""
# Normalize file path
if "\\" in file_path:
file_path = file_path.replace("\\", "/")

# Normalize paths
if workspace and "\\" in workspace:
workspace = workspace.replace("\\", "/")
if base_path and "\\" in base_path:
base_path = base_path.replace("\\", "/")
if base_paths:
base_paths = [bp.replace("\\", "/") if "\\" in bp else bp for bp in base_paths]

# Calculate the key name for the form data
key = file_path
path_stripped = False

# If base_paths is provided, try to strip one of the paths from the file path
if base_paths:
for bp in base_paths:
normalized_base_path = bp.rstrip("/") + "/" if not bp.endswith("/") else bp
if key.startswith(normalized_base_path):
key = key[len(normalized_base_path):]
path_stripped = True
break
elif key.startswith(bp.rstrip("/")):
stripped_base = bp.rstrip("/")
if key.startswith(stripped_base + "/") or key == stripped_base:
key = key[len(stripped_base):]
key = key.lstrip("/")
path_stripped = True
break
elif base_path:
normalized_base_path = base_path.rstrip("/") + "/" if not base_path.endswith("/") else base_path
if key.startswith(normalized_base_path):
key = key[len(normalized_base_path):]
path_stripped = True
elif key.startswith(base_path.rstrip("/")):
stripped_base = base_path.rstrip("/")
if key.startswith(stripped_base + "/") or key == stripped_base:
key = key[len(stripped_base):]
key = key.lstrip("/")
path_stripped = True

# If workspace is provided and no base paths matched, fall back to workspace logic
if not path_stripped and workspace and file_path.startswith(workspace):
key = file_path[len(workspace):]
# Remove all leading slashes (for absolute paths)
while key.startswith("/"):
key = key[1:]
path_stripped = True

# Clean up relative path prefixes, but preserve filename dots
while key.startswith("./"):
key = key[2:]
while key.startswith("../"):
key = key[3:]
# Remove any remaining leading slashes (for absolute paths)
while key.startswith("/"):
key = key[1:]

# Remove Windows drive letter if present (C:/...)
if len(key) > 2 and key[1] == ':' and (key[2] == '/' or key[2] == '\\'):
key = key[2:]
while key.startswith("/"):
key = key[1:]

return key

def upload_manifest_files(self, org_slug: str, file_paths: List[str], workspace: Optional[str] = None, base_path: Optional[str] = None, base_paths: Optional[List[str]] = None, use_lazy_loading: bool = True) -> str:
"""
Upload manifest files to Socket API and return tarHash.
Expand Down Expand Up @@ -46,7 +119,8 @@ def upload_manifest_files(self, org_slug: str, file_paths: List[str], workspace:
# Fallback to basic file loading if needed
loaded_files = []
for file_path in valid_files:
key = os.path.basename(file_path)
# Use the same key generation logic as lazy loading for consistency
key = self._calculate_key_name(file_path, workspace, base_path, base_paths)
with open(file_path, 'rb') as f:
loaded_files.append((key, (key, f.read())))

Expand Down
2 changes: 1 addition & 1 deletion socketdev/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "3.0.16"
__version__ = "3.0.17"
179 changes: 179 additions & 0 deletions tests/unit/test_upload_manifests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import os
import tempfile
import unittest
from unittest.mock import Mock, patch, mock_open
from socketdev.uploadmanifests import UploadManifests


class TestUploadManifests(unittest.TestCase):
def setUp(self):
self.mock_api = Mock()
self.upload_manifests = UploadManifests(self.mock_api)

def test_calculate_key_name_with_base_path(self):
"""Test that key names are calculated correctly with base_path."""
# Test with base_path
key = self.upload_manifests._calculate_key_name(
"/project/frontend/package.json",
base_path="/project"
)
self.assertEqual(key, "frontend/package.json")

def test_calculate_key_name_with_workspace(self):
"""Test that key names are calculated correctly with workspace."""
# Test with workspace
key = self.upload_manifests._calculate_key_name(
"/project/frontend/package.json",
workspace="/project/"
)
self.assertEqual(key, "frontend/package.json")

def test_calculate_key_name_with_base_paths(self):
"""Test that key names are calculated correctly with base_paths."""
# Test with base_paths (takes precedence over base_path)
key = self.upload_manifests._calculate_key_name(
"/project/frontend/package.json",
base_path="/project",
base_paths=["/different", "/project"]
)
self.assertEqual(key, "frontend/package.json")

def test_calculate_key_name_no_stripping(self):
"""Test that key names default to basename when no stripping options provided."""
# Test without any path stripping - should preserve relative path structure
key = self.upload_manifests._calculate_key_name(
"frontend/package.json"
)
self.assertEqual(key, "frontend/package.json")

def test_calculate_key_name_absolute_path_no_stripping(self):
"""Test that absolute paths get cleaned up when no stripping options provided."""
key = self.upload_manifests._calculate_key_name(
"/absolute/path/frontend/package.json"
)
self.assertEqual(key, "absolute/path/frontend/package.json")

def test_calculate_key_name_windows_paths(self):
"""Test that Windows paths are handled correctly."""
key = self.upload_manifests._calculate_key_name(
"C:\\project\\frontend\\package.json",
base_path="C:\\project"
)
self.assertEqual(key, "frontend/package.json")

@patch('socketdev.uploadmanifests.Utils.load_files_for_sending_lazy')
@patch('os.path.exists')
@patch('os.path.isfile')
def test_upload_manifest_files_lazy_loading(self, mock_isfile, mock_exists, mock_lazy_load):
"""Test that lazy loading preserves key names correctly."""
# Setup mocks
mock_exists.return_value = True
mock_isfile.return_value = True
mock_lazy_load.return_value = [
('frontend/package.json', ('frontend/package.json', Mock()))
]
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {'tarHash': 'test_hash'}
self.mock_api.do_request.return_value = mock_response

# Test lazy loading
result = self.upload_manifests.upload_manifest_files(
"test_org",
["/project/frontend/package.json"],
workspace="/project",
use_lazy_loading=True
)

self.assertEqual(result, 'test_hash')
mock_lazy_load.assert_called_once_with(
["/project/frontend/package.json"],
workspace="/project",
base_path=None,
base_paths=None
)

@patch('builtins.open', new_callable=mock_open, read_data=b'{"name": "test"}')
@patch('os.path.exists')
@patch('os.path.isfile')
def test_upload_manifest_files_non_lazy_loading(self, mock_isfile, mock_exists, mock_file):
"""Test that non-lazy loading produces same key names as lazy loading."""
# Setup mocks
mock_exists.return_value = True
mock_isfile.return_value = True
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {'tarHash': 'test_hash'}
self.mock_api.do_request.return_value = mock_response

# Test non-lazy loading with workspace
result = self.upload_manifests.upload_manifest_files(
"test_org",
["frontend/package.json"],
workspace="/project",
use_lazy_loading=False
)

self.assertEqual(result, 'test_hash')

# Verify the API was called with the correct file structure
call_args = self.mock_api.do_request.call_args
files_arg = call_args[1]['files']
self.assertEqual(len(files_arg), 1)

# The key should be 'frontend/package.json' not just 'package.json'
key, (filename, content) = files_arg[0]
self.assertEqual(key, "frontend/package.json")
self.assertEqual(filename, "frontend/package.json")

@patch('builtins.open', new_callable=mock_open, read_data=b'{"name": "test"}')
@patch('os.path.exists')
@patch('os.path.isfile')
def test_upload_manifest_files_consistency_between_modes(self, mock_isfile, mock_exists, mock_file):
"""Test that lazy and non-lazy loading produce identical key names."""
# Setup mocks
mock_exists.return_value = True
mock_isfile.return_value = True
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {'tarHash': 'test_hash'}
self.mock_api.do_request.return_value = mock_response

test_files = ["frontend/package.json", "backend/package.json"]

# Test non-lazy loading
with patch('socketdev.uploadmanifests.Utils.load_files_for_sending_lazy') as mock_lazy_load:
mock_lazy_load.return_value = [
('frontend/package.json', ('frontend/package.json', Mock())),
('backend/package.json', ('backend/package.json', Mock()))
]

# Get lazy loading result
self.upload_manifests.upload_manifest_files(
"test_org",
test_files,
use_lazy_loading=True
)
lazy_call_args = self.mock_api.do_request.call_args[1]['files']

# Reset mock
self.mock_api.reset_mock()

# Get non-lazy loading result
self.upload_manifests.upload_manifest_files(
"test_org",
test_files,
use_lazy_loading=False
)
non_lazy_call_args = self.mock_api.do_request.call_args[1]['files']

# Compare key names - they should be identical
lazy_keys = [item[0] for item in lazy_call_args]
non_lazy_keys = [item[0] for item in non_lazy_call_args]

self.assertEqual(lazy_keys, non_lazy_keys)
self.assertEqual(non_lazy_keys, ['frontend/package.json', 'backend/package.json'])


if __name__ == '__main__':
unittest.main()
Loading