ソースを参照

[importer] Refactor file operations to use username instead of filesystem object (#4195)

This commit refactors the importer module's file handling logic by replacing filesystem object (fs) parameters with username-based authentication for user-specific file operations. The changes improve security by ensuring operations are performed with proper user context and simplify the API by standardizing authentication patterns.

- Replaces fs parameter with username parameter in core file operations
- Updates API endpoints to use request.user.username instead of request.fs
- Modifies all test cases to align with the new parameter signature
Harsh Gupta 4 ヶ月 前
コミット
0df36ddfa3

+ 3 - 3
desktop/core/src/desktop/lib/importer/api.py

@@ -117,7 +117,7 @@ def guess_file_metadata(request: Request) -> Response:
   metadata_params = serializer.validated_data
 
   try:
-    metadata = operations.guess_file_metadata(data=metadata_params, fs=request.fs if metadata_params.import_type == "remote" else None)
+    metadata = operations.guess_file_metadata(data=metadata_params, username=request.user.username)
     return Response(metadata, status=status.HTTP_200_OK)
 
   except ValueError as e:
@@ -150,7 +150,7 @@ def preview_file(request: Request) -> Response:
   preview_params = serializer.validated_data
 
   try:
-    preview = operations.preview_file(data=preview_params, fs=request.fs if preview_params.import_type == "remote" else None)
+    preview = operations.preview_file(data=preview_params, username=request.user.username)
     return Response(preview, status=status.HTTP_200_OK)
 
   except ValueError as e:
@@ -188,7 +188,7 @@ def guess_file_header(request: Request) -> Response:
   header_params = serializer.validated_data
 
   try:
-    has_header = operations.guess_file_header(data=header_params, fs=request.fs if header_params.import_type == "remote" else None)
+    has_header = operations.guess_file_header(data=header_params, username=request.user.username)
     return Response({"has_header": has_header}, status=status.HTTP_200_OK)
 
   except ValueError as e:

+ 11 - 24
desktop/core/src/desktop/lib/importer/api_tests.py

@@ -103,13 +103,12 @@ class TestGuessFileMetadataAPI:
     request = APIRequestFactory().get("importer/file/guess_metadata/")
     request.user = MagicMock(username="test_user")
     request.query_params = {"file_path": "/path/to/test.csv", "import_type": "local"}
-    request.fs = None
 
     response = api.guess_file_metadata(request)
 
     assert response.status_code == status.HTTP_200_OK
     assert response.data == {"type": "csv", "field_separator": ",", "quote_char": '"', "record_separator": "\n"}
-    mock_guess_file_metadata.assert_called_once_with(data=mock_schema, fs=None)
+    mock_guess_file_metadata.assert_called_once_with(data=mock_schema, username="test_user")
 
   @patch("desktop.lib.importer.api.GuessFileMetadataSerializer")
   @patch("desktop.lib.importer.api.operations.guess_file_metadata")
@@ -127,13 +126,12 @@ class TestGuessFileMetadataAPI:
     request = APIRequestFactory().get("importer/file/guess_metadata/")
     request.user = MagicMock(username="test_user")
     request.query_params = {"file_path": "/path/to/test.xlsx", "import_type": "local"}
-    request.fs = None
 
     response = api.guess_file_metadata(request)
 
     assert response.status_code == status.HTTP_200_OK
     assert response.data == {"type": "excel", "sheet_names": ["Sheet1", "Sheet2"]}
-    mock_guess_file_metadata.assert_called_once_with(data=mock_schema, fs=None)
+    mock_guess_file_metadata.assert_called_once_with(data=mock_schema, username="test_user")
 
   @patch("desktop.lib.importer.api.GuessFileMetadataSerializer")
   @patch("desktop.lib.importer.api.operations.guess_file_metadata")
@@ -158,7 +156,7 @@ class TestGuessFileMetadataAPI:
 
     assert response.status_code == status.HTTP_200_OK
     assert response.data == {"type": "csv", "field_separator": ",", "quote_char": '"', "record_separator": "\n"}
-    mock_guess_file_metadata.assert_called_once_with(data=mock_schema, fs=mock_fs)
+    mock_guess_file_metadata.assert_called_once_with(data=mock_schema, username="test_user")
 
   @patch("desktop.lib.importer.api.GuessFileMetadataSerializer")
   def test_guess_file_metadata_invalid_data(self, mock_serializer_class):
@@ -190,7 +188,6 @@ class TestGuessFileMetadataAPI:
     request = APIRequestFactory().get("importer/file/guess_metadata/")
     request.user = MagicMock(username="test_user")
     request.query_params = {"file_path": "/path/to/test.csv", "import_type": "local"}
-    request.fs = None
 
     response = api.guess_file_metadata(request)
 
@@ -213,7 +210,6 @@ class TestGuessFileMetadataAPI:
     request = APIRequestFactory().get("importer/file/guess_metadata/")
     request.user = MagicMock(username="test_user")
     request.query_params = {"file_path": "/path/to/test.csv", "import_type": "local"}
-    request.fs = None
 
     response = api.guess_file_metadata(request)
 
@@ -249,13 +245,12 @@ class TestPreviewFileAPI:
     request = APIRequestFactory().get("importer/file/preview/")
     request.user = MagicMock(username="test_user")
     request.query_params = {"file_path": "/path/to/test.csv", "file_type": "csv", "import_type": "local"}
-    request.fs = None
 
     response = api.preview_file(request)
 
     assert response.status_code == status.HTTP_200_OK
     assert response.data == mock_preview_result
-    mock_preview_file.assert_called_once_with(data=mock_schema, fs=None)
+    mock_preview_file.assert_called_once_with(data=mock_schema, username="test_user")
 
   @patch("desktop.lib.importer.api.PreviewFileSerializer")
   @patch("desktop.lib.importer.api.operations.preview_file")
@@ -282,13 +277,12 @@ class TestPreviewFileAPI:
     request = APIRequestFactory().get("importer/file/preview/")
     request.user = MagicMock(username="test_user")
     request.query_params = {"file_path": "/path/to/test.xlsx", "file_type": "excel", "import_type": "local"}
-    request.fs = None
 
     response = api.preview_file(request)
 
     assert response.status_code == status.HTTP_200_OK
     assert response.data == mock_preview_result
-    mock_preview_file.assert_called_once_with(data=mock_schema, fs=None)
+    mock_preview_file.assert_called_once_with(data=mock_schema, username="test_user")
 
   @patch("desktop.lib.importer.api.PreviewFileSerializer")
   @patch("desktop.lib.importer.api.operations.preview_file")
@@ -317,13 +311,12 @@ class TestPreviewFileAPI:
     request = APIRequestFactory().get("importer/file/preview/")
     request.user = MagicMock(username="test_user")
     request.query_params = {"file_path": "/path/to/test.tsv", "file_type": "tsv", "import_type": "local"}
-    request.fs = None
 
     response = api.preview_file(request)
 
     assert response.status_code == status.HTTP_200_OK
     assert response.data == mock_preview_result
-    mock_preview_file.assert_called_once_with(data=mock_schema, fs=None)
+    mock_preview_file.assert_called_once_with(data=mock_schema, username="test_user")
 
   @patch("desktop.lib.importer.api.PreviewFileSerializer")
   @patch("desktop.lib.importer.api.operations.preview_file")
@@ -360,7 +353,7 @@ class TestPreviewFileAPI:
 
     assert response.status_code == status.HTTP_200_OK
     assert response.data == mock_preview_result
-    mock_preview_file.assert_called_once_with(data=mock_schema, fs=mock_fs)
+    mock_preview_file.assert_called_once_with(data=mock_schema, username="test_user")
 
   @patch("desktop.lib.importer.api.PreviewFileSerializer")
   def test_preview_file_invalid_data(self, mock_serializer_class):
@@ -412,7 +405,6 @@ class TestPreviewFileAPI:
     request = APIRequestFactory().get("importer/file/preview/")
     request.user = MagicMock(username="test_user")
     request.query_params = {"file_path": "/path/to/test.csv", "file_type": "csv", "import_type": "local"}
-    request.fs = None
 
     response = api.preview_file(request)
 
@@ -441,7 +433,6 @@ class TestPreviewFileAPI:
     request = APIRequestFactory().get("importer/file/preview/")
     request.user = MagicMock(username="test_user")
     request.query_params = {"file_path": "/path/to/test.csv", "file_type": "csv", "import_type": "local"}
-    request.fs = None
 
     response = api.preview_file(request)
 
@@ -468,13 +459,12 @@ class TestGuessFileHeaderAPI:
     request = APIRequestFactory().get("importer/file/guess_header/")
     request.user = MagicMock(username="test_user")
     request.query_params = {"file_path": "/path/to/test.csv", "file_type": "csv", "import_type": "local"}
-    request.fs = None
 
     response = api.guess_file_header(request)
 
     assert response.status_code == status.HTTP_200_OK
     assert response.data == {"has_header": True}
-    mock_guess_file_header.assert_called_once_with(data=mock_schema, fs=None)
+    mock_guess_file_header.assert_called_once_with(data=mock_schema, username="test_user")
 
   @patch("desktop.lib.importer.api.GuessFileHeaderSerializer")
   @patch("desktop.lib.importer.api.operations.guess_file_header")
@@ -494,13 +484,12 @@ class TestGuessFileHeaderAPI:
     request = APIRequestFactory().get("importer/file/guess_header/")
     request.user = MagicMock(username="test_user")
     request.query_params = {"file_path": "/path/to/test.xlsx", "file_type": "excel", "import_type": "local", "sheet_name": "Sheet1"}
-    request.fs = None
 
     response = api.guess_file_header(request)
 
     assert response.status_code == status.HTTP_200_OK
     assert response.data == {"has_header": True}
-    mock_guess_file_header.assert_called_once_with(data=mock_schema, fs=None)
+    mock_guess_file_header.assert_called_once_with(data=mock_schema, username="test_user")
 
   @patch("desktop.lib.importer.api.GuessFileHeaderSerializer")
   @patch("desktop.lib.importer.api.operations.guess_file_header")
@@ -527,7 +516,7 @@ class TestGuessFileHeaderAPI:
 
     assert response.status_code == status.HTTP_200_OK
     assert response.data == {"has_header": True}
-    mock_guess_file_header.assert_called_once_with(data=mock_schema, fs=mock_fs)
+    mock_guess_file_header.assert_called_once_with(data=mock_schema, username="test_user")
 
   @patch("desktop.lib.importer.api.GuessFileHeaderSerializer")
   @patch("desktop.lib.importer.api.operations.guess_file_header")
@@ -554,7 +543,7 @@ class TestGuessFileHeaderAPI:
 
     assert response.status_code == status.HTTP_200_OK
     assert response.data == {"has_header": False}
-    mock_guess_file_header.assert_called_once_with(data=mock_schema, fs=mock_fs)
+    mock_guess_file_header.assert_called_once_with(data=mock_schema, username="test_user")
 
   @patch("desktop.lib.importer.api.GuessFileHeaderSerializer")
   def test_guess_file_header_invalid_data(self, mock_serializer_class):
@@ -588,7 +577,6 @@ class TestGuessFileHeaderAPI:
     request = APIRequestFactory().get("importer/file/guess_header/")
     request.user = MagicMock(username="test_user")
     request.query_params = {"file_path": "/path/to/test.csv", "file_type": "csv", "import_type": "local"}
-    request.fs = None
 
     response = api.guess_file_header(request)
 
@@ -613,7 +601,6 @@ class TestGuessFileHeaderAPI:
     request = APIRequestFactory().get("importer/file/guess_header/")
     request.user = MagicMock(username="test_user")
     request.query_params = {"file_path": "/path/to/test.csv", "file_type": "csv", "import_type": "local"}
-    request.fs = None
 
     response = api.guess_file_header(request)
 

+ 25 - 18
desktop/core/src/desktop/lib/importer/operations.py

@@ -33,6 +33,7 @@ from desktop.lib.importer.schemas import (
   PreviewFileSchema,
   SqlTypeMapperSchema,
 )
+from filebrowser.utils import get_user_fs
 
 LOG = logging.getLogger()
 
@@ -165,12 +166,12 @@ def local_file_upload(data: LocalFileUploadSchema, username: str) -> Dict[str, s
     raise e
 
 
-def guess_file_metadata(data: GuessFileMetadataSchema, fs=None) -> Dict[str, Any]:
+def guess_file_metadata(data: GuessFileMetadataSchema, username: str) -> Dict[str, Any]:
   """Guess the metadata of a file based on its content or extension.
 
   Args:
     data: A Pydantic schema containing file_path and import_type.
-    fs: File system object for remote files (default: None)
+    username: The name of the user to impersonate for filesystem operations.
 
   Returns:
     Dict containing the file metadata:
@@ -184,17 +185,19 @@ def guess_file_metadata(data: GuessFileMetadataSchema, fs=None) -> Dict[str, Any
     ValueError: If the file does not exist or parameters are invalid
     Exception: For various file processing errors
   """
-  if data.import_type == "remote" and fs is None:
-    raise ValueError("File system object is required for remote import type")
+  if not username:
+    raise ValueError("Username is required and cannot be empty.")
+
+  fs = get_user_fs(username) if data.import_type == "remote" else None
 
   # Check if file exists based on import type
   if data.import_type == "local" and not os.path.exists(data.file_path):
     raise ValueError(f"Local file does not exist: {data.file_path}")
-  elif data.import_type == "remote" and fs and not fs.exists(data.file_path):
+  elif data.import_type == "remote" and not fs.exists(data.file_path):
     raise ValueError(f"Remote file does not exist: {data.file_path}")
 
   should_cleanup = False
-  fh = open(data.file_path, "rb") if data.import_type == "local" else fs.open(data.file_path, "rb")
+  fh = open(data.file_path, "rb") if data.import_type == "local" else fs.open(data.file_path, "r")
 
   try:
     sample = fh.read(16 * 1024)  # Read 16 KiB sample
@@ -227,7 +230,7 @@ def guess_file_metadata(data: GuessFileMetadataSchema, fs=None) -> Dict[str, Any
       os.remove(data.file_path)
 
 
-def preview_file(data: PreviewFileSchema, fs=None, preview_rows: int = 50) -> Dict[str, Any]:
+def preview_file(data: PreviewFileSchema, username: str, preview_rows: int = 50) -> Dict[str, Any]:
   """Generate a preview of a file's content with column type mapping.
 
   This method reads a file and returns a preview of its contents, along with
@@ -235,7 +238,7 @@ def preview_file(data: PreviewFileSchema, fs=None, preview_rows: int = 50) -> Di
 
   Args:
     data: A Pydantic schema with all the required parameters.
-    fs: File system object for remote files (default: None)
+    username: The name of the user to impersonate for filesystem operations.
     preview_rows: Number of rows to include in preview (default: 50)
 
   Returns:
@@ -248,17 +251,19 @@ def preview_file(data: PreviewFileSchema, fs=None, preview_rows: int = 50) -> Di
     ValueError: If the file does not exist or parameters are invalid
     Exception: For various file processing errors
   """
-  if data.import_type == "remote" and fs is None:
-    raise ValueError("File system object is required for remote import type")
+  if not username:
+    raise ValueError("Username is required and cannot be empty.")
+
+  fs = get_user_fs(username) if data.import_type == "remote" else None
 
   # Check if file exists based on import type
   if data.import_type == "local" and not os.path.exists(data.file_path):
     raise ValueError(f"Local file does not exist: {data.file_path}")
-  elif data.import_type == "remote" and fs and not fs.exists(data.file_path):
+  elif data.import_type == "remote" and not fs.exists(data.file_path):
     raise ValueError(f"Remote file does not exist: {data.file_path}")
 
   should_cleanup = False
-  fh = open(data.file_path, "rb") if data.import_type == "local" else fs.open(data.file_path, "rb")
+  fh = open(data.file_path, "rb") if data.import_type == "local" else fs.open(data.file_path, "r")
 
   try:
     if data.file_type == "excel":
@@ -542,7 +547,7 @@ def _preview_delimited_file(
     raise Exception(message)
 
 
-def guess_file_header(data: GuessFileHeaderSchema, fs=None) -> bool:
+def guess_file_header(data: GuessFileHeaderSchema, username: str) -> bool:
   """Guess whether a file has a header row.
 
   This function analyzes a file to determine if it contains a header row based on the
@@ -550,7 +555,7 @@ def guess_file_header(data: GuessFileHeaderSchema, fs=None) -> bool:
 
   Args:
     data: A Pydantic schema with all the required parameters.
-    fs: File system object for remote files (default: None)
+    username: The name of the user to impersonate for filesystem operations.
 
   Returns:
     has_header: Boolean indicating whether the file has a header row
@@ -559,16 +564,18 @@ def guess_file_header(data: GuessFileHeaderSchema, fs=None) -> bool:
     ValueError: If the file does not exist or parameters are invalid
     Exception: For various file processing errors
   """
-  if data.import_type == "remote" and fs is None:
-    raise ValueError("File system object is required for remote import type")
+  if not username:
+    raise ValueError("Username is required and cannot be empty.")
+
+  fs = get_user_fs(username) if data.import_type == "remote" else None
 
   # Check if file exists based on import type
   if data.import_type == "local" and not os.path.exists(data.file_path):
     raise ValueError(f"Local file does not exist: {data.file_path}")
-  elif data.import_type == "remote" and fs and not fs.exists(data.file_path):
+  elif data.import_type == "remote" and not fs.exists(data.file_path):
     raise ValueError(f"Remote file does not exist: {data.file_path}")
 
-  fh = open(data.file_path, "rb") if data.import_type == "local" else fs.open(data.file_path, "rb")
+  fh = open(data.file_path, "rb") if data.import_type == "local" else fs.open(data.file_path, "r")
 
   has_header = False
 

+ 25 - 25
desktop/core/src/desktop/lib/importer/operations_tests.py

@@ -184,7 +184,7 @@ class TestGuessFileMetadata:
     # Create schema object
     schema = GuessFileMetadataSchema(file_path=temp_file.name, import_type="local")
 
-    result = operations.guess_file_metadata(data=schema)
+    result = operations.guess_file_metadata(data=schema, username="test_user")
 
     assert result == {
       "type": "csv",
@@ -210,7 +210,7 @@ class TestGuessFileMetadata:
     # Create schema object
     schema = GuessFileMetadataSchema(file_path=temp_file.name, import_type="local")
 
-    result = operations.guess_file_metadata(data=schema)
+    result = operations.guess_file_metadata(data=schema, username="test_user")
 
     assert result == {
       "type": "tsv",
@@ -248,7 +248,7 @@ class TestGuessFileMetadata:
     # Create schema object
     schema = GuessFileMetadataSchema(file_path=temp_file.name, import_type="local")
 
-    result = operations.guess_file_metadata(data=schema)
+    result = operations.guess_file_metadata(data=schema, username="test_user")
 
     assert result == {
       "type": "excel",
@@ -271,21 +271,21 @@ class TestGuessFileMetadata:
     schema = GuessFileMetadataSchema(file_path=temp_file.name, import_type="local")
 
     with pytest.raises(ValueError, match="Unable to detect file format."):
-      operations.guess_file_metadata(data=schema)
+      operations.guess_file_metadata(data=schema, username="test_user")
 
   def test_guess_file_metadata_nonexistent_file(self):
     # Create schema object
     schema = GuessFileMetadataSchema(file_path="/path/to/nonexistent/file.csv", import_type="local")
 
     with pytest.raises(ValueError, match="Local file does not exist."):
-      operations.guess_file_metadata(data=schema)
+      operations.guess_file_metadata(data=schema, username="test_user")
 
-  def test_guess_remote_file_metadata_no_fs(self):
+  def test_guess_remote_file_metadata_no_username(self):
     # Create schema object
     schema = GuessFileMetadataSchema(file_path="s3a://bucket/user/test_user/test.csv", import_type="remote")
 
-    with pytest.raises(ValueError, match="File system object is required for remote import type"):
-      operations.guess_file_metadata(data=schema, fs=None)
+    with pytest.raises(ValueError, match="Username is required"):
+      operations.guess_file_metadata(data=schema, username="")
 
   def test_guess_file_metadata_empty_file(self, cleanup_temp_files):
     temp_file = tempfile.NamedTemporaryFile(delete=False)
@@ -297,7 +297,7 @@ class TestGuessFileMetadata:
     schema = GuessFileMetadataSchema(file_path=temp_file.name, import_type="local")
 
     with pytest.raises(ValueError, match="File is empty, cannot detect file format."):
-      operations.guess_file_metadata(data=schema)
+      operations.guess_file_metadata(data=schema, username="test_user")
 
   @patch("desktop.lib.importer.operations.is_magic_lib_available", False)
   def test_guess_file_metadata_no_magic_lib(self, cleanup_temp_files):
@@ -311,7 +311,7 @@ class TestGuessFileMetadata:
     schema = GuessFileMetadataSchema(file_path=temp_file.name, import_type="local")
 
     with pytest.raises(RuntimeError, match="Unable to guess file type. python-magic or its dependency libmagic is not installed."):
-      operations.guess_file_metadata(data=schema)
+      operations.guess_file_metadata(data=schema, username="test_user")
 
 
 @pytest.mark.usefixtures("cleanup_temp_files")
@@ -358,7 +358,7 @@ class TestPreviewFile:
       file_path=temp_file.name, file_type="excel", import_type="local", sql_dialect="hive", has_header=True, sheet_name="Sheet1"
     )
 
-    result = operations.preview_file(data=schema)
+    result = operations.preview_file(data=schema, username="test_user")
 
     assert result == {
       "type": "excel",
@@ -390,7 +390,7 @@ class TestPreviewFile:
       record_separator="\n",
     )
 
-    result = operations.preview_file(data=schema)
+    result = operations.preview_file(data=schema, username="test_user")
 
     assert result == {
       "type": "csv",
@@ -422,7 +422,7 @@ class TestPreviewFile:
       record_separator="\n",
     )
 
-    result = operations.preview_file(data=schema)
+    result = operations.preview_file(data=schema, username="test_user")
 
     assert result == {
       "type": "csv",
@@ -450,7 +450,7 @@ class TestPreviewFile:
       record_separator="\n",
     )
 
-    result = operations.preview_file(data=schema)
+    result = operations.preview_file(data=schema, username="test_user")
 
     assert result == {
       "type": "csv",
@@ -458,14 +458,14 @@ class TestPreviewFile:
       "preview_data": {},
     }
 
-  def test_preview_remote_file_no_fs(self):
+  def test_preview_remote_file_no_username(self):
     # Create schema object
     schema = PreviewFileSchema(
       file_path="s3a://bucket/user/test_user/test.csv", file_type="csv", import_type="remote", sql_dialect="hive", has_header=True
     )
 
-    with pytest.raises(ValueError, match="File system object is required for remote import type"):
-      operations.preview_file(data=schema, fs=None)
+    with pytest.raises(ValueError, match="Username is required"):
+      operations.preview_file(data=schema, username="")
 
   @patch("os.path.exists")
   def test_preview_nonexistent_local_file(self, mock_exists):
@@ -477,7 +477,7 @@ class TestPreviewFile:
     )
 
     with pytest.raises(ValueError, match="Local file does not exist: /path/to/nonexistent.csv"):
-      operations.preview_file(data=schema)
+      operations.preview_file(data=schema, username="test_user")
 
   def test_preview_trino_dialect_type_mapping(self, cleanup_temp_files):
     test_content = "string_col\nfoo\nbar"
@@ -497,7 +497,7 @@ class TestPreviewFile:
       field_separator=",",
     )
 
-    result = operations.preview_file(data=schema)
+    result = operations.preview_file(data=schema, username="test_user")
 
     # Check the result for Trino-specific type mapping
     assert result["columns"][0]["type"] == "VARCHAR"  # Not STRING
@@ -528,7 +528,7 @@ class TestGuessFileHeader:
     # Create schema object
     schema = GuessFileHeaderSchema(file_path=temp_file.name, file_type="csv", import_type="local")
 
-    result = operations.guess_file_header(data=schema)
+    result = operations.guess_file_header(data=schema, username="test_user")
 
     assert result
 
@@ -559,7 +559,7 @@ class TestGuessFileHeader:
     # Create schema object
     schema = GuessFileHeaderSchema(file_path=temp_file.name, file_type="excel", import_type="local", sheet_name="Sheet1")
 
-    result = operations.guess_file_header(data=schema)
+    result = operations.guess_file_header(data=schema, username="test_user")
 
     assert result
 
@@ -568,14 +568,14 @@ class TestGuessFileHeader:
     schema = GuessFileHeaderSchema(file_path="/path/to/nonexistent/file.csv", file_type="csv", import_type="local")
 
     with pytest.raises(ValueError, match="Local file does not exist"):
-      operations.guess_file_header(data=schema)
+      operations.guess_file_header(data=schema, username="test_user")
 
-  def test_guess_header_remote_file_no_fs(self):
+  def test_guess_header_remote_file_no_username(self):
     # Create schema object
     schema = GuessFileHeaderSchema(file_path="s3a://bucket/user/test_user/test.csv", file_type="csv", import_type="remote")
 
-    with pytest.raises(ValueError, match="File system object is required for remote import type"):
-      operations.guess_file_header(data=schema, fs=None)
+    with pytest.raises(ValueError, match="Username is required"):
+      operations.guess_file_header(data=schema, username="")
 
 
 class TestSqlTypeMapping: