test: add unit tests for extract_filament_usage_from_3mf function

- Valid filament extraction (single/multiple)
- Plate-specific filtering
- Error handling (invalid file, missing slice_info)
- Missing/invalid field handling
This commit is contained in:
AneoPsy
2026-02-14 14:49:32 -10:00
parent aa684b4b36
commit e21283de1c
+161
View File
@@ -4,15 +4,27 @@ Tests G-code parsing, filament length-to-weight conversion,
and cumulative layer usage lookup.
"""
import io
import math
import zipfile
from backend.app.utils.threemf_tools import (
extract_filament_usage_from_3mf,
get_cumulative_usage_at_layer,
mm_to_grams,
parse_gcode_layer_filament_usage,
)
def create_mock_3mf(slice_info_content: str) -> io.BytesIO:
"""Create a mock 3MF file (ZIP) with slice_info.config content."""
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, "w") as zf:
zf.writestr("Metadata/slice_info.config", slice_info_content)
buffer.seek(0)
return buffer
class TestParseGcodeLayerFilamentUsage:
"""Tests for parse_gcode_layer_filament_usage()."""
@@ -247,3 +259,152 @@ class TestGetCumulativeUsageAtLayer:
"""Target layer 0."""
data = {0: {0: 10.0}, 1: {0: 20.0}}
assert get_cumulative_usage_at_layer(data, 0) == {0: 10.0}
class TestExtractFilamentUsageFrom3mf:
"""Tests for extract_filament_usage_from_3mf function."""
def test_extract_single_filament(self, tmp_path):
"""Test extracting a single filament."""
xml_content = """<?xml version="1.0" encoding="UTF-8"?>
<config>
<filament id="1" used_g="50.5" type="PLA" color="#FF0000"/>
</config>
"""
mock_3mf = create_mock_3mf(xml_content)
file_path = tmp_path / "test.3mf"
file_path.write_bytes(mock_3mf.read())
result = extract_filament_usage_from_3mf(file_path)
assert len(result) == 1
assert result[0]["slot_id"] == 1
assert result[0]["used_g"] == 50.5
assert result[0]["type"] == "PLA"
assert result[0]["color"] == "#FF0000"
def test_extract_multiple_filaments(self, tmp_path):
"""Test extracting multiple filaments."""
xml_content = """<?xml version="1.0" encoding="UTF-8"?>
<config>
<filament id="1" used_g="50.5" type="PLA" color="#FF0000"/>
<filament id="2" used_g="30.2" type="PETG" color="#00FF00"/>
<filament id="3" used_g="10.0" type="ABS" color="#0000FF"/>
</config>
"""
mock_3mf = create_mock_3mf(xml_content)
file_path = tmp_path / "test.3mf"
file_path.write_bytes(mock_3mf.read())
result = extract_filament_usage_from_3mf(file_path)
assert len(result) == 3
assert result[0]["slot_id"] == 1
assert result[1]["slot_id"] == 2
assert result[2]["slot_id"] == 3
def test_extract_filament_with_plate_id(self, tmp_path):
"""Test extracting filament for a specific plate."""
xml_content = """<?xml version="1.0" encoding="UTF-8"?>
<config>
<plate>
<metadata key="index" value="1"/>
<filament id="1" used_g="25.0" type="PLA" color="#FF0000"/>
</plate>
<plate>
<metadata key="index" value="2"/>
<filament id="1" used_g="75.0" type="PETG" color="#00FF00"/>
</plate>
</config>
"""
mock_3mf = create_mock_3mf(xml_content)
file_path = tmp_path / "test.3mf"
file_path.write_bytes(mock_3mf.read())
result = extract_filament_usage_from_3mf(file_path, plate_id=2)
assert len(result) == 1
assert result[0]["used_g"] == 75.0
assert result[0]["type"] == "PETG"
def test_missing_slice_info_returns_empty(self, tmp_path):
"""Test that missing slice_info.config returns empty list."""
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, "w") as zf:
zf.writestr("other_file.txt", "content")
buffer.seek(0)
file_path = tmp_path / "test.3mf"
file_path.write_bytes(buffer.read())
result = extract_filament_usage_from_3mf(file_path)
assert result == []
def test_invalid_file_returns_empty(self, tmp_path):
"""Test that invalid file returns empty list."""
file_path = tmp_path / "invalid.3mf"
file_path.write_text("not a zip file")
result = extract_filament_usage_from_3mf(file_path)
assert result == []
def test_nonexistent_file_returns_empty(self, tmp_path):
"""Test that nonexistent file returns empty list."""
file_path = tmp_path / "nonexistent.3mf"
result = extract_filament_usage_from_3mf(file_path)
assert result == []
def test_filament_without_id_is_skipped(self, tmp_path):
"""Test that filament without id is skipped."""
xml_content = """<?xml version="1.0" encoding="UTF-8"?>
<config>
<filament used_g="50.5" type="PLA" color="#FF0000"/>
<filament id="2" used_g="30.0" type="PETG" color="#00FF00"/>
</config>
"""
mock_3mf = create_mock_3mf(xml_content)
file_path = tmp_path / "test.3mf"
file_path.write_bytes(mock_3mf.read())
result = extract_filament_usage_from_3mf(file_path)
assert len(result) == 1
assert result[0]["slot_id"] == 2
def test_invalid_used_g_is_skipped(self, tmp_path):
"""Test that filament with invalid used_g is skipped."""
xml_content = """<?xml version="1.0" encoding="UTF-8"?>
<config>
<filament id="1" used_g="invalid" type="PLA" color="#FF0000"/>
<filament id="2" used_g="30.0" type="PETG" color="#00FF00"/>
</config>
"""
mock_3mf = create_mock_3mf(xml_content)
file_path = tmp_path / "test.3mf"
file_path.write_bytes(mock_3mf.read())
result = extract_filament_usage_from_3mf(file_path)
assert len(result) == 1
assert result[0]["slot_id"] == 2
def test_missing_optional_fields(self, tmp_path):
"""Test that missing type and color default to empty string."""
xml_content = """<?xml version="1.0" encoding="UTF-8"?>
<config>
<filament id="1" used_g="50.5"/>
</config>
"""
mock_3mf = create_mock_3mf(xml_content)
file_path = tmp_path / "test.3mf"
file_path.write_bytes(mock_3mf.read())
result = extract_filament_usage_from_3mf(file_path)
assert len(result) == 1
assert result[0]["type"] == ""
assert result[0]["color"] == ""