#!/usr/bin/env python3
"""Offline fixture tests. No package download, database, Nginx or PM2 execution."""
import importlib.util,json,os,subprocess,tempfile,hashlib,tarfile
from pathlib import Path
root=Path(__file__).resolve().parent
spec=importlib.util.spec_from_file_location('assembler',root/'tools/assemble.py');mod=importlib.util.module_from_spec(spec);spec.loader.exec_module(mod)

def put(p,s=''):
 p.parent.mkdir(parents=True,exist_ok=True);p.write_text(s)

def fixture(base,name):
 j=base/name;src=j/'src';src.mkdir(parents=True);put(j/'.foodsafe-build-workspace')
 put(j/'build-meta.json',json.dumps({'version':name,'source_commit':'a'*40}))
 put(j/'logs/release-overlay.json','[]');put(src/'pnpm-lock.yaml','lockfileVersion: 9.0')
 put(src/'services/gateway-service/package.json',json.dumps({'dependencies':{'dmdb':'1.0.52452'}}))
 (src/'services/gateway-service/node_modules').mkdir();put(src/'services/gateway-service/dist/services/gateway-service/src/main.js','// fixture')
 put(src/'libs/shared/models/index.js','// fixture');put(src/'libs/shared/.env','PRIVATE_FIXTURE_SENTINEL')
 for m in ['dmdb','dotenv','@nestjs/core','express','mysql2','mqtt']:
  put(src/'node_modules'/m/'index.js','module.exports = {};')
 for m,f in [('hls.js','hls.min.js'),('flv.js','flv.min.js')]:put(src/'node_modules'/m/'dist'/f,'// fixture')
 for app in ['web-school','web-regulator','screen','mobile-device']:put(src/'apps'/app/'dist/index.html','<head></head>')
 put(j/'extra/node_modules/qrcode/index.js','module.exports = {};');put(j/'extra/package-lock.json','{}')
 (src/'node_modules/qrcode').symlink_to(j/'extra/node_modules/qrcode',target_is_directory=True)
 return j

with tempfile.TemporaryDirectory(prefix='foodsafe-tools-test-') as td:
 b=Path(td);j=fixture(b,'good');a=mod.assemble(j,root/'tools/template',b/'out')
 kit=b/'out/foodsafe-good-kylin-x64'
 assert not (kit/'payload/libs/shared/.env').exists()
 assert not (kit/'payload/node_modules/qrcode').is_symlink()
 for line in (kit/'PAYLOAD.sha256').read_text().splitlines():
  digest,rel=line.split('  ',1);assert mod.sha(kit/rel)==digest
 with tarfile.open(a) as tf:
  assert not any('.env'==Path(x.name).name for x in tf.getmembers())
  assert all(not x.name.startswith('/') and '..' not in Path(x.name).parts for x in tf.getmembers())
 try:mod.assemble(j,root/'tools/template',b/'out');raise AssertionError('overwrite accepted')
 except RuntimeError as e:assert '覆盖' in str(e)
 for kind,target in [('absolute','/tmp'),('dangling','missing-target')]:
  j=fixture(b,kind);(j/'src/node_modules/unexpected').symlink_to(target)
  try:mod.assemble(j,root/'tools/template',b/'out');raise AssertionError(kind+' accepted')
  except RuntimeError as e:assert '链接' in str(e)
print(json.dumps({'fixture_checks':['successful_portable_assembly','payload_hashes','archive_paths','private_env_removed','qrcode_materialized','refuse_overwrite','reject_absolute_symlink','reject_dangling_symlink'],'production_access':False,'real_customer_build':False},ensure_ascii=False))
