@@ -798,3 +798,153 @@ async def test_civitai_handles_empty_trained_words(self, tmp_path):
798798 )
799799
800800 assert config .trigger_words == []
801+
802+
803+ class TestLoraLoaderMixinStaticLoras :
804+ """Tests for LoraLoaderMixin static LoRA management methods."""
805+
806+ def test_set_static_loras_stores_configs (self ):
807+ """set_static_loras stores a copy of the configs."""
808+ from oneiro .pipelines .lora import LoraLoaderMixin
809+
810+ class TestMixin (LoraLoaderMixin ):
811+ pass
812+
813+ mixin = TestMixin ()
814+ loras = [
815+ LoraConfig (name = "lora1" , source = LoraSource .LOCAL , path = "/path/1" ),
816+ LoraConfig (name = "lora2" , source = LoraSource .LOCAL , path = "/path/2" ),
817+ ]
818+
819+ mixin .set_static_loras (loras )
820+
821+ assert mixin ._static_lora_configs == loras
822+ # Verify it's a copy, not the same list
823+ assert mixin ._static_lora_configs is not loras
824+
825+ def test_set_static_loras_empty_list (self ):
826+ """set_static_loras handles empty list."""
827+ from oneiro .pipelines .lora import LoraLoaderMixin
828+
829+ class TestMixin (LoraLoaderMixin ):
830+ pass
831+
832+ mixin = TestMixin ()
833+ mixin .set_static_loras ([])
834+
835+ assert mixin ._static_lora_configs == []
836+
837+ def test_restore_static_loras_no_op_when_empty (self ):
838+ """restore_static_loras is no-op when no static loras and no loaded adapters."""
839+ from oneiro .pipelines .lora import LoraLoaderMixin
840+
841+ class TestMixin (LoraLoaderMixin ):
842+ pass
843+
844+ mixin = TestMixin ()
845+ mixin .unload_loras = Mock ()
846+ mixin .load_loras_sync = Mock ()
847+ mixin .set_lora_adapters = Mock ()
848+
849+ mixin .restore_static_loras ()
850+
851+ mixin .unload_loras .assert_not_called ()
852+ mixin .load_loras_sync .assert_not_called ()
853+ mixin .set_lora_adapters .assert_not_called ()
854+
855+ def test_restore_static_loras_resets_weights_when_adapters_match (self ):
856+ """restore_static_loras only resets weights when adapters match static config."""
857+ from oneiro .pipelines .lora import LoraLoaderMixin
858+
859+ class TestMixin (LoraLoaderMixin ):
860+ pass
861+
862+ mixin = TestMixin ()
863+ loras = [
864+ LoraConfig (name = "lora1" , source = LoraSource .LOCAL , path = "/path/1" , weight = 0.8 ),
865+ LoraConfig (name = "lora2" , source = LoraSource .LOCAL , path = "/path/2" , weight = 0.5 ),
866+ ]
867+ mixin .set_static_loras (loras )
868+ mixin ._loaded_adapters = ["lora1" , "lora2" ]
869+
870+ mixin .unload_loras = Mock ()
871+ mixin .load_loras_sync = Mock ()
872+ mixin .set_lora_adapters = Mock ()
873+
874+ mixin .restore_static_loras ()
875+
876+ mixin .unload_loras .assert_not_called ()
877+ mixin .load_loras_sync .assert_not_called ()
878+ mixin .set_lora_adapters .assert_called_once_with (["lora1" , "lora2" ], [0.8 , 0.5 ])
879+
880+ def test_restore_static_loras_reloads_when_adapters_differ (self ):
881+ """restore_static_loras reloads when loaded adapters differ from static."""
882+ from oneiro .pipelines .lora import LoraLoaderMixin
883+
884+ class TestMixin (LoraLoaderMixin ):
885+ pass
886+
887+ mixin = TestMixin ()
888+ static_loras = [
889+ LoraConfig (name = "static-lora" , source = LoraSource .LOCAL , path = "/path/1" ),
890+ ]
891+ mixin .set_static_loras (static_loras )
892+ # Simulate dynamic lora was added
893+ mixin ._loaded_adapters = ["static-lora" , "dynamic-lora" ]
894+
895+ mixin .unload_loras = Mock ()
896+ mixin .load_loras_sync = Mock ()
897+ mixin .set_lora_adapters = Mock ()
898+
899+ mixin .restore_static_loras ()
900+
901+ mixin .unload_loras .assert_called_once ()
902+ mixin .load_loras_sync .assert_called_once_with (static_loras )
903+ mixin .set_lora_adapters .assert_not_called ()
904+
905+ def test_restore_static_loras_unloads_only_when_no_static (self ):
906+ """restore_static_loras unloads all when no static but has loaded adapters."""
907+ from oneiro .pipelines .lora import LoraLoaderMixin
908+
909+ class TestMixin (LoraLoaderMixin ):
910+ pass
911+
912+ mixin = TestMixin ()
913+ mixin ._loaded_adapters = ["dynamic-lora" ]
914+
915+ mixin .unload_loras = Mock ()
916+ mixin .load_loras_sync = Mock ()
917+ mixin .set_lora_adapters = Mock ()
918+
919+ mixin .restore_static_loras ()
920+
921+ mixin .unload_loras .assert_called_once ()
922+ mixin .load_loras_sync .assert_not_called ()
923+
924+ def test_restore_static_loras_uses_adapter_name_when_set (self ):
925+ """restore_static_loras uses adapter_name if specified, otherwise name."""
926+ from oneiro .pipelines .lora import LoraLoaderMixin
927+
928+ class TestMixin (LoraLoaderMixin ):
929+ pass
930+
931+ mixin = TestMixin ()
932+ loras = [
933+ LoraConfig (
934+ name = "lora1" ,
935+ source = LoraSource .LOCAL ,
936+ path = "/path/1" ,
937+ adapter_name = "custom_adapter" ,
938+ weight = 0.7 ,
939+ ),
940+ ]
941+ mixin .set_static_loras (loras )
942+ mixin ._loaded_adapters = ["custom_adapter" ]
943+
944+ mixin .unload_loras = Mock ()
945+ mixin .load_loras_sync = Mock ()
946+ mixin .set_lora_adapters = Mock ()
947+
948+ mixin .restore_static_loras ()
949+
950+ mixin .set_lora_adapters .assert_called_once_with (["custom_adapter" ], [0.7 ])
0 commit comments