"""数据字典模块测试。""" import sys from unittest.mock import patch, AsyncMock, MagicMock from httpx import AsyncClient from tests.conftest import auth_header from app.services.db_connector import QueryResult def _mock_query_result(columns, rows): """Helper: build a QueryResult for mocking execute_query.""" return QueryResult( columns=columns, rows=rows, total=len(rows), elapsed_ms=10, truncated=False, ) class TestListDatabases: async def test_list_databases_success( self, client: AsyncClient, test_instance, admin_token ): mock_result = _mock_query_result( ["Database"], [ {"Database": "test_db"}, {"Database": "app_db"}, {"Database": "information_schema"}, ], ) with patch( "app.services.dictionary_service.DBConnector.execute_query", new_callable=AsyncMock, return_value=mock_result, ): resp = await client.get( "/api/v1/dictionary/instances/" + str(test_instance.id) + "/databases", headers=auth_header(admin_token), ) assert resp.status_code == 200 data = resp.json() assert "databases" in data db_list = data["databases"] assert "test_db" in db_list assert "app_db" in db_list # system databases should be filtered assert "information_schema" not in db_list async def test_list_databases_not_found( self, client: AsyncClient, admin_token ): resp = await client.get( "/api/v1/dictionary/instances/99999/databases", headers=auth_header(admin_token), ) assert resp.status_code == 404 class TestListTables: async def test_list_tables_success( self, client: AsyncClient, test_instance, admin_token ): mock_result = _mock_query_result( ["TABLE_NAME"], [{"TABLE_NAME": "users"}, {"TABLE_NAME": "orders"}], ) with patch( "app.services.dictionary_service.DBConnector.execute_query", new_callable=AsyncMock, return_value=mock_result, ): resp = await client.get( "/api/v1/dictionary/instances/" + str(test_instance.id) + "/tables", params={"database": "test_db"}, headers=auth_header(admin_token), ) assert resp.status_code == 200 data = resp.json() assert "tables" in data assert "users" in data["tables"] assert "orders" in data["tables"] class TestGetTableDetail: async def test_table_detail_success( self, client: AsyncClient, test_instance, admin_token ): col_result = _mock_query_result( ["Field", "Type", "Null", "Key", "Default", "Extra", "Comment"], [ { "Field": "id", "Type": "int", "Null": "NO", "Key": "PRI", "Default": None, "Extra": "auto_increment", "Comment": "primary key", }, ], ) idx_result = _mock_query_result( ["Key_name", "Non_unique", "Index_type", "Column_name"], [ {"Key_name": "PRIMARY", "Non_unique": 0, "Index_type": "BTREE", "Column_name": "id"}, ], ) ddl_result = _mock_query_result( ["Table", "Create Table"], [{"Table": "users", "Create Table": "CREATE TABLE users (id int PRIMARY KEY)"}], ) cnt_result = _mock_query_result( ["TABLE_ROWS"], [{"TABLE_ROWS": 42}], ) call_count = 0 results = [col_result, idx_result, ddl_result, cnt_result] async def _mock_execute(*args, **kwargs): nonlocal call_count r = results[call_count] call_count += 1 return r with patch( "app.services.dictionary_service.DBConnector.execute_query", new=AsyncMock(side_effect=_mock_execute), ): resp = await client.get( "/api/v1/dictionary/instances/" + str(test_instance.id) + "/tables/users", params={"database": "test_db"}, headers=auth_header(admin_token), ) assert resp.status_code == 200 data = resp.json() assert "columns" in data assert len(data["columns"]) == 1 assert data["columns"][0]["Field"] == "id" assert "indexes" in data assert "create_ddl" in data assert "row_count" in data assert data["row_count"] == 42 class TestExportDictionary: async def test_export_success( self, client: AsyncClient, test_instance, admin_token ): # list_tables returns 1 table tbl_result = _mock_query_result( ["TABLE_NAME"], [{"TABLE_NAME": "users"}], ) # get_table_detail: columns col_result = _mock_query_result( ["Field", "Type", "Null", "Key", "Default", "Extra", "Comment"], [{"Field": "id", "Type": "int", "Null": "NO", "Key": "PRI", "Default": None, "Extra": "", "Comment": ""}], ) # indexes idx_result = _mock_query_result( ["Key_name", "Non_unique", "Index_type", "Column_name"], [{"Key_name": "PRIMARY", "Non_unique": 0, "Index_type": "BTREE", "Column_name": "id"}], ) # ddl ddl_result = _mock_query_result( ["Table", "Create Table"], [{"Table": "users", "Create Table": "CREATE TABLE users (id int)"}], ) # row count cnt_result = _mock_query_result( ["TABLE_ROWS"], [{"TABLE_ROWS": 10}], ) results = [tbl_result, col_result, idx_result, ddl_result, cnt_result] call_count = 0 async def _mock_execute(*args, **kwargs): nonlocal call_count r = results[call_count] call_count += 1 return r with patch( "app.services.dictionary_service.DBConnector.execute_query", new=AsyncMock(side_effect=_mock_execute), ): resp = await client.get( "/api/v1/dictionary/instances/" + str(test_instance.id) + "/export", params={"database": "test_db"}, headers=auth_header(admin_token), ) assert resp.status_code == 200 data = resp.json() assert "tables" in data assert len(data["tables"]) == 1 assert data["tables"][0]["table_name"] == "users" class TestPermission: async def test_guest_forbidden( self, client: AsyncClient, test_instance, guest_token ): resp = await client.get( "/api/v1/dictionary/instances/" + str(test_instance.id) + "/databases", headers=auth_header(guest_token), ) assert resp.status_code == 403 async def test_developer_allowed( self, client: AsyncClient, test_instance, developer_token ): mock_result = _mock_query_result(["Database"], []) with patch( "app.services.dictionary_service.DBConnector.execute_query", new_callable=AsyncMock, return_value=mock_result, ): resp = await client.get( "/api/v1/dictionary/instances/" + str(test_instance.id) + "/databases", headers=auth_header(developer_token), ) assert resp.status_code == 200