Files
DashBoard/tests/test_resource_history_routes.py
2026-02-08 08:30:48 +08:00

298 lines
11 KiB
Python

# -*- coding: utf-8 -*-
"""Integration tests for resource history API endpoints.
Tests API endpoints for proper response format, error handling,
and parameter validation.
"""
import unittest
from unittest.mock import patch, MagicMock
import json
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
import mes_dashboard.core.database as db
from mes_dashboard.app import create_app
class TestResourceHistoryOptionsAPI(unittest.TestCase):
"""Integration tests for /api/resource/history/options endpoint."""
def setUp(self):
"""Set up test client."""
db._ENGINE = None
self.app = create_app('testing')
self.app.config['TESTING'] = True
self.client = self.app.test_client()
@patch('mes_dashboard.routes.resource_history_routes.get_filter_options')
def test_options_success(self, mock_get_options):
"""Successful options request should return workcenter_groups and families."""
mock_get_options.return_value = {
'workcenter_groups': [
{'name': '焊接_DB', 'sequence': 1},
{'name': '成型', 'sequence': 4}
],
'families': ['FAM01', 'FAM02']
}
response = self.client.get('/api/resource/history/options')
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertTrue(data['success'])
self.assertIn('data', data)
self.assertEqual(len(data['data']['workcenter_groups']), 2)
self.assertEqual(data['data']['workcenter_groups'][0]['name'], '焊接_DB')
self.assertEqual(data['data']['families'], ['FAM01', 'FAM02'])
@patch('mes_dashboard.routes.resource_history_routes.get_filter_options')
def test_options_failure(self, mock_get_options):
"""Failed options request should return error."""
mock_get_options.return_value = None
response = self.client.get('/api/resource/history/options')
self.assertEqual(response.status_code, 500)
data = json.loads(response.data)
self.assertFalse(data['success'])
self.assertIn('error', data)
class TestResourceHistorySummaryAPI(unittest.TestCase):
"""Integration tests for /api/resource/history/summary endpoint."""
def setUp(self):
"""Set up test client."""
db._ENGINE = None
self.app = create_app('testing')
self.app.config['TESTING'] = True
self.client = self.app.test_client()
def test_missing_start_date(self):
"""Missing start_date should return 400."""
response = self.client.get('/api/resource/history/summary?end_date=2024-01-31')
self.assertEqual(response.status_code, 400)
data = json.loads(response.data)
self.assertFalse(data['success'])
self.assertIn('start_date', data['error'])
def test_missing_end_date(self):
"""Missing end_date should return 400."""
response = self.client.get('/api/resource/history/summary?start_date=2024-01-01')
self.assertEqual(response.status_code, 400)
data = json.loads(response.data)
self.assertFalse(data['success'])
self.assertIn('end_date', data['error'])
@patch('mes_dashboard.routes.resource_history_routes.query_summary')
def test_date_range_exceeds_limit(self, mock_query):
"""Date range exceeding 730 days should return error."""
mock_query.return_value = {'error': '查詢範圍不可超過 730 天(兩年)'}
response = self.client.get(
'/api/resource/history/summary?start_date=2024-01-01&end_date=2026-01-02'
)
self.assertEqual(response.status_code, 400)
data = json.loads(response.data)
self.assertFalse(data['success'])
self.assertIn('730', data['error'])
@patch('mes_dashboard.routes.resource_history_routes.query_summary')
def test_successful_summary(self, mock_query):
"""Successful summary request should return all data sections."""
mock_query.return_value = {
'kpi': {
'ou_pct': 80.0,
'prd_hours': 800,
'sby_hours': 100,
'udt_hours': 50,
'sdt_hours': 30,
'egt_hours': 20,
'nst_hours': 100,
'machine_count': 10
},
'trend': [{'date': '2024-01-01', 'ou_pct': 80.0}],
'heatmap': [{'workcenter': 'WC01', 'date': '2024-01-01', 'ou_pct': 80.0}],
'workcenter_comparison': [{'workcenter': 'WC01', 'ou_pct': 80.0}]
}
response = self.client.get(
'/api/resource/history/summary?start_date=2024-01-01&end_date=2024-01-07'
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertTrue(data['success'])
self.assertIn('kpi', data['data'])
self.assertIn('trend', data['data'])
self.assertIn('heatmap', data['data'])
self.assertIn('workcenter_comparison', data['data'])
@patch('mes_dashboard.routes.resource_history_routes.query_summary')
def test_summary_with_filters(self, mock_query):
"""Summary with filters should pass them to service."""
mock_query.return_value = {'kpi': {}, 'trend': [], 'heatmap': [], 'workcenter_comparison': []}
response = self.client.get(
'/api/resource/history/summary'
'?start_date=2024-01-01'
'&end_date=2024-01-07'
'&granularity=week'
'&workcenter_groups=焊接_DB'
'&workcenter_groups=成型'
'&families=FAM01'
'&families=FAM02'
'&is_production=1'
'&is_key=1'
)
self.assertEqual(response.status_code, 200)
mock_query.assert_called_once()
call_kwargs = mock_query.call_args[1]
self.assertEqual(call_kwargs['granularity'], 'week')
self.assertEqual(call_kwargs['workcenter_groups'], ['焊接_DB', '成型'])
self.assertEqual(call_kwargs['families'], ['FAM01', 'FAM02'])
self.assertTrue(call_kwargs['is_production'])
self.assertTrue(call_kwargs['is_key'])
class TestResourceHistoryDetailAPI(unittest.TestCase):
"""Integration tests for /api/resource/history/detail endpoint."""
def setUp(self):
"""Set up test client."""
db._ENGINE = None
self.app = create_app('testing')
self.app.config['TESTING'] = True
self.client = self.app.test_client()
def test_missing_dates(self):
"""Missing dates should return 400."""
response = self.client.get('/api/resource/history/detail')
self.assertEqual(response.status_code, 400)
data = json.loads(response.data)
self.assertFalse(data['success'])
@patch('mes_dashboard.routes.resource_history_routes.query_detail')
def test_successful_detail(self, mock_query):
"""Successful detail request should return data with total and truncated flag."""
mock_query.return_value = {
'data': [
{'workcenter': 'WC01', 'family': 'FAM01', 'resource': 'RES01', 'ou_pct': 80.0}
],
'total': 100,
'truncated': False,
'max_records': None
}
response = self.client.get(
'/api/resource/history/detail?start_date=2024-01-01&end_date=2024-01-07'
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertTrue(data['success'])
self.assertIn('data', data)
self.assertIn('total', data)
self.assertIn('truncated', data)
self.assertFalse(data['truncated'])
@patch('mes_dashboard.routes.resource_history_routes.query_detail')
def test_detail_truncated_warning(self, mock_query):
"""Detail with truncated data should return truncated flag and max_records."""
mock_query.return_value = {
'data': [{'workcenter': 'WC01', 'family': 'FAM01', 'resource': 'RES01', 'ou_pct': 80.0}],
'total': 6000,
'truncated': True,
'max_records': 5000
}
response = self.client.get(
'/api/resource/history/detail'
'?start_date=2024-01-01'
'&end_date=2024-01-07'
)
self.assertEqual(response.status_code, 200)
data = json.loads(response.data)
self.assertTrue(data['success'])
self.assertTrue(data['truncated'])
self.assertEqual(data['max_records'], 5000)
self.assertEqual(data['total'], 6000)
class TestResourceHistoryExportAPI(unittest.TestCase):
"""Integration tests for /api/resource/history/export endpoint."""
def setUp(self):
"""Set up test client."""
db._ENGINE = None
self.app = create_app('testing')
self.app.config['TESTING'] = True
self.client = self.app.test_client()
def test_missing_dates(self):
"""Missing dates should return 400."""
response = self.client.get('/api/resource/history/export')
self.assertEqual(response.status_code, 400)
data = json.loads(response.data)
self.assertFalse(data['success'])
@patch('mes_dashboard.routes.resource_history_routes.export_csv')
def test_successful_export(self, mock_export):
"""Successful export should return CSV with correct headers."""
mock_export.return_value = iter(['站點,型號,機台,OU%\n', 'WC01,FAM01,RES01,80%\n'])
response = self.client.get(
'/api/resource/history/export?start_date=2024-01-01&end_date=2024-01-07'
)
self.assertEqual(response.status_code, 200)
self.assertIn('text/csv', response.content_type)
self.assertIn('attachment', response.headers['Content-Disposition'])
self.assertIn('resource_history', response.headers['Content-Disposition'])
@patch('mes_dashboard.routes.resource_history_routes.export_csv')
def test_export_filename_includes_dates(self, mock_export):
"""Export filename should include date range."""
mock_export.return_value = iter(['header\n'])
response = self.client.get(
'/api/resource/history/export?start_date=2024-01-01&end_date=2024-01-07'
)
self.assertIn('2024-01-01', response.headers['Content-Disposition'])
self.assertIn('2024-01-07', response.headers['Content-Disposition'])
class TestAPIContentType(unittest.TestCase):
"""Test that APIs return proper content types."""
def setUp(self):
"""Set up test client."""
db._ENGINE = None
self.app = create_app('testing')
self.app.config['TESTING'] = True
self.client = self.app.test_client()
@patch('mes_dashboard.routes.resource_history_routes.get_filter_options')
def test_json_content_type(self, mock_get_options):
"""API endpoints should return application/json content type."""
mock_get_options.return_value = {'workcenter_groups': [], 'families': []}
response = self.client.get('/api/resource/history/options')
self.assertIn('application/json', response.content_type)
if __name__ == '__main__':
unittest.main()