Repository: ftnext/diy-googledrive-operate

Issue:
コピーしたファイルが置かれるフォルダをidで指定できる
GoogleドライブのフォルダのURL中の文字列で指定する想定

Discussion:

Patch:
diff --git a/main.py b/main.py
index 04e6958..b4d2e18 100644
--- a/main.py
+++ b/main.py
@@ -9,7 +9,9 @@ from operate_drive.file import DiyGDriveFile
 def main():
     args = parse_args()
 
-    dest_file = cp_in_drive(args.source_id, args.dest_title)
+    dest_file = cp_in_drive(
+        args.source_id, args.dest_title, args.parent_dir_id
+    )
 
     info = display_information(dest_file)
     print(info)
@@ -19,17 +21,20 @@ def parse_args():
     parser = argparse.ArgumentParser()
     parser.add_argument("source_id")
     parser.add_argument("--dest_title")
+    parser.add_argument("--parent_dir_id")
     return parser.parse_args()
 
 
 def cp_in_drive(
-    source_id: str, dest_title: Optional[str] = None
+    source_id: str,
+    dest_title: Optional[str] = None,
+    parent_dir_id: Optional[str] = None,
 ) -> DiyGDriveFile:
     """Copy a specified file in Google Drive and return the created file."""
     drive = create_diy_gdrive()
     if dest_title is None:
         dest_title = build_dest_title(drive, source_id)
-    return drive.copy_file(source_id, dest_title)
+    return drive.copy_file(source_id, dest_title, parent_dir_id)
 
 
 def build_dest_title(drive: DiyGoogleDrive, source_id: str) -> str:
diff --git a/operate_drive/drive.py b/operate_drive/drive.py
index 1fa65ad..c8534dc 100644
--- a/operate_drive/drive.py
+++ b/operate_drive/drive.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from dataclasses import dataclass
 
 from pydrive2.drive import GoogleDrive
@@ -15,12 +17,16 @@ class DiyGoogleDrive:
         gdrive_file = self._drive.CreateFile(metadata)
         return DiyGDriveFile(gdrive_file)
 
-    def copy_file(self, source_id: str, dest_title: str) -> DiyGDriveFile:
+    def copy_file(
+        self, source_id: str, dest_title: str, parent_dir_id: str | None = None
+    ) -> DiyGDriveFile:
         if self._drive.auth.service is None:
             self._drive.GetAbout()  # Workaround to set auth (Issue #6)
 
         access_to_files = self._drive.auth.service.files()
         metadata = {"title": dest_title}
+        if parent_dir_id:
+            metadata["parents"] = [{"id": parent_dir_id}]
         request_to_copy = access_to_files.copy(
             fileId=source_id, body=metadata, supportsAllDrives=True
         )


Test patch:
diff --git a/tests/operate_drive/test_drive.py b/tests/operate_drive/test_drive.py
index 6d588ac..c7e7392 100644
--- a/tests/operate_drive/test_drive.py
+++ b/tests/operate_drive/test_drive.py
@@ -103,3 +103,26 @@ class DiyGoogleDriveCopyFileTestCase(TestCase):
         request_to_copy.execute.assert_called_once_with()
         fetch_file_by_id.assert_called_once_with(copied_file_info_dict["id"])
         self.assertEqual(actual, fetch_file_by_id.return_value)
+
+    def test_should_copy_under_specified_dir(self, fetch_file_by_id):
+        access_to_files = self.gdrive.auth.service.files.return_value
+        request_to_copy = access_to_files.copy.return_value
+        copied_file_info_dict = request_to_copy.execute.return_value
+        # bool(MagicMock(spec=str)) は False のため、実の文字列を使う
+        parent_dir_id = "1K##############################d"
+        metadata = {
+            "title": self.dest_title,
+            "parents": [{"id": parent_dir_id}],
+        }
+
+        actual = self.a_drive.copy_file(
+            self.source_id, self.dest_title, parent_dir_id
+        )
+
+        self.gdrive.auth.service.files.assert_called_once_with()
+        access_to_files.copy.assert_called_once_with(
+            fileId=self.source_id, body=metadata, supportsAllDrives=True
+        )
+        request_to_copy.execute.assert_called_once_with()
+        fetch_file_by_id.assert_called_once_with(copied_file_info_dict["id"])
+        self.assertEqual(actual, fetch_file_by_id.return_value)
diff --git a/tests/test_main.py b/tests/test_main.py
index d0c9998..d474575 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -21,7 +21,9 @@ class MainTestCase(TestCase):
         m.main()
 
         parse_args.assert_called_once_with()
-        cp_in_drive.assert_called_once_with(args.source_id, args.dest_title)
+        cp_in_drive.assert_called_once_with(
+            args.source_id, args.dest_title, args.parent_dir_id
+        )
         display_information.assert_called_once_with(dest_file)
         mock_print.assert_called_once_with(info)
 
@@ -35,7 +37,7 @@ class ParseArgsTestCase(TestCase):
 
         mock_argument_parser.assert_called_once_with()
         parser.add_argument.assert_has_calls(
-            [call("source_id"), call("--dest_title")]
+            [call("source_id"), call("--dest_title"), call("--parent_dir_id")]
         )
         parser.parse_args.assert_called_once_with()
         self.assertEqual(actual, parser.parse_args.return_value)
@@ -53,7 +55,7 @@ class CpInDriveTestCase(TestCase):
 
         create_diy_gdrive.assert_called_once_with()
         build_dest_title.assert_called_once_with(drive, source_id)
-        drive.copy_file.assert_called_once_with(source_id, dest_title)
+        drive.copy_file.assert_called_once_with(source_id, dest_title, None)
         self.assertEqual(actual, drive.copy_file.return_value)
 
     @patch("main.create_diy_gdrive")
@@ -66,7 +68,22 @@ class CpInDriveTestCase(TestCase):
 
         self.assertEqual(actual, drive.copy_file.return_value)
         create_diy_gdrive.assert_called_once_with()
-        drive.copy_file.assert_called_once_with(source_id, dest_title)
+        drive.copy_file.assert_called_once_with(source_id, dest_title, None)
+
+    @patch("main.create_diy_gdrive")
+    def test_should_copy_with_parent_dir_specified(self, create_diy_gdrive):
+        source_id = MagicMock(spec=str)
+        dest_title = MagicMock(spec=str)
+        parent_dir_id = MagicMock(spec=str)
+        drive = create_diy_gdrive.return_value
+
+        actual = m.cp_in_drive(source_id, dest_title, parent_dir_id)
+
+        self.assertEqual(actual, drive.copy_file.return_value)
+        create_diy_gdrive.assert_called_once_with()
+        drive.copy_file.assert_called_once_with(
+            source_id, dest_title, parent_dir_id
+        )
 
 
 class BuildDestTitleTestCase(TestCase):