# -*- coding: utf-8 -*- """Unit tests for workcenter mapping in filter_cache module. Tests workcenter group lookup and mapping functionality. """ import importlib import pytest from unittest.mock import patch, MagicMock import pandas as pd class TestGetWorkcenterGroup: """Test get_workcenter_group function.""" @pytest.fixture(autouse=True) def reset_cache(self): """Reset cache state before each test.""" import mes_dashboard.services.filter_cache as fc with fc._CACHE_LOCK: fc._CACHE['workcenter_groups'] = None fc._CACHE['workcenter_mapping'] = None fc._CACHE['workcenter_to_short'] = None fc._CACHE['last_refresh'] = None fc._CACHE['is_loading'] = False yield with fc._CACHE_LOCK: fc._CACHE['workcenter_groups'] = None fc._CACHE['workcenter_mapping'] = None fc._CACHE['workcenter_to_short'] = None fc._CACHE['last_refresh'] = None fc._CACHE['is_loading'] = False def test_returns_group_for_valid_workcenter(self): """Test returns group for valid workcenter name.""" import mes_dashboard.services.filter_cache as fc mock_mapping = { 'DB-01': {'group': '焊接', 'sequence': 1}, 'WB-01': {'group': '焊線', 'sequence': 2}, } with patch.object(fc, 'get_workcenter_mapping', return_value=mock_mapping): result = fc.get_workcenter_group('DB-01') assert result == '焊接' def test_returns_none_for_unknown_workcenter(self): """Test returns None for unknown workcenter name.""" import mes_dashboard.services.filter_cache as fc mock_mapping = { 'DB-01': {'group': '焊接', 'sequence': 1}, } with patch.object(fc, 'get_workcenter_mapping', return_value=mock_mapping): result = fc.get_workcenter_group('UNKNOWN') assert result is None def test_returns_none_when_mapping_unavailable(self): """Test returns None when mapping is unavailable.""" import mes_dashboard.services.filter_cache as fc with patch.object(fc, 'get_workcenter_mapping', return_value=None): result = fc.get_workcenter_group('DB-01') assert result is None class TestGetWorkcenterShort: """Test get_workcenter_short function.""" @pytest.fixture(autouse=True) def reset_cache(self): """Reset cache state before each test.""" import mes_dashboard.services.filter_cache as fc with fc._CACHE_LOCK: fc._CACHE['workcenter_groups'] = None fc._CACHE['workcenter_mapping'] = None fc._CACHE['workcenter_to_short'] = None fc._CACHE['last_refresh'] = None fc._CACHE['is_loading'] = False yield with fc._CACHE_LOCK: fc._CACHE['workcenter_groups'] = None fc._CACHE['workcenter_mapping'] = None fc._CACHE['workcenter_to_short'] = None fc._CACHE['last_refresh'] = None fc._CACHE['is_loading'] = False def test_returns_short_name_for_valid_workcenter(self): """Test returns short name for valid workcenter.""" import mes_dashboard.services.filter_cache as fc from datetime import datetime # Set up cache directly with fc._CACHE_LOCK: fc._CACHE['workcenter_to_short'] = { 'DB-01': 'DB', 'WB-01': 'WB', } fc._CACHE['workcenter_groups'] = [{'name': '焊接', 'sequence': 1}] fc._CACHE['workcenter_mapping'] = {} fc._CACHE['last_refresh'] = datetime.now() result = fc.get_workcenter_short('DB-01') assert result == 'DB' def test_returns_none_for_unknown_workcenter(self): """Test returns None for unknown workcenter.""" import mes_dashboard.services.filter_cache as fc from datetime import datetime with fc._CACHE_LOCK: fc._CACHE['workcenter_to_short'] = { 'DB-01': 'DB', } fc._CACHE['workcenter_groups'] = [{'name': '焊接', 'sequence': 1}] fc._CACHE['workcenter_mapping'] = {} fc._CACHE['last_refresh'] = datetime.now() result = fc.get_workcenter_short('UNKNOWN') assert result is None class TestGetWorkcentersByGroup: """Test get_workcenters_by_group function.""" def test_returns_workcenters_in_group(self): """Test returns all workcenters in specified group.""" import mes_dashboard.services.filter_cache as fc mock_mapping = { 'DB-01': {'group': '焊接', 'sequence': 1}, 'DB-02': {'group': '焊接', 'sequence': 1}, 'WB-01': {'group': '焊線', 'sequence': 2}, } with patch.object(fc, 'get_workcenter_mapping', return_value=mock_mapping): result = fc.get_workcenters_by_group('焊接') assert len(result) == 2 assert 'DB-01' in result assert 'DB-02' in result assert 'WB-01' not in result def test_returns_empty_for_unknown_group(self): """Test returns empty list for unknown group.""" import mes_dashboard.services.filter_cache as fc mock_mapping = { 'DB-01': {'group': '焊接', 'sequence': 1}, } with patch.object(fc, 'get_workcenter_mapping', return_value=mock_mapping): result = fc.get_workcenters_by_group('UNKNOWN') assert result == [] def test_returns_empty_when_mapping_unavailable(self): """Test returns empty list when mapping unavailable.""" import mes_dashboard.services.filter_cache as fc with patch.object(fc, 'get_workcenter_mapping', return_value=None): result = fc.get_workcenters_by_group('焊接') assert result == [] class TestGetWorkcentersForGroups: """Test get_workcenters_for_groups function.""" def test_returns_workcenters_for_multiple_groups(self): """Test returns workcenters for multiple groups.""" import mes_dashboard.services.filter_cache as fc mock_mapping = { 'DB-01': {'group': '焊接', 'sequence': 1}, 'WB-01': {'group': '焊線', 'sequence': 2}, 'MD-01': {'group': '成型', 'sequence': 3}, } with patch.object(fc, 'get_workcenter_mapping', return_value=mock_mapping): result = fc.get_workcenters_for_groups(['焊接', '焊線']) assert len(result) == 2 assert 'DB-01' in result assert 'WB-01' in result assert 'MD-01' not in result def test_returns_empty_for_empty_groups_list(self): """Test returns empty list for empty groups list.""" import mes_dashboard.services.filter_cache as fc mock_mapping = { 'DB-01': {'group': '焊接', 'sequence': 1}, } with patch.object(fc, 'get_workcenter_mapping', return_value=mock_mapping): result = fc.get_workcenters_for_groups([]) assert result == [] class TestGetWorkcenterGroups: """Test get_workcenter_groups function.""" @pytest.fixture(autouse=True) def reset_cache(self): """Reset cache state before each test.""" import mes_dashboard.services.filter_cache as fc with fc._CACHE_LOCK: fc._CACHE['workcenter_groups'] = None fc._CACHE['workcenter_mapping'] = None fc._CACHE['workcenter_to_short'] = None fc._CACHE['last_refresh'] = None fc._CACHE['is_loading'] = False yield with fc._CACHE_LOCK: fc._CACHE['workcenter_groups'] = None fc._CACHE['workcenter_mapping'] = None fc._CACHE['workcenter_to_short'] = None fc._CACHE['last_refresh'] = None fc._CACHE['is_loading'] = False def test_returns_groups_sorted_by_sequence(self): """Test returns groups sorted by sequence.""" import mes_dashboard.services.filter_cache as fc from datetime import datetime # Set up cache directly with fc._CACHE_LOCK: fc._CACHE['workcenter_groups'] = [ {'name': '成型', 'sequence': 3}, {'name': '焊接', 'sequence': 1}, {'name': '焊線', 'sequence': 2}, ] fc._CACHE['workcenter_mapping'] = {} fc._CACHE['workcenter_to_short'] = {} fc._CACHE['last_refresh'] = datetime.now() result = fc.get_workcenter_groups() # Should preserve original order (as stored) assert len(result) == 3 names = [g['name'] for g in result] assert '成型' in names assert '焊接' in names assert '焊線' in names class TestLoadWorkcenterMappingFromSpec: """Test _load_workcenter_mapping_from_spec function.""" def test_builds_mapping_from_spec_view(self): """Test builds mapping from SPEC_WORKCENTER_V data.""" import mes_dashboard.services.filter_cache as fc mock_df = pd.DataFrame({ 'WORK_CENTER': ['DB-01', 'DB-02', 'WB-01'], 'WORK_CENTER_GROUP': ['焊接', '焊接', '焊線'], 'WORKCENTERSEQUENCE_GROUP': [1, 1, 2], 'WORK_CENTER_SHORT': ['DB', 'DB', 'WB'], }) with patch.object(fc, 'read_sql_df', return_value=mock_df): groups, mapping, short_mapping = fc._load_workcenter_mapping_from_spec() # Check groups assert len(groups) == 2 # 2 unique groups group_names = [g['name'] for g in groups] assert '焊接' in group_names assert '焊線' in group_names # Check mapping assert len(mapping) == 3 assert mapping['DB-01']['group'] == '焊接' assert mapping['WB-01']['group'] == '焊線' # Check short mapping assert short_mapping['DB-01'] == 'DB' assert short_mapping['WB-01'] == 'WB' def test_returns_empty_when_no_data(self): """Test returns empty structures when no data.""" import mes_dashboard.services.filter_cache as fc with patch.object(fc, 'read_sql_df', return_value=None): groups, mapping, short_mapping = fc._load_workcenter_mapping_from_spec() assert groups == [] assert mapping == {} assert short_mapping == {} def test_handles_empty_dataframe(self): """Test handles empty DataFrame.""" import mes_dashboard.services.filter_cache as fc mock_df = pd.DataFrame(columns=['WORK_CENTER', 'WORK_CENTER_GROUP', 'WORKCENTERSEQUENCE_GROUP', 'WORK_CENTER_SHORT']) with patch.object(fc, 'read_sql_df', return_value=mock_df): groups, mapping, short_mapping = fc._load_workcenter_mapping_from_spec() assert groups == [] assert mapping == {} assert short_mapping == {} class TestGetCacheStatus: """Test get_cache_status function.""" @pytest.fixture(autouse=True) def reset_cache(self): """Reset cache state before each test.""" import mes_dashboard.services.filter_cache as fc with fc._CACHE_LOCK: fc._CACHE['workcenter_groups'] = None fc._CACHE['workcenter_mapping'] = None fc._CACHE['workcenter_to_short'] = None fc._CACHE['last_refresh'] = None fc._CACHE['is_loading'] = False yield with fc._CACHE_LOCK: fc._CACHE['workcenter_groups'] = None fc._CACHE['workcenter_mapping'] = None fc._CACHE['workcenter_to_short'] = None fc._CACHE['last_refresh'] = None fc._CACHE['is_loading'] = False def test_returns_not_loaded_when_empty(self): """Test returns loaded=False when cache empty.""" import mes_dashboard.services.filter_cache as fc result = fc.get_cache_status() assert result['loaded'] is False assert result['last_refresh'] is None def test_returns_loaded_when_data_exists(self): """Test returns loaded=True when cache has data.""" import mes_dashboard.services.filter_cache as fc from datetime import datetime now = datetime.now() with fc._CACHE_LOCK: fc._CACHE['workcenter_groups'] = [{'name': 'G1', 'sequence': 1}] fc._CACHE['workcenter_mapping'] = {'WC1': {'group': 'G1', 'sequence': 1}} fc._CACHE['last_refresh'] = now result = fc.get_cache_status() assert result['loaded'] is True assert result['last_refresh'] is not None assert result['workcenter_groups_count'] == 1 assert result['workcenter_mapping_count'] == 1 class TestFilterCacheConfig: """Test environment-based filter-cache source configuration.""" def test_filter_cache_views_are_env_configurable(self, monkeypatch): monkeypatch.setenv("FILTER_CACHE_WIP_VIEW", "CUSTOM.WIP_VIEW") monkeypatch.setenv("FILTER_CACHE_SPEC_WORKCENTER_VIEW", "CUSTOM.SPEC_VIEW") import mes_dashboard.services.filter_cache as fc reloaded = importlib.reload(fc) assert reloaded.WIP_VIEW == "CUSTOM.WIP_VIEW" assert reloaded.SPEC_WORKCENTER_VIEW == "CUSTOM.SPEC_VIEW"