import unittest,json,re,tempfile
from pathlib import Path
from types import SimpleNamespace
from build_dashboard import iso_date,percent,project,build
from sales_analysis import load_sales
class DashboardTests(unittest.TestCase):
 def test_date_no_inferred_year(self):
  for v in ['7月1日','9月','2026-02-30','明天','',None]:self.assertEqual(iso_date(v),'')
  self.assertEqual(iso_date('2026-09-10'),'2026-09-10')
 def test_percentage_not_imputed(self):
  self.assertIsNone(percent(''));self.assertEqual(percent('0'),0);self.assertEqual(percent(0),0);self.assertEqual(percent('50%'),50)
  for v in ['101','-5','待定','NaN']:self.assertIsNone(percent(v))
 def test_plan_is_review_signal_not_overdue(self):
  self.assertFalse(project({'项目名称':'测试','预期上线日期':'2026-08-08'},1,'2026-08-08')['planPast'])
  self.assertTrue(project({'项目名称':'测试','预期上线日期':'2026-08-07'},1,'2026-08-08')['planPast'])
  self.assertFalse(project({'项目名称':'测试','预期上线日期':'2026-08-07','项目状态':'已完成'},1,'2026-08-08')['planPast'])
 def test_each_historical_case_has_one_offer(self):
  s=load_sales();self.assertEqual(len(s['cases']),28);self.assertEqual(sum(x['count'] for x in s['packages']),28)
  self.assertEqual(len({x['key'] for x in s['cases']}),28)
  self.assertEqual(sum(x['observationState']=='待补证' for x in s['cases']),15)
  self.assertEqual([x['count'] for x in s['packages']],[10,5,1,3,3,4,2])
  self.assertTrue(all(x['uniqueProject'] is False for x in s['cases']))
 def test_sales_does_not_fill_operational_records(self):
  with tempfile.TemporaryDirectory() as t:
   build(SimpleNamespace(snapshot_dir=None,snapshot_date=None,output=Path(t)))
   d=json.loads((Path(t)/'snapshot.json').read_text());self.assertFalse(d['connected']);self.assertEqual(d['projects'],[]);self.assertEqual(d['sales']['caseCount'],28)
   h=(Path(t)/'delivery-project-command-center.html').read_text();self.assertNotIn('__DATA__',h)
   self.assertIsNotNone(re.search('id="snapshot" type="application/json">(.*?)</script>',h,re.S))
   self.assertNotRegex(h,r'<script[^>]+src=');self.assertNotRegex(h,r'<link[^>]+stylesheet')
 def test_no_raw_person_identifiers(self):
  p=project({'项目名称':'测试','交付负责人':'{"user_id":"secret-person-key"}'},1,'2026-08-08')
  self.assertTrue(p['ownerRecorded']);self.assertNotIn('secret-person-key',json.dumps(p))
class LiveSnapshotTests(unittest.TestCase):
 def test_excel_serial_and_zero(self):
  from refresh_live_dashboard import excel_date,normalize_project
  self.assertEqual(excel_date(46279),'2026-09-14')
  self.assertEqual(excel_date(None),'')
  p=normalize_project({'项目名称':{'text':'测试','url':'https://doc.weixin.qq.com/sheet/test'},'项目完成度':0,'交付负责人':[{'name':'负责人','id':'not-to-render'}]},1,'2026-09-14')
  self.assertEqual(p['progress'],0);self.assertEqual(p['ownerText'],'负责人');self.assertNotIn('not-to-render',json.dumps(p))
 def test_placeholder_links_are_not_exposed(self):
  from refresh_live_dashboard import safe_project_url
  for u in ['https://湖南体育职业学院','javascript:alert(1)','https://doc.weixin.qq.com.evil.test/sheet/a','https://doc.weixin.qq.com/evil']:
   self.assertEqual(safe_project_url(u),'')
  self.assertEqual(safe_project_url('https://doc.weixin.qq.com/sheet/abc'),'https://doc.weixin.qq.com/sheet/abc')
if __name__=='__main__':unittest.main()
