Compare commits

..

No commits in common. "8597fd1b495db7f0ecd29d3e870d70d5ac17d2cd" and "c4e08faa168b2313866dd3535b19733a7a8c4efb" have entirely different histories.

3 changed files with 16 additions and 271 deletions

View File

@ -1,2 +1,4 @@
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,15 +1,8 @@
#!/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()
@ -36,118 +29,15 @@ def login_with_password(browser, email, password):
click_password_button(browser) click_password_button(browser)
input_password(browser, password) input_password(browser, password)
def click_go_to_your_garden(browser): def main():
browser.implicitly_wait(100) with initialize_webdriver() as browser:
goToYourGardenButton = browser.find_element(By.LINK_TEXT, "GO TO YOUR GARDEN") navigate_to_filegarden(browser)
goToYourGardenButton.click() # Go To Your Garden
# Log in (email)
def get_garden_filenames(browser): # Go To Your Garden
titles = [] # Get list of filenames in File Garden
soup = bs4.BeautifulSoup(browser.page_source, "lxml") # Get list of files in target upload directory that don't exist in File Garden (test based on filename? size? file contents?)
# 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(sys.argv[1], sys.argv[2], sys.argv[3]) main()

View File

@ -66,162 +66,15 @@ 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,
mockUploadFiles, mockInitializeWebdriver,
mockGetMissingFilenames, mockNavigateToFilegarden):
mockGetLocalFilenames, jardin.main()
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_with(mockInitializeWebdriver.return_value) mockNavigateToFilegarden.assert_called_once()
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()