Files
Investidor-V2/tools/portal-geo-crosswalk-http-qa.py
T
nala 01405cbf8a
tests / ci (push) Canceled after 0s
oportunidades 5
2026-10-10 14:15:28 +02:00

114 lines
6.9 KiB
Python

"""Authenticated requests to the guarded QA server; no UI acceptance or active writes."""
import base64
import hashlib
import hmac
import http.cookiejar
import json
import re
import struct
import time
import urllib.error
import urllib.parse
import urllib.request
import uuid
import sys
from datetime import datetime, timezone
from pathlib import Path
root = Path(__file__).resolve().parent.parent
private = root / 'storage/app/private/investmoz-v2/portal-audit'
backup = json.loads((private.parent / 'opportunity-resources-backup-proof.json').read_text(encoding='utf-8-sig'))
target = backup['restored_target']
assert backup['restore_verified'] and re.fullmatch(r'desk_restore_\d{8}_\d{6}_test', target['database']) and int(target['port']) == 55441
expected = target['database'] + ':55441'
events = []
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar))
def call(method, path, data=None, status=200, html=False):
assert path.startswith('/') and not path.startswith('//')
headers = {'Accept': 'text/html' if html else 'application/json', 'Content-Type': 'application/json'}
for cookie in jar:
if cookie.name == 'XSRF-TOKEN':
headers['X-XSRF-TOKEN'] = urllib.parse.unquote(cookie.value)
request = urllib.request.Request('http://127.0.0.1:18097' + path, headers=headers, data=None if data is None else json.dumps(data).encode(), method=method)
try:
response = opener.open(request)
except urllib.error.HTTPError as e:
response = e
assert response.headers.get('X-Investmoz-QA-Target') == expected
content = response.read()
assert response.status == status, f'{method} {path}: expected {status}, observed {response.status}; response body withheld'
events.append({'method': method, 'url': path, 'status': response.status, 'qa_target_verified': True})
return None if html or not content else json.loads(content)
call('GET', '/login', html=True)
credentials = json.loads((private / 'i0-qa-actor-private.json').read_text(encoding='utf-8-sig'))
login = call('POST', '/login', {k: credentials[k] for k in ['email', 'password']})
if login.get('two_factor'):
secret = base64.b32decode(credentials['totp_secret'])
digest = hmac.new(secret, struct.pack('>Q', int(time.time()) // 30), hashlib.sha1).digest()
offset = digest[-1] & 15
code = f'{(struct.unpack(">I", digest[offset:offset + 4])[0] & 0x7fffffff) % 1000000:06d}'
call('POST', '/two-factor-challenge', {'code': code}, status=204)
plans = []
results = []
apply = '--apply' in sys.argv
verify_new = '--verify-new' in sys.argv
assert not (apply and verify_new), 'Verification must not apply another batch.'
for level in [1, 2, 3]:
plan = call('GET', f'/management/territory-geometry-import?level={level}')
plans.append(plan)
assert plan['expected'] == {1: 11, 2: 159, 3: 411}[level]
if not apply:
continue
assert plan['scan_ready'] and not any('conflict' in r['status'] or r['status'] == 'invalid_geometry' for r in plan['rows'])
data = {'level': level, 'hash': plan['hash'], 'idempotency_key': str(uuid.uuid4()), 'reason': 'Correspondências explícitas Maputo e descendentes sob os pais aceites, exclusivamente em QA', 'confirm': True}
result = call('POST', '/management/territory-geometry-import', data)
assert not result['published'] and result['resolved'] == plan['resolved']
assert call('POST', '/management/territory-geometry-import', data) == result
updated = call('GET', f'/management/territory-geometry-import?level={level}')
assert all(r['status'] == 'reuse' for r in updated['rows'] if r['location_id'])
results.append(result)
# Recovery through the actual endpoints used by the hierarchy fields and map component.
consumers = []
locations_to_verify = []
for plan in plans:
for row in plan['rows']:
if row['status'] != 'insert' or not row['location_id'] or not apply:
continue
locations_to_verify.append(row['location_id'])
if verify_new:
preservation = json.loads((private / 'geometry-crosswalk-preservation-qa.json').read_text(encoding='utf-8-sig'))
assert preservation['target'] == target and preservation['catalogue_preserved'] and preservation['original_geometries_preserved'] == 361
locations_to_verify = [g['location_id'] for g in preservation['new_geometries']]
assert len(locations_to_verify) == len(set(locations_to_verify)) == 32
for location_id in locations_to_verify:
context = call('GET', '/management/territory-geometries?' + urllib.parse.urlencode({'territory_id': location_id}))
assert context['selected_geometry_available'] and any(a['selected_territory'] and a['territory_id'] == location_id for a in context['areas'])
consumers.append({'territory_id': location_id, 'selected_geometry_available_manager': True, 'areas': len(context['areas'])})
province_name_recovery = []
if verify_new:
for name in ['Maputo Província', 'Maputo Cidade']:
options = call('GET', '/api/v1/investment/territory-options?' + urllib.parse.urlencode({'type': 'province', 'page': 1, 'q': name}))
matches = [o for o in options['data'] if o['name'] == name]
assert len(matches) == 1 and not options.get('next_page_url')
assert matches[0]['id'] in locations_to_verify
province_name_recovery.append({'name': name, 'territory_id': matches[0]['id'], 'unique_exact_match': True})
call('POST', '/logout', status=204)
public_recovery = []
if verify_new:
for location_id in locations_to_verify:
context = call('GET', '/api/v1/investment/territory-geometries?' + urllib.parse.urlencode({'territory_id': location_id}))
assert not context['selected_geometry_available'] and context['areas'] == []
public_recovery.append({'territory_id': location_id, 'private_geometry_not_disclosed': True})
proof = {'captured_at': datetime.now(timezone.utc).isoformat(), 'target': target, 'applied': apply, 'mfa_login_completed': bool(login.get('two_factor')), 'plans': plans, 'results': results, 'consumer_recovery': consumers, 'province_name_recovery': province_name_recovery, 'public_recovery': public_recovery, 'requests': events, 'interface_observed': False, 'active_writes': 0, 'published': False}
destination = private / ('geometry-crosswalk-http-qa.json' if apply else 'geometry-crosswalk-consumers-http-qa.json' if verify_new else 'geometry-crosswalk-preview-qa.json')
if destination.exists():
destination.rename(private / (destination.stem + '-' + datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S') + '.json'))
destination.write_text(json.dumps(proof, ensure_ascii=False, indent=2), encoding='utf-8')
print(json.dumps({'target': target, 'applied': apply, 'levels': [{'level': p['level'], 'expected': p['expected'], 'resolved': p['resolved'], 'pending': p['pending']} for p in plans], 'inserted': sum(r['inserted'] for r in results), 'reused': sum(r['reused'] for r in results), 'consumers': len(consumers), 'published': False, 'interface_observed': False, 'active_writes': 0}))