"""Integration tests using the custom TestAssembly.dll fixture. These tests exercise the full stack including ilspycmd calls. Tests are skipped if ilspycmd is not installed. """ import pytest from mcilspy.ilspy_wrapper import ILSpyWrapper from mcilspy.metadata_reader import MetadataReader from mcilspy.models import ( DecompileRequest, EntityType, LanguageVersion, ListTypesRequest, ) class TestMetadataReaderWithTestAssembly: """Test MetadataReader against our custom test assembly.""" def test_get_assembly_metadata(self, test_assembly_path): """Test reading metadata from test assembly.""" with MetadataReader(test_assembly_path) as reader: meta = reader.get_assembly_metadata() assert meta.name == "TestAssemblyProject" assert meta.type_count > 0 assert meta.method_count > 0 def test_list_methods_finds_known_methods(self, test_assembly_path): """Test that we can find methods we know exist.""" with MetadataReader(test_assembly_path) as reader: methods = reader.list_methods() method_names = [m.name for m in methods] # Check for methods we defined in TestClass assert "DoSomething" in method_names assert "GetGreeting" in method_names assert "Add" in method_names def test_list_methods_with_type_filter(self, test_assembly_path): """Test filtering methods by type.""" with MetadataReader(test_assembly_path) as reader: methods = reader.list_methods(type_filter="TestClass") # All methods should be from types containing "TestClass" for method in methods: assert "TestClass" in method.declaring_type def test_list_methods_with_namespace_filter(self, test_assembly_path): """Test filtering methods by namespace.""" with MetadataReader(test_assembly_path) as reader: methods = reader.list_methods(namespace_filter="SubNamespace") # Should only find methods from SubNamespace for method in methods: assert method.namespace is not None assert "SubNamespace" in method.namespace def test_list_methods_public_only(self, test_assembly_path): """Test filtering for public methods only.""" with MetadataReader(test_assembly_path) as reader: public_methods = reader.list_methods(public_only=True) all_methods = reader.list_methods(public_only=False) # Should have fewer public methods than total assert len(public_methods) <= len(all_methods) # All returned methods should be public for method in public_methods: assert method.is_public def test_list_fields_finds_known_fields(self, test_assembly_path): """Test that we can find fields we defined.""" with MetadataReader(test_assembly_path) as reader: fields = reader.list_fields() field_names = [f.name for f in fields] # Check for constants and fields we defined assert "API_KEY" in field_names assert "BASE_URL" in field_names assert "MAX_RETRIES" in field_names def test_list_fields_constants_only(self, test_assembly_path): """Test filtering for constant fields only.""" with MetadataReader(test_assembly_path) as reader: constants = reader.list_fields(constants_only=True) # All returned fields should be literals for field in constants: assert field.is_literal const_names = [f.name for f in constants] assert "API_KEY" in const_names assert "MAX_RETRIES" in const_names def test_list_properties_finds_known_properties(self, test_assembly_path): """Test that we can find properties we defined.""" with MetadataReader(test_assembly_path) as reader: properties = reader.list_properties() prop_names = [p.name for p in properties] # Check for properties we defined assert "Name" in prop_names assert "Age" in prop_names assert "IsActive" in prop_names assert "ServiceName" in prop_names def test_list_events_finds_known_events(self, test_assembly_path): """Test that we can find events we defined.""" with MetadataReader(test_assembly_path) as reader: events = reader.list_events() event_names = [e.name for e in events] # Check for events we defined assert "OnChange" in event_names assert "OnMessage" in event_names def test_list_resources_empty_for_test_assembly(self, test_assembly_path): """Test that test assembly has no embedded resources.""" with MetadataReader(test_assembly_path) as reader: resources = reader.list_resources() # Our simple test assembly has no resources assert isinstance(resources, list) class TestILSpyWrapperWithTestAssembly: """Integration tests for ILSpyWrapper using real ilspycmd calls.""" @pytest.fixture def wrapper(self, skip_without_ilspycmd): """Get wrapper instance, skipping if ilspycmd not available.""" return ILSpyWrapper() @pytest.mark.asyncio async def test_list_types_finds_classes(self, wrapper, test_assembly_path): """Test listing classes from test assembly.""" request = ListTypesRequest( assembly_path=test_assembly_path, entity_types=[EntityType.CLASS], ) response = await wrapper.list_types(request) assert response.success assert response.total_count > 0 type_names = [t.name for t in response.types] assert "TestClass" in type_names assert "TestServiceImpl" in type_names assert "OuterClass" in type_names @pytest.mark.asyncio async def test_list_types_finds_interfaces(self, wrapper, test_assembly_path): """Test listing interfaces from test assembly.""" request = ListTypesRequest( assembly_path=test_assembly_path, entity_types=[EntityType.INTERFACE], ) response = await wrapper.list_types(request) assert response.success type_names = [t.name for t in response.types] assert "ITestService" in type_names assert "IConfigurable" in type_names @pytest.mark.asyncio async def test_list_types_finds_structs(self, wrapper, test_assembly_path): """Test listing structs from test assembly.""" request = ListTypesRequest( assembly_path=test_assembly_path, entity_types=[EntityType.STRUCT], ) response = await wrapper.list_types(request) assert response.success type_names = [t.name for t in response.types] assert "TestStruct" in type_names @pytest.mark.asyncio async def test_list_types_finds_enums(self, wrapper, test_assembly_path): """Test listing enums from test assembly.""" request = ListTypesRequest( assembly_path=test_assembly_path, entity_types=[EntityType.ENUM], ) response = await wrapper.list_types(request) assert response.success type_names = [t.name for t in response.types] assert "TestEnum" in type_names @pytest.mark.asyncio async def test_list_types_finds_delegates(self, wrapper, test_assembly_path): """Test listing delegates from test assembly.""" request = ListTypesRequest( assembly_path=test_assembly_path, entity_types=[EntityType.DELEGATE], ) response = await wrapper.list_types(request) assert response.success type_names = [t.name for t in response.types] assert "TestDelegate" in type_names @pytest.mark.asyncio async def test_decompile_specific_type(self, wrapper, test_assembly_path): """Test decompiling a specific type.""" request = DecompileRequest( assembly_path=test_assembly_path, type_name="TestNamespace.TestClass", language_version=LanguageVersion.LATEST, ) response = await wrapper.decompile(request) assert response.success assert response.source_code is not None # Check that decompiled code contains expected elements source = response.source_code assert "class TestClass" in source assert "DoSomething" in source assert "GetGreeting" in source @pytest.mark.asyncio async def test_decompile_entire_assembly(self, wrapper, test_assembly_path): """Test decompiling the entire assembly.""" request = DecompileRequest( assembly_path=test_assembly_path, language_version=LanguageVersion.LATEST, ) response = await wrapper.decompile(request) assert response.success assert response.source_code is not None # Check that all types are present source = response.source_code assert "TestClass" in source assert "ITestService" in source assert "TestStruct" in source assert "TestEnum" in source @pytest.mark.asyncio async def test_decompile_to_il(self, wrapper, test_assembly_path): """Test decompiling to IL code.""" request = DecompileRequest( assembly_path=test_assembly_path, type_name="TestNamespace.TestClass", show_il_code=True, ) response = await wrapper.decompile(request) assert response.success assert response.source_code is not None # IL code should contain IL-specific keywords source = response.source_code # IL typically shows .method, .field, etc. assert ".class" in source or "IL_" in source @pytest.mark.asyncio async def test_decompile_to_output_dir(self, wrapper, test_assembly_path, temp_output_dir): """Test decompiling to an output directory.""" request = DecompileRequest( assembly_path=test_assembly_path, output_dir=temp_output_dir, ) response = await wrapper.decompile(request) assert response.success assert response.output_path is not None @pytest.mark.asyncio async def test_decompile_with_project_structure( self, wrapper, test_assembly_path, temp_output_dir ): """Test decompiling with project structure.""" request = DecompileRequest( assembly_path=test_assembly_path, output_dir=temp_output_dir, create_project=True, ) response = await wrapper.decompile(request) assert response.success @pytest.mark.asyncio async def test_decompile_nonexistent_type(self, wrapper, test_assembly_path): """Test decompiling a type that doesn't exist.""" request = DecompileRequest( assembly_path=test_assembly_path, type_name="NonExistent.FakeClass", ) response = await wrapper.decompile(request) # Should still succeed but with empty or no matching output # The actual behavior depends on ilspycmd version assert response is not None class TestIntegrationEndToEnd: """End-to-end integration tests covering complete workflows.""" @pytest.mark.asyncio async def test_discover_and_decompile_workflow( self, skip_without_ilspycmd, test_assembly_path ): """Test the typical workflow: list types, then decompile specific one.""" wrapper = ILSpyWrapper() # Step 1: List all types list_request = ListTypesRequest( assembly_path=test_assembly_path, entity_types=[EntityType.CLASS], ) list_response = await wrapper.list_types(list_request) assert list_response.success assert len(list_response.types) > 0 # Step 2: Find TestServiceImpl service_type = None for t in list_response.types: if t.name == "TestServiceImpl": service_type = t break assert service_type is not None # Step 3: Decompile it decompile_request = DecompileRequest( assembly_path=test_assembly_path, type_name=service_type.full_name, ) decompile_response = await wrapper.decompile(decompile_request) assert decompile_response.success assert decompile_response.source_code is not None assert "TestServiceImpl" in decompile_response.source_code assert "ITestService" in decompile_response.source_code @pytest.mark.asyncio async def test_metadata_and_decompile_combined( self, skip_without_ilspycmd, test_assembly_path ): """Test using metadata reader and ILSpy wrapper together.""" # Use metadata reader for quick discovery with MetadataReader(test_assembly_path) as reader: methods = reader.list_methods(type_filter="TestClass") add_method = None for m in methods: if m.name == "Add": add_method = m break assert add_method is not None assert add_method.is_static # Use ILSpy for decompilation wrapper = ILSpyWrapper() request = DecompileRequest( assembly_path=test_assembly_path, type_name="TestNamespace.TestClass", ) response = await wrapper.decompile(request) assert response.success # Verify the static method is in the output assert "static" in response.source_code assert "Add" in response.source_code