Skip to content

Commit 29bfa3a

Browse files
authored
Fixed logic for upload manifest files to not strip the folder names for the tash hash (#55)
1 parent 3595c3f commit 29bfa3a

File tree

4 files changed

+256
-3
lines changed

4 files changed

+256
-3
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "socketdev"
7-
version = "3.0.16"
7+
version = "3.0.17"
88
requires-python = ">= 3.9"
99
dependencies = [
1010
'requests',

socketdev/uploadmanifests/__init__.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,79 @@ class UploadManifests:
1010
def __init__(self, api):
1111
self.api = api
1212

13+
def _calculate_key_name(self, file_path: str, workspace: Optional[str] = None, base_path: Optional[str] = None, base_paths: Optional[List[str]] = None) -> str:
14+
"""
15+
Calculate the key name for a file using the same logic as load_files_for_sending_lazy.
16+
This ensures consistency between lazy and non-lazy loading modes.
17+
"""
18+
# Normalize file path
19+
if "\\" in file_path:
20+
file_path = file_path.replace("\\", "/")
21+
22+
# Normalize paths
23+
if workspace and "\\" in workspace:
24+
workspace = workspace.replace("\\", "/")
25+
if base_path and "\\" in base_path:
26+
base_path = base_path.replace("\\", "/")
27+
if base_paths:
28+
base_paths = [bp.replace("\\", "/") if "\\" in bp else bp for bp in base_paths]
29+
30+
# Calculate the key name for the form data
31+
key = file_path
32+
path_stripped = False
33+
34+
# If base_paths is provided, try to strip one of the paths from the file path
35+
if base_paths:
36+
for bp in base_paths:
37+
normalized_base_path = bp.rstrip("/") + "/" if not bp.endswith("/") else bp
38+
if key.startswith(normalized_base_path):
39+
key = key[len(normalized_base_path):]
40+
path_stripped = True
41+
break
42+
elif key.startswith(bp.rstrip("/")):
43+
stripped_base = bp.rstrip("/")
44+
if key.startswith(stripped_base + "/") or key == stripped_base:
45+
key = key[len(stripped_base):]
46+
key = key.lstrip("/")
47+
path_stripped = True
48+
break
49+
elif base_path:
50+
normalized_base_path = base_path.rstrip("/") + "/" if not base_path.endswith("/") else base_path
51+
if key.startswith(normalized_base_path):
52+
key = key[len(normalized_base_path):]
53+
path_stripped = True
54+
elif key.startswith(base_path.rstrip("/")):
55+
stripped_base = base_path.rstrip("/")
56+
if key.startswith(stripped_base + "/") or key == stripped_base:
57+
key = key[len(stripped_base):]
58+
key = key.lstrip("/")
59+
path_stripped = True
60+
61+
# If workspace is provided and no base paths matched, fall back to workspace logic
62+
if not path_stripped and workspace and file_path.startswith(workspace):
63+
key = file_path[len(workspace):]
64+
# Remove all leading slashes (for absolute paths)
65+
while key.startswith("/"):
66+
key = key[1:]
67+
path_stripped = True
68+
69+
# Clean up relative path prefixes, but preserve filename dots
70+
while key.startswith("./"):
71+
key = key[2:]
72+
while key.startswith("../"):
73+
key = key[3:]
74+
# Remove any remaining leading slashes (for absolute paths)
75+
while key.startswith("/"):
76+
key = key[1:]
77+
78+
# Remove Windows drive letter if present (C:/...)
79+
if len(key) > 2 and key[1] == ':' and (key[2] == '/' or key[2] == '\\'):
80+
key = key[2:]
81+
while key.startswith("/"):
82+
key = key[1:]
83+
84+
return key
85+
1386
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:
1487
"""
1588
Upload manifest files to Socket API and return tarHash.
@@ -46,7 +119,8 @@ def upload_manifest_files(self, org_slug: str, file_paths: List[str], workspace:
46119
# Fallback to basic file loading if needed
47120
loaded_files = []
48121
for file_path in valid_files:
49-
key = os.path.basename(file_path)
122+
# Use the same key generation logic as lazy loading for consistency
123+
key = self._calculate_key_name(file_path, workspace, base_path, base_paths)
50124
with open(file_path, 'rb') as f:
51125
loaded_files.append((key, (key, f.read())))
52126

socketdev/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "3.0.16"
1+
__version__ = "3.0.17"
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import os
2+
import tempfile
3+
import unittest
4+
from unittest.mock import Mock, patch, mock_open
5+
from socketdev.uploadmanifests import UploadManifests
6+
7+
8+
class TestUploadManifests(unittest.TestCase):
9+
def setUp(self):
10+
self.mock_api = Mock()
11+
self.upload_manifests = UploadManifests(self.mock_api)
12+
13+
def test_calculate_key_name_with_base_path(self):
14+
"""Test that key names are calculated correctly with base_path."""
15+
# Test with base_path
16+
key = self.upload_manifests._calculate_key_name(
17+
"/project/frontend/package.json",
18+
base_path="/project"
19+
)
20+
self.assertEqual(key, "frontend/package.json")
21+
22+
def test_calculate_key_name_with_workspace(self):
23+
"""Test that key names are calculated correctly with workspace."""
24+
# Test with workspace
25+
key = self.upload_manifests._calculate_key_name(
26+
"/project/frontend/package.json",
27+
workspace="/project/"
28+
)
29+
self.assertEqual(key, "frontend/package.json")
30+
31+
def test_calculate_key_name_with_base_paths(self):
32+
"""Test that key names are calculated correctly with base_paths."""
33+
# Test with base_paths (takes precedence over base_path)
34+
key = self.upload_manifests._calculate_key_name(
35+
"/project/frontend/package.json",
36+
base_path="/project",
37+
base_paths=["/different", "/project"]
38+
)
39+
self.assertEqual(key, "frontend/package.json")
40+
41+
def test_calculate_key_name_no_stripping(self):
42+
"""Test that key names default to basename when no stripping options provided."""
43+
# Test without any path stripping - should preserve relative path structure
44+
key = self.upload_manifests._calculate_key_name(
45+
"frontend/package.json"
46+
)
47+
self.assertEqual(key, "frontend/package.json")
48+
49+
def test_calculate_key_name_absolute_path_no_stripping(self):
50+
"""Test that absolute paths get cleaned up when no stripping options provided."""
51+
key = self.upload_manifests._calculate_key_name(
52+
"/absolute/path/frontend/package.json"
53+
)
54+
self.assertEqual(key, "absolute/path/frontend/package.json")
55+
56+
def test_calculate_key_name_windows_paths(self):
57+
"""Test that Windows paths are handled correctly."""
58+
key = self.upload_manifests._calculate_key_name(
59+
"C:\\project\\frontend\\package.json",
60+
base_path="C:\\project"
61+
)
62+
self.assertEqual(key, "frontend/package.json")
63+
64+
@patch('socketdev.uploadmanifests.Utils.load_files_for_sending_lazy')
65+
@patch('os.path.exists')
66+
@patch('os.path.isfile')
67+
def test_upload_manifest_files_lazy_loading(self, mock_isfile, mock_exists, mock_lazy_load):
68+
"""Test that lazy loading preserves key names correctly."""
69+
# Setup mocks
70+
mock_exists.return_value = True
71+
mock_isfile.return_value = True
72+
mock_lazy_load.return_value = [
73+
('frontend/package.json', ('frontend/package.json', Mock()))
74+
]
75+
mock_response = Mock()
76+
mock_response.status_code = 200
77+
mock_response.json.return_value = {'tarHash': 'test_hash'}
78+
self.mock_api.do_request.return_value = mock_response
79+
80+
# Test lazy loading
81+
result = self.upload_manifests.upload_manifest_files(
82+
"test_org",
83+
["/project/frontend/package.json"],
84+
workspace="/project",
85+
use_lazy_loading=True
86+
)
87+
88+
self.assertEqual(result, 'test_hash')
89+
mock_lazy_load.assert_called_once_with(
90+
["/project/frontend/package.json"],
91+
workspace="/project",
92+
base_path=None,
93+
base_paths=None
94+
)
95+
96+
@patch('builtins.open', new_callable=mock_open, read_data=b'{"name": "test"}')
97+
@patch('os.path.exists')
98+
@patch('os.path.isfile')
99+
def test_upload_manifest_files_non_lazy_loading(self, mock_isfile, mock_exists, mock_file):
100+
"""Test that non-lazy loading produces same key names as lazy loading."""
101+
# Setup mocks
102+
mock_exists.return_value = True
103+
mock_isfile.return_value = True
104+
mock_response = Mock()
105+
mock_response.status_code = 200
106+
mock_response.json.return_value = {'tarHash': 'test_hash'}
107+
self.mock_api.do_request.return_value = mock_response
108+
109+
# Test non-lazy loading with workspace
110+
result = self.upload_manifests.upload_manifest_files(
111+
"test_org",
112+
["frontend/package.json"],
113+
workspace="/project",
114+
use_lazy_loading=False
115+
)
116+
117+
self.assertEqual(result, 'test_hash')
118+
119+
# Verify the API was called with the correct file structure
120+
call_args = self.mock_api.do_request.call_args
121+
files_arg = call_args[1]['files']
122+
self.assertEqual(len(files_arg), 1)
123+
124+
# The key should be 'frontend/package.json' not just 'package.json'
125+
key, (filename, content) = files_arg[0]
126+
self.assertEqual(key, "frontend/package.json")
127+
self.assertEqual(filename, "frontend/package.json")
128+
129+
@patch('builtins.open', new_callable=mock_open, read_data=b'{"name": "test"}')
130+
@patch('os.path.exists')
131+
@patch('os.path.isfile')
132+
def test_upload_manifest_files_consistency_between_modes(self, mock_isfile, mock_exists, mock_file):
133+
"""Test that lazy and non-lazy loading produce identical key names."""
134+
# Setup mocks
135+
mock_exists.return_value = True
136+
mock_isfile.return_value = True
137+
mock_response = Mock()
138+
mock_response.status_code = 200
139+
mock_response.json.return_value = {'tarHash': 'test_hash'}
140+
self.mock_api.do_request.return_value = mock_response
141+
142+
test_files = ["frontend/package.json", "backend/package.json"]
143+
144+
# Test non-lazy loading
145+
with patch('socketdev.uploadmanifests.Utils.load_files_for_sending_lazy') as mock_lazy_load:
146+
mock_lazy_load.return_value = [
147+
('frontend/package.json', ('frontend/package.json', Mock())),
148+
('backend/package.json', ('backend/package.json', Mock()))
149+
]
150+
151+
# Get lazy loading result
152+
self.upload_manifests.upload_manifest_files(
153+
"test_org",
154+
test_files,
155+
use_lazy_loading=True
156+
)
157+
lazy_call_args = self.mock_api.do_request.call_args[1]['files']
158+
159+
# Reset mock
160+
self.mock_api.reset_mock()
161+
162+
# Get non-lazy loading result
163+
self.upload_manifests.upload_manifest_files(
164+
"test_org",
165+
test_files,
166+
use_lazy_loading=False
167+
)
168+
non_lazy_call_args = self.mock_api.do_request.call_args[1]['files']
169+
170+
# Compare key names - they should be identical
171+
lazy_keys = [item[0] for item in lazy_call_args]
172+
non_lazy_keys = [item[0] for item in non_lazy_call_args]
173+
174+
self.assertEqual(lazy_keys, non_lazy_keys)
175+
self.assertEqual(non_lazy_keys, ['frontend/package.json', 'backend/package.json'])
176+
177+
178+
if __name__ == '__main__':
179+
unittest.main()

0 commit comments

Comments
 (0)