Compare commits

...

10 Commits

Author SHA1 Message Date
The Magician 8597fd1b49 Add javascript to make upload request 2023-11-20 20:51:39 +00:00
The Magician 83db84091f Add file input to DOM to create File objects 2023-10-21 16:32:17 +01:00
The Magician 05211354ea Start process for uploading files 2023-10-21 15:09:23 +01:00
The Magician 853d2ecf69 Call get_missing_files 2023-10-20 20:07:23 +01:00
The Magician 07cd251aaa Write get_missing_filenames 2023-10-20 20:03:39 +01:00
The Magician dfa7bddf96 Take local directory name as cli argument 2023-10-20 18:40:40 +01:00
The Magician eac6deee1d Get files in local directory 2023-10-20 18:38:21 +01:00
The Magician 68d37afec7 Get list of filenames in File Garden 2023-10-20 18:32:49 +01:00
The Magician a60ca8eb52 Remove run target from Makefile 2023-10-20 12:53:09 +01:00
The Magician fd77e2075a Call login code 2023-10-20 12:52:58 +01:00
3 changed files with 271 additions and 16 deletions

View File

@ -1,4 +1,2 @@
run:
./jardin.py
test: test:
find -name "*jardin.py" | entr -cd ./test_jardin.py find -name "*jardin.py" | entr -cd ./test_jardin.py

130
jardin.py
View File

@ -1,8 +1,15 @@
#!/usr/bin/python3 #!/usr/bin/python3
import re
import os
import sys
import bs4
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
def initialize_webdriver(): def initialize_webdriver():
return webdriver.Firefox() return webdriver.Firefox()
@ -29,15 +36,118 @@ def login_with_password(browser, email, password):
click_password_button(browser) click_password_button(browser)
input_password(browser, password) input_password(browser, password)
def main(): def click_go_to_your_garden(browser):
with initialize_webdriver() as browser: browser.implicitly_wait(100)
navigate_to_filegarden(browser) goToYourGardenButton = browser.find_element(By.LINK_TEXT, "GO TO YOUR GARDEN")
# Go To Your Garden goToYourGardenButton.click()
# Log in (email)
# Go To Your Garden def get_garden_filenames(browser):
# Get list of filenames in File Garden titles = []
# Get list of files in target upload directory that don't exist in File Garden (test based on filename? size? file contents?) soup = bs4.BeautifulSoup(browser.page_source, "lxml")
# For each file in the second list, go through the file upload process
for item in soup.find_all(attrs={"class": "name"}):
titles.append(item.text)
return titles
def get_local_filenames(directory):
return os.listdir(directory)
def get_missing_filenames(localFiles, gardenFiles):
missingFiles = []
for localFile in localFiles:
if localFile not in gardenFiles:
missingFiles.append(localFile)
return missingFiles
def create_file_input(browser, userId):
javascript = """const fileInput = document.createElement("input");
fileInput.id = "upload";
fileInput.type = "file";
fileInput.multiple = true;
fileInput.addEventListener("change", () => {
for (const file of fileInput.files) {
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.responseType = "text";
xhr.open("POST", "/users/""" + userId + """/pipe", true);
const data = {
parent: null,
name: file.name
};
xhr.setRequestHeader("Content-Type", "application/octet-stream");
xhr.setRequestHeader("X-Data", encodeURI(JSON.stringify(data)));
xhr.send(file);
/*
Miro.request(
"POST",
"/users/""" + userId + """/pipe",
{"Content-Type": "application/octet-stream"},
file,
function(xhr) {
console.log("Started uploading " + file.name + "...");
xhr.addEventListener("readystatechange", function() {
if (xhr.readyState == XMLHttpRequest.OPENED) {
const data = {
parent: null,
name: file.name
};
xhr.setRequestHeader("Content-Type", "application/octet-stream");
xhr.setRequestHeader("X-Data", encodeURI(JSON.stringify(data)));
}
});
},
true)
.then(Miro.response(
function(xhr) {
console.log("Uploaded " + file.name);
}),
function(xhr, error) {
console.log("Failed to upload " + file.name + " due to error:");
console.log(error);
});
*/
}
fileInput.value = null;
});
document.body.appendChild(fileInput);
"""
browser.execute_script(javascript)
def get_user_id(browser):
url = browser.current_url
userId = re.search("[0-9a-f]{24}", url)[0]
return userId
def upload_files(browser, directory, missingFiles):
userId = get_user_id(browser)
create_file_input(browser, userId)
uploadInput = browser.find_element(By.ID, "upload")
for file in missingFiles:
uploadInput.send_keys(directory + file)
def main(email, password, directory):
browser = initialize_webdriver()
navigate_to_filegarden(browser)
login_with_password(browser, email, password)
click_go_to_your_garden(browser)
garden_filenames = get_garden_filenames(browser)
local_filenames = get_local_filenames(directory)
missing_files = get_missing_filenames(local_filenames, garden_filenames)
upload_files(browser, directory, missing_files)
#browser.close()
if __name__ == "__main__": if __name__ == "__main__":
main() main(sys.argv[1], sys.argv[2], sys.argv[3])

View File

@ -66,15 +66,162 @@ class TestJardin(unittest.TestCase):
mockClickPasswordButton.assert_called_once_with(mockFirefox) mockClickPasswordButton.assert_called_once_with(mockFirefox)
mockInputPassword.assert_called_once_with(mockFirefox, mockPassword) mockInputPassword.assert_called_once_with(mockFirefox, mockPassword)
@patch("jardin.webdriver.Firefox")
def test_click_go_to_your_garden_clicks_button(self, mockFirefox):
jardin.click_go_to_your_garden(mockFirefox)
mockFirefox.find_element.assert_called_with(By.LINK_TEXT, "GO TO YOUR GARDEN")
mockFirefox.find_element.return_value.click.assert_called_once()
@patch("jardin.webdriver.Firefox")
@patch("jardin.bs4.BeautifulSoup")
def test_get_garden_filenames_creates_beautifulsoup_and_finds_names(self, mockBeautifulSoup, mockFirefox):
jardin.get_garden_filenames(mockFirefox)
mockBeautifulSoup.assert_called_once_with(mockFirefox.page_source, "lxml")
mockBeautifulSoup.return_value.find_all.assert_called_once_with(attrs={"class": "name"})
@patch("jardin.webdriver.Firefox")
def test_get_garden_filenames_returns_filenames_correctly(self, mockFirefox):
mockFirefox.page_source = """
<html lang="en">
<body class="mdc-typography">
<div id="items" class="items">
<a class="item typeFile selected" draggable="false" ondragstart="return false;" href="https://file.garden/fakeid/file1.jpg">
<div class="cell icon">
<button class="mdc-icon-button material-icons" type="button">image</button>
</div>
<div class="cell name" title="file1.jpg">file1.jpg</div>
<div class="cell size" title="10 B">10 B</div>
<div class="cell type" title="image/jpeg">image/jpeg</div>
<div class="cell date" title="Wed Aug 30 2023 13:04:10 GMT+0100 (British Summer Time)">Aug 30 2023 13:04:10</div>
</a>
<a class="item typeFile" draggable="false" ondragstart="return false;" href="https://file.garden/fakeid/file2.jpg">
<div class="cell icon">
<button class="mdc-icon-button material-icons" type="button">image</button>
</div>
<div class="cell name" title="file2.jpg">file2.jpg</div>
<div class="cell size" title="20 B">20 B</div>
<div class="cell type" title="image/jpeg">image/jpeg</div>
<div class="cell date" title="Wed Jun 29 2022 17:37:14 GMT+0100 (British Summer Time)">Jun 29 2022 17:37:14</div>
</a>
</div>
</body>
</html>
"""
filenames = jardin.get_garden_filenames(mockFirefox)
self.assertEqual(filenames, ["file1.jpg", "file2.jpg"])
@patch("jardin.os.listdir")
def test_get_local_filenames_gets_filenames_in_local_directory(self, mockListdir):
mockDirectory = "/home/luser/gardenfiles/"
mockFiles = ["file1", "file2", "file3"]
mockListdir.return_value = mockFiles
localFiles = jardin.get_local_filenames(mockDirectory)
mockListdir.assert_called_once_with(mockDirectory)
self.assertEqual(localFiles, mockFiles)
def test_get_missing_filenames_returns_local_filenames_without_garden_filenames(self):
mockLocalFilenames = ["file1.jpg", "file2.jpg", "file3.jpg", "file4.jpg", "file5.jpg"]
mockGardenFilenames = ["file2.jpg", "file4.jpg", "file6.jpg", "file8.jpg", "file10.jpg"]
missingFilenames = jardin.get_missing_filenames(mockLocalFilenames, mockGardenFilenames)
self.assertEqual(missingFilenames, ["file1.jpg", "file3.jpg", "file5.jpg"])
@patch("jardin.webdriver.Firefox")
def test_create_file_input_runs_correct_script(self, mockFirefox):
expectedScript = """const fileInput = document.createElement("input");
fileInput.id = "upload";
fileInput.type = "file";
fileInput.multiple = true;
fileInput.addEventListener("change", () => {
for (const file of fileInput.files) {
console.log(file); // Debug
// Miro.request(file); // What I'll probably actually use (or wrapper method)
//addFile(file); // Original (token not accessible from minified code)
}
fileInput.value = null;
});
document.body.appendChild(fileInput);
"""
jardin.create_file_input(mockFirefox)
mockFirefox.execute_script.assert_called_once_with(expectedScript)
@patch("jardin.webdriver.Firefox")
def test_get_user_id_returns_user_id_from_url(self, mockFirefox):
mockUserId = "73927475b259be67f8cea98f"
mockUrl = f"https://filegarden.com/users/{mockUserId}/garden/#"
mockFirefox.current_url = mockUrl
userId = jardin.get_user_id(mockFirefox)
self.assertEqual(userId, mockUserId)
@patch("jardin.get_user_id")
@patch("jardin.create_file_input")
@patch("jardin.webdriver.Firefox")
def test_upload_files_calls_correct_setup_methods(self, mockFirefox, mockCreateFileInput, mockGetUserId):
jardin.upload_files(mockFirefox, "", [])
mockCreateFileInput.assert_called_once_with(mockFirefox)
mockGetUserId.assert_called_once_with(mockFirefox)
@patch("jardin.get_user_id")
@patch("jardin.webdriver.Firefox")
def test_upload_files_calls_upload_file_once_per_file(self, mockFirefox, mockGetUserId):
mockGetUserId.return_value = "userid"
mockFiles = ["file1.jpg", "file2.jpg", "file3.jpg"]
mockDirectory = "/home/luser/gardenfiles/"
jardin.upload_files(mockFirefox, mockDirectory, mockFiles)
mockFirefox.find_element.return_value.send_keys.assert_has_calls([
call(mockDirectory + "file1.jpg"),
call(mockDirectory + "file2.jpg"),
call(mockDirectory + "file3.jpg")
])
@patch("jardin.initialize_webdriver") @patch("jardin.initialize_webdriver")
@patch("jardin.navigate_to_filegarden") @patch("jardin.navigate_to_filegarden")
@patch("jardin.login_with_password")
@patch("jardin.click_go_to_your_garden")
@patch("jardin.get_garden_filenames")
@patch("jardin.get_local_filenames")
@patch("jardin.get_missing_filenames")
@patch("jardin.upload_files")
def test_main_calls_methods_in_correct_order(self, def test_main_calls_methods_in_correct_order(self,
mockInitializeWebdriver, mockUploadFiles,
mockNavigateToFilegarden): mockGetMissingFilenames,
jardin.main() mockGetLocalFilenames,
mockGetGardenFilenames,
mockClickGoToYourGarden,
mockLoginWithPassword,
mockNavigateToFilegarden,
mockInitializeWebdriver):
mockEmail = "email@mail.com"
mockPassword = "p4$$w0rd"
mockDirectory = "/home/luser/gardenfiles/"
jardin.main(mockEmail, mockPassword, mockDirectory)
mockInitializeWebdriver.assert_called_once() mockInitializeWebdriver.assert_called_once()
mockNavigateToFilegarden.assert_called_once() mockNavigateToFilegarden.assert_called_once_with(mockInitializeWebdriver.return_value)
mockLoginWithPassword.assert_called_once_with(mockInitializeWebdriver.return_value, mockEmail, mockPassword)
mockClickGoToYourGarden.assert_called_once_with(mockInitializeWebdriver.return_value)
mockGetGardenFilenames.assert_called_once_with(mockInitializeWebdriver.return_value)
mockGetLocalFilenames.assert_called_once_with(mockDirectory)
mockGetMissingFilenames.assert_called_once_with(mockGetLocalFilenames.return_value, mockGetGardenFilenames.return_value)
mockUploadFiles.assert_called_once_with(mockInitializeWebdriver.return_value, mockDirectory, mockGetMissingFilenames.return_value)
#mockInitializeWebdriver.return_value.close.assert_called_once()
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()