@@ -399,3 +399,88 @@ def test_raises_when_path_does_not_exist(
399399 f"GET to { url } failed, got <Response [400]>: { invalid_path } does not exist"
400400 )
401401 assert str (e .value ) == expected_error_msg
402+
403+
404+ def test_tree_post_uploads_single_file (
405+ api_token , api_responses , base_url , home_dir_path , tmp_path
406+ ):
407+ (tmp_path / "index.html" ).write_bytes (b"<h1>hello</h1>" )
408+ remote_dir = f"{ home_dir_path } /myapp"
409+ remote_file_url = f"{ base_url } path{ remote_dir } /index.html"
410+ api_responses .add (responses .POST , url = remote_file_url , status = 201 )
411+
412+ Files ().tree_post (str (tmp_path ), remote_dir )
413+
414+ assert len (api_responses .calls ) == 1
415+ assert api_responses .calls [0 ].request .url == remote_file_url
416+
417+
418+ def test_tree_post_preserves_nested_structure (
419+ api_token , api_responses , base_url , home_dir_path , tmp_path
420+ ):
421+ (tmp_path / "index.html" ).write_bytes (b"<h1>hello</h1>" )
422+ (tmp_path / "static" / "css" ).mkdir (parents = True )
423+ (tmp_path / "static" / "css" / "style.css" ).write_bytes (b"body {}" )
424+ (tmp_path / "static" / "app.js" ).write_bytes (b"console.log('hi')" )
425+
426+ remote_dir = f"{ home_dir_path } /myapp"
427+ api_responses .add (responses .POST , url = f"{ base_url } path{ remote_dir } /index.html" , status = 201 )
428+ api_responses .add (responses .POST , url = f"{ base_url } path{ remote_dir } /static/app.js" , status = 201 )
429+ api_responses .add (responses .POST , url = f"{ base_url } path{ remote_dir } /static/css/style.css" , status = 201 )
430+
431+ Files ().tree_post (str (tmp_path ), remote_dir )
432+
433+ assert len (api_responses .calls ) == 3
434+ uploaded_urls = {call .request .url for call in api_responses .calls }
435+ assert uploaded_urls == {
436+ f"{ base_url } path{ remote_dir } /index.html" ,
437+ f"{ base_url } path{ remote_dir } /static/app.js" ,
438+ f"{ base_url } path{ remote_dir } /static/css/style.css" ,
439+ }
440+
441+
442+ def test_tree_post_creates_empty_directories (
443+ api_token , api_responses , base_url , home_dir_path , tmp_path
444+ ):
445+ (tmp_path / "empty_dir" ).mkdir ()
446+ remote_dir = f"{ home_dir_path } /myapp"
447+ empty_file_url = f"{ base_url } path{ remote_dir } /empty_dir/.empty"
448+ api_responses .add (responses .POST , url = empty_file_url , status = 201 )
449+ api_responses .add (responses .DELETE , url = empty_file_url , status = 204 )
450+
451+ Files ().tree_post (str (tmp_path ), remote_dir )
452+
453+ assert len (api_responses .calls ) == 2
454+ assert api_responses .calls [0 ].request .url == empty_file_url
455+ assert api_responses .calls [0 ].request .method == "POST"
456+ assert api_responses .calls [1 ].request .url == empty_file_url
457+ assert api_responses .calls [1 ].request .method == "DELETE"
458+
459+
460+ def test_tree_post_raises_when_path_is_not_a_directory (
461+ api_token , tmp_path
462+ ):
463+ file_path = tmp_path / "somefile.txt"
464+ file_path .write_bytes (b"content" )
465+
466+ with pytest .raises (ValueError ) as e :
467+ Files ().tree_post (str (file_path ), "/home/user/remote" )
468+
469+ assert "is not a directory" in str (e .value )
470+
471+
472+ def test_tree_post_fails_fast_on_upload_error (
473+ api_token , api_responses , base_url , home_dir_path , tmp_path
474+ ):
475+ (tmp_path / "a.txt" ).write_bytes (b"aaa" )
476+ (tmp_path / "b.txt" ).write_bytes (b"bbb" )
477+ (tmp_path / "c.txt" ).write_bytes (b"ccc" )
478+
479+ remote_dir = f"{ home_dir_path } /myapp"
480+ api_responses .add (responses .POST , url = f"{ base_url } path{ remote_dir } /a.txt" , status = 201 )
481+ api_responses .add (responses .POST , url = f"{ base_url } path{ remote_dir } /b.txt" , status = 403 )
482+
483+ with pytest .raises (PythonAnywhereApiException ):
484+ Files ().tree_post (str (tmp_path ), remote_dir )
485+
486+ assert len (api_responses .calls ) == 2
0 commit comments