auto: 일일 백업 2026-09-30 02:00

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hyowons
2026-09-30 02:00:03 +09:00
parent 1c8af0b523
commit d8a778cf67
136 changed files with 1838 additions and 2128 deletions
@@ -0,0 +1,160 @@
import sys
import unittest
from datetime import date
from pathlib import Path
from unittest.mock import patch
SCRIPTS = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(SCRIPTS))
import ipo_calendar_sync as sync
class IpoParserTests(unittest.TestCase):
def test_naver_api_parser_includes_cutoff_date(self):
payload = {
'examinationList': [],
'demandForecastingList': [],
'forecastingCompleteList': [],
'subscriptionList': [{
'ipoCode': 'A0035S0',
'compName': '빅웨이브로보틱스',
'poStartDate': '2026-09-15',
'poEndDate': '2026-09-16',
'lcalDate': '2026-09-29',
'orgNm': '한국투자증권',
}],
'subscriptionCompleteList': [],
'listingList': [],
}
with patch.object(sync, 'fetch', return_value=sync.json.dumps(payload)):
events = sync.parse_naver_api_events_from(date(2026, 9, 29))
self.assertEqual([(e.kind, e.name) for e in events], [
('listing', '빅웨이브로보틱스'),
])
self.assertEqual(events[0].start_date, date(2026, 9, 29))
def test_38_parsers_read_subscription_and_listing_tables(self):
subscription_html = '''
<table summary="공모주 청약일정"><tbody>
<tr><th>종목명</th><th>공모주일정</th><th>확정공모가</th><th>희망공모가</th><th>경쟁률</th><th>주간사</th></tr>
<tr><td>넥스아이</td><td>2026.11.03~11.04</td><td>-</td><td>-</td><td></td><td>한국투자증권</td></tr>
</tbody></table>'''
listing_html = '''
<table summary="신규상장종목"><tbody>
<tr><th>기업명</th><th>신규상장일</th></tr>
<tr><td>빅웨이브로보틱스</td><td>2026/09/29</td></tr>
</tbody></table>'''
subscriptions = sync.parse_38_subscription_html(
subscription_html, date(2026, 9, 29)
)
listings = sync.parse_38_listing_html(listing_html, date(2026, 9, 29))
self.assertEqual(subscriptions[0].start_date, date(2026, 11, 3))
self.assertEqual(subscriptions[0].brokers, '한국투자증권')
self.assertEqual(listings[0].name, '빅웨이브로보틱스')
self.assertEqual(listings[0].start_date, date(2026, 9, 29))
def test_zero_primary_result_switches_to_38_fallback(self):
fallback = sync.EventSpec(
'listing', '빅웨이브로보틱스', date(2026, 9, 29),
date(2026, 9, 29), '', sync.LISTING_URL,
source_label='38커뮤니케이션 IPO',
)
with patch.object(sync, 'parse_naver_api_events_from', return_value=[]), patch.object(
sync, 'parse_38_events_from', return_value=[fallback]
):
events, parser_used, errors = sync.collect_ipo_events(date(2026, 9, 29))
self.assertEqual(parser_used, '38_fallback')
self.assertEqual(events, [fallback])
self.assertEqual(errors, ['naver_api: 수집 결과 0건'])
def test_all_parser_failures_raise_structured_error(self):
with patch.object(
sync, 'parse_naver_api_events_from', side_effect=ValueError('schema changed')
), patch.object(
sync, 'parse_38_events_from', side_effect=ValueError('table changed')
):
with self.assertRaises(sync.IpoParserError) as caught:
sync.collect_ipo_events(date(2026, 9, 29))
self.assertEqual(caught.exception.errors, [
'naver_api: schema changed',
'38_fallback: table changed',
])
def test_parser_alert_is_deduplicated_and_recovery_is_reported(self):
state = {}
errors = ['naver_api: 수집 결과 0건']
with patch.object(sync, 'send_telegram', return_value=True) as send:
sync.update_parser_health(state, 'degraded', errors, 12)
sync.update_parser_health(state, 'degraded', errors, 12)
sync.update_parser_health(state, 'healthy', [], 18)
self.assertEqual(send.call_count, 2)
self.assertIn('파서 일부 실패', send.call_args_list[0].args[0])
self.assertIn('파서 복구', send.call_args_list[1].args[0])
self.assertEqual(state['parser_alerted_status'], 'healthy')
def test_failed_alert_is_retried_on_next_run(self):
state = {}
errors = ['naver_api: timeout', '38_fallback: timeout']
with patch.object(sync, 'send_telegram', side_effect=[False, True]) as send:
sync.update_parser_health(state, 'failed', errors, 0)
sync.update_parser_health(state, 'failed', errors, 0)
self.assertEqual(send.call_count, 2)
self.assertEqual(state['parser_alerted_status'], 'failed')
def test_primary_parser_gets_same_day_listing_augmentation(self):
future = sync.EventSpec(
'subscription', '넥스아이', date(2026, 11, 3), date(2026, 11, 4),
'한국투자증권', 'https://stock.naver.com/ipo/A493410',
)
listing_html = '''
<table summary="신규상장종목"><tbody>
<tr><th>기업명</th><th>신규상장일</th></tr>
<tr><td>빅웨이브로보틱스</td><td>2026/09/29</td></tr>
</tbody></table>'''
with patch.object(sync, 'parse_naver_api_events_from', return_value=[future]), patch.object(
sync, 'fetch', return_value=listing_html
):
events, parser_used, errors = sync.collect_ipo_events(date(2026, 9, 29))
self.assertEqual(parser_used, 'naver_api')
self.assertEqual(errors, [])
self.assertEqual({e.state_key for e in events}, {
'subscription|넥스아이', 'listing|빅웨이브로보틱스',
})
def test_primary_parser_backfills_recent_non_spac_listing(self):
future = sync.EventSpec(
'subscription', '넥스아이', date(2026, 11, 3), date(2026, 11, 4),
'한국투자증권', 'https://stock.naver.com/ipo/A493410',
)
listing_html = '''
<table summary="신규상장종목"><tbody>
<tr><th>기업명</th><th>신규상장일</th></tr>
<tr><td>와이즈플래닛컴퍼니</td><td>2026/09/23</td></tr>
<tr><td>KB스팩34호</td><td>2026/09/22</td></tr>
<tr><td>덕산넵코어스(구.넵코어스)</td><td>2026/09/30</td></tr>
</tbody></table>'''
with patch.object(sync, 'parse_naver_api_events_from', return_value=[future]), patch.object(
sync, 'fetch', return_value=listing_html
):
events, parser_used, errors = sync.collect_ipo_events(date(2026, 9, 29))
self.assertEqual(parser_used, 'naver_api')
self.assertEqual(errors, [])
self.assertIn('listing|와이즈플래닛컴퍼니', {e.state_key for e in events})
self.assertNotIn('listing|KB스팩34호', {e.state_key for e in events})
self.assertNotIn(
'listing|덕산넵코어스(구.넵코어스)', {e.state_key for e in events}
)
if __name__ == '__main__':
unittest.main()