@@ -490,8 +490,9 @@ def test_success_builds_move_to_folder_and_never_delete(self):
490490 client .record_verification (["List-Unsubscribe" ], 3 )
491491
492492 def responder (method , url , ** kw ):
493- if url .endswith ("/me/mailFolders?$top=100&$select=id,displayName" ):
494- return {"value" : [{"id" : "folder-123" , "displayName" : "Newsletters" }]}
493+ # Top-level folder match (no regression for non-nested targets).
494+ if url .split ("?" , 1 )[0 ].endswith ("/me/mailFolders" ):
495+ return {"value" : [{"id" : "folder-123" , "displayName" : "Newsletters" , "childFolderCount" : 0 }]}
495496 if method == "POST" :
496497 return {"id" : "rule-new" }
497498 return {}
@@ -520,6 +521,70 @@ def responder(method, url, **kw):
520521 # no DELETE on any message endpoint
521522 self .assertNotIn ("DELETE" , rec .methods ())
522523
524+ def test_success_resolves_move_to_folder_nested_under_inbox (self ):
525+ # feature 007: a folder nested under Inbox resolves to the nested folder's id.
526+ self ._sign_in ("Mail.Read MailboxSettings.ReadWrite offline_access" )
527+ client .record_verification (["List-Unsubscribe" ], 2 )
528+
529+ # Folder tree: Inbox (top) → Newsletters (nested). Served path-based, ignoring query.
530+ tree = {
531+ "/me/mailFolders" : [{"id" : "inbox-1" , "displayName" : "Inbox" , "childFolderCount" : 1 }],
532+ "/me/mailFolders/inbox-1/childFolders" : [
533+ {"id" : "nested-9" , "displayName" : "Newsletters" , "childFolderCount" : 0 }
534+ ],
535+ }
536+
537+ def responder (method , url , ** kw ):
538+ if method == "POST" :
539+ return {"id" : "rule-new" }
540+ base = url .split ("?" , 1 )[0 ]
541+ for path , value in tree .items ():
542+ if base .endswith (path ):
543+ return {"value" : value }
544+ return {"value" : []}
545+
546+ import contextlib
547+ import io
548+
549+ rec = _HttpRecorder (responder )
550+ runtime ._http = rec
551+ with contextlib .redirect_stdout (io .StringIO ()):
552+ self .assertEqual (
553+ client .cmd_rule_create (
554+ _Args (
555+ name = "Newsletters" ,
556+ header_contains = ["List-Unsubscribe" ],
557+ move_to_folder = "Newsletters" ,
558+ )
559+ ),
560+ 0 ,
561+ )
562+ post = next (c for c in rec .calls if c [0 ] == "POST" )
563+ self .assertEqual (post [3 ]["actions" ]["moveToFolder" ], "nested-9" )
564+
565+ def test_refuses_when_move_to_folder_name_exists_nowhere (self ):
566+ # feature 007: a name matching no folder at any depth still raises the steering error,
567+ # and no rule is POSTed.
568+ self ._sign_in ("Mail.Read MailboxSettings.ReadWrite offline_access" )
569+ client .record_verification (["List-Unsubscribe" ], 1 )
570+
571+ def responder (method , url , ** kw ):
572+ if url .split ("?" , 1 )[0 ].endswith ("/me/mailFolders" ):
573+ return {"value" : [{"id" : "f1" , "displayName" : "Inbox" , "childFolderCount" : 0 }]}
574+ return {"value" : []}
575+
576+ rec = _HttpRecorder (responder )
577+ runtime ._http = rec
578+ with self .assertRaises (client .SteerError ):
579+ client .cmd_rule_create (
580+ _Args (
581+ name = "X" ,
582+ header_contains = ["List-Unsubscribe" ],
583+ move_to_folder = "DoesNotExist" ,
584+ )
585+ )
586+ self .assertNotIn ("POST" , rec .methods ())
587+
523588
524589# ================================================================================================
525590# T032 — rule-remove (reversibility primitive)
0 commit comments