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:
find -name "*jardin.py" | entr -cd ./test_jardin.py

130
jardin.py
View File

@ -1,8 +1,15 @@
#!/usr/bin/python3
import re
import os
import sys
import bs4
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
def initialize_webdriver():
return webdriver.Firefox()
@ -29,15 +36,118 @@ def login_with_password(browser, email, password):
click_password_button(browser)
input_password(browser, password)
def main():
with initialize_webdriver() as browser:
navigate_to_filegarden(browser)
# Go To Your Garden
# Log in (email)
# Go To Your Garden
# Get list of filenames in File Garden
# 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
def click_go_to_your_garden(browser):
browser.implicitly_wait(100)
goToYourGardenButton = browser.find_element(By.LINK_TEXT, "GO TO YOUR GARDEN")
goToYourGardenButton.click()
def get_garden_filenames(browser):
titles = []
soup = bs4.BeautifulSoup(browser.page_source, "lxml")
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__":
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)
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.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,
mockInitializeWebdriver,
mockNavigateToFilegarden):
jardin.main()
mockUploadFiles,
mockGetMissingFilenames,
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()
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__":
unittest.main()