# -*- coding: utf-8 -*- """Integration tests for Job Query API routes. Tests the API endpoints with mocked service dependencies. """ import pytest import json from unittest.mock import patch, MagicMock from mes_dashboard import create_app @pytest.fixture def app(): """Create test Flask application.""" app = create_app() app.config['TESTING'] = True return app @pytest.fixture def client(app): """Create test client.""" return app.test_client() class TestJobQueryPage: """Tests for /job-query page route.""" def test_page_returns_html(self, client): """Should return the job query page.""" response = client.get('/job-query') assert response.status_code == 200 assert b'html' in response.data.lower() class TestGetResources: """Tests for /api/job-query/resources endpoint.""" @patch('mes_dashboard.services.resource_cache.get_all_resources') def test_get_resources_success(self, mock_get_resources, client): """Should return resources list.""" mock_get_resources.return_value = [ { 'RESOURCEID': 'RES001', 'RESOURCENAME': 'Machine-01', 'WORKCENTERNAME': 'WC-A', 'RESOURCEFAMILYNAME': 'FAM-01' }, { 'RESOURCEID': 'RES002', 'RESOURCENAME': 'Machine-02', 'WORKCENTERNAME': 'WC-B', 'RESOURCEFAMILYNAME': 'FAM-02' } ] response = client.get('/api/job-query/resources') assert response.status_code == 200 data = json.loads(response.data) assert 'data' in data assert 'total' in data assert data['total'] == 2 assert data['data'][0]['RESOURCEID'] in ['RES001', 'RES002'] @patch('mes_dashboard.services.resource_cache.get_all_resources') def test_get_resources_empty(self, mock_get_resources, client): """Should return error when no resources available.""" mock_get_resources.return_value = [] response = client.get('/api/job-query/resources') assert response.status_code == 500 data = json.loads(response.data) assert 'error' in data @patch('mes_dashboard.services.resource_cache.get_all_resources') def test_get_resources_exception(self, mock_get_resources, client): """Should handle exception gracefully.""" mock_get_resources.side_effect = Exception('ORA-01017 invalid username/password') response = client.get('/api/job-query/resources') assert response.status_code == 500 data = json.loads(response.data) assert 'error' in data assert data['error'] == '服務暫時無法使用' assert 'ORA-01017' not in data['error'] class TestQueryJobs: """Tests for /api/job-query/jobs endpoint.""" def test_missing_resource_ids(self, client): """Should return error without resource_ids.""" response = client.post( '/api/job-query/jobs', json={ 'start_date': '2024-01-01', 'end_date': '2024-01-31' } ) assert response.status_code == 400 data = json.loads(response.data) assert 'error' in data assert '設備' in data['error'] def test_empty_resource_ids(self, client): """Should return error for empty resource_ids.""" response = client.post( '/api/job-query/jobs', json={ 'resource_ids': [], 'start_date': '2024-01-01', 'end_date': '2024-01-31' } ) assert response.status_code == 400 data = json.loads(response.data) assert 'error' in data def test_missing_start_date(self, client): """Should return error without start_date.""" response = client.post( '/api/job-query/jobs', json={ 'resource_ids': ['RES001'], 'end_date': '2024-01-31' } ) assert response.status_code == 400 data = json.loads(response.data) assert 'error' in data assert '日期' in data['error'] def test_missing_end_date(self, client): """Should return error without end_date.""" response = client.post( '/api/job-query/jobs', json={ 'resource_ids': ['RES001'], 'start_date': '2024-01-01' } ) assert response.status_code == 400 data = json.loads(response.data) assert 'error' in data def test_invalid_date_range(self, client): """Should return error for invalid date range.""" response = client.post( '/api/job-query/jobs', json={ 'resource_ids': ['RES001'], 'start_date': '2024-12-31', 'end_date': '2024-01-01' } ) assert response.status_code == 400 data = json.loads(response.data) assert 'error' in data assert '結束日期' in data['error'] or '早於' in data['error'] def test_date_range_exceeds_limit(self, client): """Should reject date range > 365 days.""" response = client.post( '/api/job-query/jobs', json={ 'resource_ids': ['RES001'], 'start_date': '2023-01-01', 'end_date': '2024-12-31' } ) assert response.status_code == 400 data = json.loads(response.data) assert 'error' in data assert '365' in data['error'] @patch('mes_dashboard.routes.job_query_routes.get_jobs_by_resources') def test_query_jobs_success(self, mock_query, client): """Should return jobs list on success.""" mock_query.return_value = { 'data': [ {'JOBID': 'JOB001', 'RESOURCENAME': 'Machine-01', 'JOBSTATUS': 'Complete'} ], 'total': 1, 'resource_count': 1 } response = client.post( '/api/job-query/jobs', json={ 'resource_ids': ['RES001'], 'start_date': '2024-01-01', 'end_date': '2024-01-31' } ) assert response.status_code == 200 data = json.loads(response.data) assert 'data' in data assert data['total'] == 1 assert data['data'][0]['JOBID'] == 'JOB001' @patch('mes_dashboard.routes.job_query_routes.get_jobs_by_resources') def test_query_jobs_service_error(self, mock_query, client): """Should return error from service.""" mock_query.return_value = {'error': '查詢失敗: Database error'} response = client.post( '/api/job-query/jobs', json={ 'resource_ids': ['RES001'], 'start_date': '2024-01-01', 'end_date': '2024-01-31' } ) assert response.status_code == 400 data = json.loads(response.data) assert 'error' in data class TestQueryJobTxnHistory: """Tests for /api/job-query/txn/ endpoint.""" @patch('mes_dashboard.routes.job_query_routes.get_job_txn_history') def test_get_txn_history_success(self, mock_query, client): """Should return transaction history.""" mock_query.return_value = { 'data': [ { 'JOBTXNHISTORYID': 'TXN001', 'JOBID': 'JOB001', 'TXNDATE': '2024-01-15 10:30:00', 'FROMJOBSTATUS': 'Open', 'JOBSTATUS': 'In Progress' } ], 'total': 1, 'job_id': 'JOB001' } response = client.get('/api/job-query/txn/JOB001') assert response.status_code == 200 data = json.loads(response.data) assert 'data' in data assert data['total'] == 1 assert data['job_id'] == 'JOB001' @patch('mes_dashboard.routes.job_query_routes.get_job_txn_history') def test_get_txn_history_service_error(self, mock_query, client): """Should return error from service.""" mock_query.return_value = {'error': '查詢失敗: Job not found'} response = client.get('/api/job-query/txn/INVALID_JOB') assert response.status_code == 400 data = json.loads(response.data) assert 'error' in data class TestExportJobs: """Tests for /api/job-query/export endpoint.""" def test_missing_resource_ids(self, client): """Should return error without resource_ids.""" response = client.post( '/api/job-query/export', json={ 'start_date': '2024-01-01', 'end_date': '2024-01-31' } ) assert response.status_code == 400 data = json.loads(response.data) assert 'error' in data def test_missing_dates(self, client): """Should return error without dates.""" response = client.post( '/api/job-query/export', json={ 'resource_ids': ['RES001'] } ) assert response.status_code == 400 data = json.loads(response.data) assert 'error' in data def test_invalid_date_range(self, client): """Should return error for invalid date range.""" response = client.post( '/api/job-query/export', json={ 'resource_ids': ['RES001'], 'start_date': '2024-12-31', 'end_date': '2024-01-01' } ) assert response.status_code == 400 data = json.loads(response.data) assert 'error' in data @patch('mes_dashboard.routes.job_query_routes.export_jobs_with_history') def test_export_success(self, mock_export, client): """Should return CSV streaming response.""" # Mock generator that yields CSV content def mock_generator(*args): yield '\ufeff設備名稱,工單ID\n' yield 'Machine-01,JOB001\n' mock_export.return_value = mock_generator() response = client.post( '/api/job-query/export', json={ 'resource_ids': ['RES001'], 'start_date': '2024-01-01', 'end_date': '2024-01-31' } ) assert response.status_code == 200 assert 'text/csv' in response.content_type assert 'attachment' in response.headers.get('Content-Disposition', '') assert 'job_history_export.csv' in response.headers.get('Content-Disposition', '')