Skip to main content
Glama
test_company_api.py17.6 kB
"""E2E tests for company-related APIs.""" import pytest from utils.api_client import TWSEAPIClient class TestCompanyDividendAPI: """股利分派情形 API 測試.""" ENDPOINT = "/opendata/t187ap45_L" EXPECTED_FIELDS = [ "出表日期", "公司代號", "公司名稱", "決議(擬議)進度", "股利年度", "股利所屬年(季)度", "股利所屬期間", "期別", "董事會(擬議)股利分派日", "股東會日期", "股東配發-盈餘分配之現金股利(元/股)", "股東配發-股東配發之現金(股利)總金額(元)", "摘錄公司章程-股利分派部分" ] def test_response_schema_matches_expected(self): """測試回應 schema 符合預期.""" data = TWSEAPIClient.get_data(self.ENDPOINT) # 取第一筆資料檢查欄位 first_item = data[0] assert isinstance(first_item, dict), "每筆資料應該是 dict" # 檢查所有必要欄位都存在 for field in self.EXPECTED_FIELDS: assert field in first_item, f"欄位 '{field}' 應該存在於回應中" def test_company_code_exists(self): """測試公司代號欄位存在且有效.""" data = TWSEAPIClient.get_data(self.ENDPOINT) for item in data[:10]: # 檢查前 10 筆 code = item.get("公司代號") assert code is not None, "公司代號不應為 None" assert isinstance(code, str), "公司代號應該是字串" assert code.strip() != "", "公司代號不應為空字串" # 不限制長度或特定格式,支援一般股、特別股、ETF等各種代號 def test_get_company_data_by_code(self, sample_stock_code): """測試依公司代號查詢資料.""" data = TWSEAPIClient.get_company_data(self.ENDPOINT, sample_stock_code) if data: assert data.get("公司代號") == sample_stock_code, \ f"查詢結果應該是指定的公司代號 {sample_stock_code}" class TestCompanyNewsAPI: """上市公司每日重大訊息 API 測試.""" ENDPOINT = "/opendata/t187ap04_L" EXPECTED_FIELDS = [ "出表日期", "發言日期", "發言時間", "公司代號", "公司名稱", "主旨 ", "符合條款", "事實發生日", "說明" ] def test_response_schema_matches_expected(self): """測試回應 schema 符合預期.""" data = TWSEAPIClient.get_data(self.ENDPOINT) # 取第一筆資料檢查欄位 first_item = data[0] assert isinstance(first_item, dict), "每筆資料應該是 dict" # 檢查所有必要欄位都存在 for field in self.EXPECTED_FIELDS: assert field in first_item, f"欄位 '{field}' 應該存在於回應中" def test_company_code_format(self): """測試公司代號格式正確.""" data = TWSEAPIClient.get_data(self.ENDPOINT) for item in data[:10]: # 檢查前 10 筆 code = item.get("公司代號") assert code is not None, "公司代號不應為 None" assert isinstance(code, str), "公司代號應該是字串" assert code.isdigit(), f"公司代號應該是數字: {code}" assert len(code) == 4, f"公司代號應該是 4 碼: {code}" def test_get_company_data_by_code(self, sample_stock_code): """測試依公司代號查詢資料.""" data = TWSEAPIClient.get_company_data(self.ENDPOINT, sample_stock_code) if data: assert data.get("公司代號") == sample_stock_code, \ f"查詢結果應該是指定的公司代號 {sample_stock_code}" class TestCompanyRevenueAPI: """上市公司每月營業收入彙總表 API 測試.""" ENDPOINT = "/opendata/t187ap05_L" EXPECTED_FIELDS = [ "出表日期", "資料年月", "公司代號", "公司名稱", "產業別", "營業收入-當月營收", "營業收入-上月營收", "營業收入-去年當月營收", "營業收入-上月比較增減(%)", "營業收入-去年同月增減(%)", "累計營業收入-當月累計營收", "累計營業收入-去年累計營收", "累計營業收入-前期比較增減(%)", "備註" ] def test_response_schema_matches_expected(self): """測試回應 schema 符合預期.""" data = TWSEAPIClient.get_data(self.ENDPOINT) # 取第一筆資料檢查欄位 first_item = data[0] assert isinstance(first_item, dict), "每筆資料應該是 dict" # 檢查所有必要欄位都存在 for field in self.EXPECTED_FIELDS: assert field in first_item, f"欄位 '{field}' 應該存在於回應中" def test_company_code_format(self): """測試公司代號格式正確.""" data = TWSEAPIClient.get_data(self.ENDPOINT) for item in data[:10]: # 檢查前 10 筆 code = item.get("公司代號") assert code is not None, "公司代號不應為 None" assert isinstance(code, str), "公司代號應該是字串" assert code.isdigit(), f"公司代號應該是數字: {code}" assert len(code) == 4, f"公司代號應該是 4 碼: {code}" def test_get_company_data_by_code(self, sample_stock_code): """測試依公司代號查詢資料.""" data = TWSEAPIClient.get_company_data(self.ENDPOINT, sample_stock_code) if data: assert data.get("公司代號") == sample_stock_code, \ f"查詢結果應該是指定的公司代號 {sample_stock_code}" class TestOtherESGAPIs: """其他 ESG API 測試.""" @pytest.mark.parametrize("endpoint,name", [ ("/opendata/t187ap46_L_18", "持股及控制力"), ("/opendata/t187ap46_L_14", "產品品質與安全"), ("/opendata/t187ap46_L_12", "食品安全"), ("/opendata/t187ap46_L_11", "產品生命週期"), ("/opendata/t187ap46_L_10", "燃料管理"), ("/opendata/t187ap46_L_7", "投資人溝通"), ("/opendata/t187ap46_L_6", "董事會"), ("/opendata/t187ap46_L_5", "人力發展"), ("/opendata/t187ap46_L_4", "廢棄物管理"), ("/opendata/t187ap46_L_3", "水資源管理"), ("/opendata/t187ap46_L_2", "能源管理"), ("/opendata/t187ap46_L_1", "溫室氣體排放"), ]) def test_esg_api_schema(self, endpoint, name): """測試 ESG API schema.""" try: data = TWSEAPIClient.get_data(endpoint) # 只測試 schema,檢查有數據時的欄位結構 if data and len(data) > 0: first_item = data[0] assert isinstance(first_item, dict), f"{name} 資料應該是 dict" except Exception as e: # 如果是食品安全API,允許異常,因為它可能返回無效JSON if endpoint == "/opendata/t187ap46_L_12": pass # 預期會有異常,跳過 else: raise # 其他API不應該有異常 class TestCompanyBasicInfoAPI: """上市公司基本資料 API 測試.""" ENDPOINT = "/opendata/t187ap03_L" def test_response_has_company_code(self): """測試回應包含公司代號欄位.""" data = TWSEAPIClient.get_data(self.ENDPOINT) first_item = data[0] assert "公司代號" in first_item or "Code" in first_item, "應該包含公司代號欄位" def test_get_company_data_by_code(self, sample_stock_code): """測試依公司代號查詢資料.""" data = TWSEAPIClient.get_company_data(self.ENDPOINT, sample_stock_code) if data: returned_code = data.get("公司代號") or data.get("Code") assert returned_code == sample_stock_code, \ f"查詢結果應該是指定的公司代號 {sample_stock_code}" class TestCompanyMajorShareholdersAPI: """上市公司持股逾 10% 大股東名單 API 測試.""" ENDPOINT = "/opendata/t187ap02_L" def test_response_has_company_code(self): """測試回應包含公司代號欄位.""" data = TWSEAPIClient.get_data(self.ENDPOINT) first_item = data[0] assert "公司代號" in first_item or "Code" in first_item, "應該包含公司代號欄位" def test_get_company_data_by_code(self, sample_stock_code): """測試依公司代號查詢資料.""" data = TWSEAPIClient.get_company_data(self.ENDPOINT, sample_stock_code) if data: returned_code = data.get("公司代號") or data.get("Code") assert returned_code == sample_stock_code, \ f"查詢結果應該是指定的公司代號 {sample_stock_code}" class TestCompanyEPSStatisticsAPI: """上市公司各產業EPS統計資訊 API 測試.""" ENDPOINT = "/opendata/t187ap14_L" class TestCompanyDirectorShareholdingAPIs: """董事、監察人持股相關 APIs 測試.""" @pytest.mark.parametrize("endpoint,name", [ ("/opendata/t187ap08_L", "董事、監察人持股不足法定成數彙總表"), ("/opendata/t187ap11_L", "董監事持股餘額明細資料"), ("/opendata/t187ap12_L", "每日內部人持股轉讓事前申報表-持股轉讓日報表"), ("/opendata/t187ap13_L", "每日內部人持股轉讓事前申報表-持股未轉讓日報表"), ("/opendata/t187ap10_L", "董事、監察人持股不足法定成數連續達3個月以上彙總表"), ("/opendata/t187ap09_L", "董事、監察人質權設定占董事及監察人實際持有股數彙總表"), ]) def test_director_shareholding_api_schema(self, endpoint, name): """測試董事、監察人持股相關 API schema.""" data = TWSEAPIClient.get_data(endpoint) # 只測試 schema,檢查有數據時的欄位結構 if data and len(data) > 0: first_item = data[0] assert isinstance(first_item, dict), f"{name} 資料應該是 dict" class TestCompanyGovernanceAPIs: """公司治理相關 APIs 測試.""" @pytest.mark.parametrize("endpoint,name", [ ("/opendata/t187ap22_L", "金管會證券期貨局裁罰案件專區"), ("/opendata/t187ap30_L", "獨立董監事兼任情形彙總表"), ("/opendata/t187ap29_A_L", "董事酬金相關資訊"), ("/opendata/t187ap29_B_L", "監察人酬金相關資訊"), ("/opendata/t187ap29_C_L", "合併報表董事酬金相關資訊"), ("/opendata/t187ap29_D_L", "合併報表監察人酬金相關資訊"), ("/opendata/t187ap23_L", "違反資訊申報、重大訊息及說明記者會規定專區"), ("/static/20151104/CSR103", "103 年應編製與申報 CSR 報告書名單"), ("/opendata/t187ap03_P", "公開發行公司基本資料"), ("/announcement/punish", "集中市場公布處置股票"), ("/opendata/t187ap38_L", "股東會公告-召集股東常(臨時)會公告資料彙總表"), ("/opendata/t187ap24_L", "經營權及營業範圍異(變)動專區-經營權異動公司"), ("/opendata/t187ap26_L", "經營權異動且營業範圍重大變更停止買賣公司"), ("/opendata/t187ap41_L", "召開股東常 (臨時) 會日期、地點及採用電子投票情形等資料彙總表"), ("/opendata/t187ap25_L", "營業範圍重大變更公司"), ("/opendata/t187ap27_L", "經營權異動且營業範圍重大變更列為變更交易公司"), ("/opendata/t187ap32_L", "公司治理之相關規程規則"), ("/opendata/t187ap33_L", "董事長是否兼任總經理"), ("/opendata/t187ap34_L", "採累積投票制、全額連記法、候選人提名制選任董監事及當選資料彙總表"), ("/opendata/t187ap35_L", "股東行使提案權情形彙總表"), ]) def test_governance_api_schema(self, endpoint, name): """測試公司治理相關 API schema.""" try: data = TWSEAPIClient.get_data(endpoint) # 只測試 schema,檢查有數據時的欄位結構 if data and len(data) > 0: first_item = data[0] assert isinstance(first_item, dict), f"{name} 資料應該是 dict" except Exception as e: # 如果是CSR報告書名單API,允許異常,因為它可能返回無效JSON if endpoint == "/static/20151104/CSR103": pass # 預期會有異常,跳過 else: raise # 其他API不應該有異常 class TestCompanyListingAPIs: """公司上市相關 APIs 測試.""" @pytest.mark.parametrize("endpoint", [ "/company/applylistingForeign", "/company/newlisting", "/company/suspendListingCsvAndHtml", "/company/applylistingLocal", "/opendata/t187ap05_P", ]) def test_listing_apis_have_basic_fields(self, endpoint): """測試公司上市相關 APIs 都有基本欄位.""" data = TWSEAPIClient.get_data(endpoint) first_item = data[0] # 確保至少有基本欄位存在 assert len(first_item) > 0, f"{endpoint} 應該至少包含一些欄位" class TestShareholderMeetingAnnouncementsAPI: """股東會公告 API 測試.""" ENDPOINT = "/opendata/t187ap38_L" EXPECTED_FIELDS = [ "出表日期", "公司代號", "公司名稱", "股東常(臨時)會日期-常或臨時", "股東常(臨時)會日期-日期", "停止過戶起訖日期-起", "停止過戶起訖日期-訖", "預擬配發現金(股利)(元/股)-盈餘", "預擬配發現金(股利)(元/股)-法定盈餘公積、資本公積", "預擬配股(元/股)-盈餘", "預擬配股(元/股)-法定盈餘公積、資本公積", "擬現金增資金額(元)", "現金增資認購率(%)", "員工紅利-現金紅利(元)", "員工紅利-股票紅利(股)", "特別股股利(元/股)", "董監酬勞(元)", "公告日期", "公告時間", "種類" ] def test_response_schema_matches_expected(self): """測試回應 schema 符合預期.""" data = TWSEAPIClient.get_data(self.ENDPOINT) # 取第一筆資料檢查欄位 first_item = data[0] assert isinstance(first_item, dict), "每筆資料應該是 dict" # 檢查所有必要欄位都存在 for field in self.EXPECTED_FIELDS: assert field in first_item, f"欄位 '{field}' 應該存在於回應中" def test_hardcoded_fields_exist(self): """測試程式碼中寫死的欄位確實存在於 API 回應中.""" data = TWSEAPIClient.get_data(self.ENDPOINT) first_item = data[0] # 這些是我們在 get_company_shareholder_meeting_announcements 函數中寫死的欄位 hardcoded_fields = [ "公司代號", "公司名稱", "股東常(臨時)會日期-日期", "股東常(臨時)會日期-常或臨時" ] for field in hardcoded_fields: assert field in first_item, f"寫死欄位 '{field}' 必須存在於 API 回應中" def test_meaningful_data_filtering_fields(self): """測試用於過濾有意義資料的欄位確實存在.""" data = TWSEAPIClient.get_data(self.ENDPOINT) first_item = data[0] # 這些是我們用 has_meaningful_data 檢查的關鍵欄位 filtering_fields = [ "公司代號", "公司名稱", "股東常(臨時)會日期-日期", "股東常(臨時)會日期-常或臨時" ] for field in filtering_fields: assert field in first_item, f"過濾欄位 '{field}' 必須存在於 API 回應中" def test_company_code_format(self): """測試公司代號格式正確.""" data = TWSEAPIClient.get_data(self.ENDPOINT) for item in data[:10]: # 檢查前 10 筆 code = item.get("公司代號") assert code is not None, "公司代號不應為 None" assert isinstance(code, str), "公司代號應該是字串" assert code.strip() != "", "公司代號不應為空字串" # 股票代號通常是 4 碼數字,但也可能有其他格式 assert len(code) >= 3, f"公司代號長度應該至少 3 碼: {code}" def test_get_company_data_by_code(self, sample_stock_code): """測試依公司代號查詢資料.""" data = TWSEAPIClient.get_company_data(self.ENDPOINT, sample_stock_code) if data: assert data.get("公司代號") == sample_stock_code, \ f"查詢結果應該是指定的公司代號 {sample_stock_code}" def test_meeting_type_values(self): """測試股東會類型的值符合預期.""" data = TWSEAPIClient.get_data(self.ENDPOINT) valid_meeting_types = ["常會", "臨時會"] for item in data[:20]: # 檢查前 20 筆 meeting_type = item.get("股東常(臨時)會日期-常或臨時") if meeting_type and meeting_type.strip(): assert meeting_type in valid_meeting_types, \ f"股東會類型應該是 '常會' 或 '臨時會',實際值:'{meeting_type}'"

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/twjackysu/TWSEMCPServer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server