@@ -862,3 +862,207 @@ async def test_pagination_stores_message(self, mock_dal_class, mock_ctx):
862862 call_kwargs = mock_ctx .send .call_args [1 ]
863863 view = call_kwargs ["view" ]
864864 assert view .message is sent_msg
865+
866+
867+ class TestEmbedPaginatorViewPersistence :
868+ """Test EmbedPaginatorView database persistence methods."""
869+
870+ def _make_pages (self , count = 3 ):
871+ return [discord .Embed (title = f"Page { i + 1 } " , description = f"Content { i + 1 } " ) for i in range (count )]
872+
873+ def test_embed_to_dict (self ):
874+ """Test _embed_to_dict converts embed to dict."""
875+ embed = discord .Embed (title = "Test" , description = "Desc" , color = discord .Color .green ())
876+ result = EmbedPaginatorView ._embed_to_dict (embed )
877+ assert isinstance (result , dict )
878+ assert result ["title" ] == "Test"
879+ assert result ["description" ] == "Desc"
880+
881+ def test_dict_to_embed (self ):
882+ """Test _dict_to_embed converts dict back to embed."""
883+ data = {"title" : "Test" , "description" : "Desc" , "color" : 0x00FF00 }
884+ result = EmbedPaginatorView ._dict_to_embed (data )
885+ assert isinstance (result , discord .Embed )
886+ assert result .title == "Test"
887+ assert result .description == "Desc"
888+
889+ def test_embed_roundtrip (self ):
890+ """Test embed -> dict -> embed preserves data."""
891+ original = discord .Embed (title = "Round" , description = "Trip" , color = discord .Color .red ())
892+ original .set_footer (text = "Footer" )
893+ data = EmbedPaginatorView ._embed_to_dict (original )
894+ restored = EmbedPaginatorView ._dict_to_embed (data )
895+ assert restored .title == original .title
896+ assert restored .description == original .description
897+ assert restored .footer .text == original .footer .text
898+
899+ @pytest .mark .asyncio
900+ @patch ("src.database.dal.bot.embed_pages_dal.EmbedPagesDal" )
901+ async def test_send_and_save (self , mock_dal_class ):
902+ """Test send_and_save sends message and saves to DB."""
903+ mock_dal = MagicMock ()
904+ mock_dal .insert_embed_pages = AsyncMock ()
905+ mock_dal_class .return_value = mock_dal
906+
907+ pages = self ._make_pages (2 )
908+ view = EmbedPaginatorView (pages , author_id = 42 )
909+
910+ ctx = MagicMock ()
911+ mock_msg = MagicMock ()
912+ mock_msg .id = 111
913+ mock_msg .channel .id = 222
914+ ctx .send = AsyncMock (return_value = mock_msg )
915+ ctx .bot .db_session = MagicMock ()
916+ ctx .bot .log = MagicMock ()
917+
918+ await view .send_and_save (ctx )
919+
920+ assert view .message is mock_msg
921+ ctx .send .assert_called_once ()
922+ mock_dal .insert_embed_pages .assert_awaited_once ()
923+ call_args = mock_dal .insert_embed_pages .call_args
924+ assert call_args [0 ][0 ] == 111 # message_id
925+ assert call_args [0 ][1 ] == 222 # channel_id
926+ assert call_args [0 ][2 ] == 42 # author_id
927+ assert len (call_args [0 ][3 ]) == 2 # pages data
928+
929+ @pytest .mark .asyncio
930+ @patch ("src.database.dal.bot.embed_pages_dal.EmbedPagesDal" )
931+ async def test_load_from_db_pages_already_set (self , mock_dal_class ):
932+ """Test _load_from_db returns True immediately when pages exist."""
933+ pages = self ._make_pages (2 )
934+ view = EmbedPaginatorView (pages , author_id = 42 )
935+ interaction = MagicMock ()
936+
937+ result = await view ._load_from_db (interaction )
938+
939+ assert result is True
940+ mock_dal_class .assert_not_called ()
941+
942+ @pytest .mark .asyncio
943+ @patch ("src.database.dal.bot.embed_pages_dal.EmbedPagesDal" )
944+ async def test_load_from_db_loads_from_database (self , mock_dal_class ):
945+ """Test _load_from_db loads pages from DB when not in memory."""
946+ mock_dal = MagicMock ()
947+ page_data = [{"title" : "P1" , "description" : "D1" }, {"title" : "P2" , "description" : "D2" }]
948+ mock_dal .get_embed_pages = AsyncMock (return_value = {
949+ "pages" : page_data ,
950+ "current_page" : 1 ,
951+ "author_id" : 42 ,
952+ })
953+ mock_dal_class .return_value = mock_dal
954+
955+ view = EmbedPaginatorView () # No pages
956+ interaction = MagicMock ()
957+ interaction .message .id = 111
958+ interaction .client .db_session = MagicMock ()
959+ interaction .client .log = MagicMock ()
960+
961+ result = await view ._load_from_db (interaction )
962+
963+ assert result is True
964+ assert len (view .pages ) == 2
965+ assert view .current_page == 1
966+ assert view .author_id == 42
967+
968+ @pytest .mark .asyncio
969+ @patch ("src.database.dal.bot.embed_pages_dal.EmbedPagesDal" )
970+ async def test_load_from_db_record_not_found (self , mock_dal_class ):
971+ """Test _load_from_db returns False when record not in DB."""
972+ mock_dal = MagicMock ()
973+ mock_dal .get_embed_pages = AsyncMock (return_value = None )
974+ mock_dal_class .return_value = mock_dal
975+
976+ view = EmbedPaginatorView () # No pages
977+ interaction = MagicMock ()
978+ interaction .message .id = 999
979+ interaction .client .db_session = MagicMock ()
980+ interaction .client .log = MagicMock ()
981+ interaction .response = AsyncMock ()
982+
983+ result = await view ._load_from_db (interaction )
984+
985+ assert result is False
986+ interaction .response .send_message .assert_called_once_with (
987+ "This pagination has expired." , ephemeral = True
988+ )
989+
990+ @pytest .mark .asyncio
991+ @patch ("src.database.dal.bot.embed_pages_dal.EmbedPagesDal" )
992+ async def test_save_current_page (self , mock_dal_class ):
993+ """Test _save_current_page updates DB."""
994+ mock_dal = MagicMock ()
995+ mock_dal .update_current_page = AsyncMock ()
996+ mock_dal_class .return_value = mock_dal
997+
998+ pages = self ._make_pages (3 )
999+ view = EmbedPaginatorView (pages , author_id = 42 )
1000+ view .current_page = 2
1001+
1002+ interaction = MagicMock ()
1003+ interaction .message .id = 111
1004+ interaction .client .db_session = MagicMock ()
1005+ interaction .client .log = MagicMock ()
1006+
1007+ await view ._save_current_page (interaction )
1008+
1009+ mock_dal .update_current_page .assert_awaited_once_with (111 , 2 )
1010+
1011+ def test_init_no_pages (self ):
1012+ """Test EmbedPaginatorView with no pages defaults."""
1013+ view = EmbedPaginatorView ()
1014+ assert view .pages == []
1015+ assert view .author_id == 0
1016+ assert view .page_indicator .label == "0/0"
1017+
1018+
1019+ class TestEmbedPagesDal :
1020+ """Test EmbedPagesDal class."""
1021+
1022+ @pytest .fixture
1023+ def mock_dal (self ):
1024+ db_session = MagicMock ()
1025+ log = MagicMock ()
1026+ with patch ("src.database.dal.bot.embed_pages_dal.DBUtilsAsync" ) as mock_db_utils_class :
1027+ mock_db_utils = MagicMock ()
1028+ mock_db_utils .insert = AsyncMock ()
1029+ mock_db_utils .execute = AsyncMock ()
1030+ mock_db_utils .fetchall = AsyncMock (return_value = [])
1031+ mock_db_utils_class .return_value = mock_db_utils
1032+ from src .database .dal .bot .embed_pages_dal import EmbedPagesDal
1033+ dal = EmbedPagesDal (db_session , log )
1034+ dal ._mock_db_utils = mock_db_utils
1035+ return dal
1036+
1037+ @pytest .mark .asyncio
1038+ async def test_insert_embed_pages (self , mock_dal ):
1039+ """Test insert_embed_pages calls db_utils.insert."""
1040+ pages = [{"title" : "P1" }]
1041+ await mock_dal .insert_embed_pages (111 , 222 , 42 , pages )
1042+ mock_dal ._mock_db_utils .insert .assert_awaited_once ()
1043+
1044+ @pytest .mark .asyncio
1045+ async def test_get_embed_pages_found (self , mock_dal ):
1046+ """Test get_embed_pages returns record when found."""
1047+ mock_dal ._mock_db_utils .fetchall = AsyncMock (return_value = [{"message_id" : 111 , "pages" : []}])
1048+ result = await mock_dal .get_embed_pages (111 )
1049+ assert result == {"message_id" : 111 , "pages" : []}
1050+
1051+ @pytest .mark .asyncio
1052+ async def test_get_embed_pages_not_found (self , mock_dal ):
1053+ """Test get_embed_pages returns None when not found."""
1054+ mock_dal ._mock_db_utils .fetchall = AsyncMock (return_value = [])
1055+ result = await mock_dal .get_embed_pages (999 )
1056+ assert result is None
1057+
1058+ @pytest .mark .asyncio
1059+ async def test_update_current_page (self , mock_dal ):
1060+ """Test update_current_page calls db_utils.execute."""
1061+ await mock_dal .update_current_page (111 , 2 )
1062+ mock_dal ._mock_db_utils .execute .assert_awaited_once ()
1063+
1064+ @pytest .mark .asyncio
1065+ async def test_delete_embed_pages (self , mock_dal ):
1066+ """Test delete_embed_pages calls db_utils.execute."""
1067+ await mock_dal .delete_embed_pages (111 )
1068+ mock_dal ._mock_db_utils .execute .assert_awaited_once ()
0 commit comments