mirror of
https://github.com/superdesigndev/treg.git
synced 2026-10-02 03:24:35 +08:00
146 lines
5.7 KiB
Python
146 lines
5.7 KiB
Python
"""Exercise the installed SDK and real CLI process against local ingestion."""
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
import time
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
|
|
import pytest
|
|
|
|
from treg import cli, cli_analytics
|
|
|
|
|
|
@pytest.fixture
|
|
def ingestion():
|
|
events = []
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def do_POST(self):
|
|
events.append(json.loads(self.rfile.read(int(self.headers['Content-Length']))))
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
self.wfile.write(b'{}')
|
|
|
|
def log_message(self, *args):
|
|
pass
|
|
|
|
server = ThreadingHTTPServer(('127.0.0.1', 0), Handler)
|
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
thread.start()
|
|
yield f'http://127.0.0.1:{server.server_port}', events
|
|
server.shutdown()
|
|
server.server_close()
|
|
thread.join()
|
|
|
|
|
|
def test_real_cli_sdk_delivery_and_opt_out(tmp_path, ingestion):
|
|
host, events = ingestion
|
|
env = {**os.environ, 'TREG_CONFIG': str(tmp_path / 'config.json'),
|
|
'TREG_TELEMETRY': '1', 'DO_NOT_TRACK': '0',
|
|
'PYTHONPYCACHEPREFIX': str(tmp_path / 'bytecode'),
|
|
'TREG_CLI_POSTHOG_KEY': 'phc_test', 'TREG_CLI_POSTHOG_HOST': host}
|
|
|
|
def run(*args):
|
|
# This test asserts delivery, which the production one-second best-effort budget
|
|
# cannot guarantee during cold SDK imports under xdist load. Exercise the real
|
|
# CLI/SDK with a longer test-only join; the slow-ingestion test below separately
|
|
# verifies the unchanged production exit budget.
|
|
entry = ('from treg import cli, cli_analytics; '
|
|
'cli_analytics.EXIT_WAIT_SECONDS = 10; cli.main()')
|
|
return subprocess.run([sys.executable, '-c', entry, *args], env=env,
|
|
capture_output=True, text=True, timeout=30)
|
|
|
|
result = run('version')
|
|
assert result.returncode == 0
|
|
assert result.stdout.strip() and not result.stderr
|
|
assert len(events) == 1
|
|
event = events[0]['batch'][0]
|
|
assert event['event'] == 'cli_command_completed'
|
|
props = event['properties']
|
|
assert props['command'] == 'version'
|
|
assert props['success'] is True
|
|
assert props['exit_code'] == 0
|
|
assert props['duration_ms'] >= 0
|
|
assert props['cli_version'] and props['os']
|
|
identifier = (tmp_path / 'analytics-id').read_text()
|
|
assert event.get('distinct_id', props.get('distinct_id')) == identifier
|
|
|
|
# Raw argument contents must never enter an event.
|
|
result = run('secret', 'add', 'private-name', '--value', 'secret-token')
|
|
assert result.returncode != 0
|
|
assert len(events) == 2
|
|
failed = events[-1]['batch'][0]['properties']
|
|
assert failed['command'] == 'secret_add'
|
|
assert failed['success'] is False
|
|
assert failed['exit_code'] == result.returncode
|
|
assert events[-1]['batch'][0].get('distinct_id', failed.get('distinct_id')) == identifier
|
|
assert 'private-name' not in json.dumps(events)
|
|
assert run('--help').returncode == 0
|
|
assert len(events) == 2
|
|
assert 'secret-token' not in json.dumps(events)
|
|
env['TREG_TELEMETRY'] = '0'
|
|
count = len(events)
|
|
assert run('version').returncode == 0
|
|
assert len(events) == count
|
|
assert run('--help').returncode == 0
|
|
|
|
|
|
@pytest.mark.parametrize('error, expected', [(None, 0), (SystemExit(3), 3),
|
|
(SystemExit('private error'), 1), (KeyboardInterrupt(), 130), (RuntimeError('private error'), 1)])
|
|
def test_dispatch_result_preserved(monkeypatch, tmp_path, error, expected):
|
|
def cmd_version(args, cfg):
|
|
if error is not None:
|
|
raise error
|
|
monkeypatch.setattr(cli, 'cmd_version', cmd_version)
|
|
monkeypatch.setattr(cli, 'CONFIG_PATH', tmp_path / 'config.json')
|
|
captured = []
|
|
monkeypatch.setattr(cli_analytics, 'track_command', lambda **kwargs: captured.append(kwargs))
|
|
if error is None:
|
|
cli.main(['version'])
|
|
else:
|
|
with pytest.raises(type(error)) as raised:
|
|
cli.main(['version'])
|
|
assert raised.value is error
|
|
assert captured[0]['exit_code'] == expected
|
|
assert captured[0]['command'] == 'version'
|
|
|
|
|
|
@pytest.mark.parametrize('env, base', [({'TREG_TELEMETRY': '0'}, 'https://treg.to'),
|
|
({'DO_NOT_TRACK': '1'}, 'https://treg.to'), ({}, 'https://self.example')])
|
|
def test_disabled_has_no_side_effects(monkeypatch, tmp_path, env, base):
|
|
for key in ('TREG_TELEMETRY', 'DO_NOT_TRACK', 'TREG_CLI_POSTHOG_KEY'):
|
|
monkeypatch.delenv(key, raising=False)
|
|
for key, value in env.items():
|
|
monkeypatch.setenv(key, value)
|
|
monkeypatch.setattr(cli_analytics, '_send', lambda *args: pytest.fail('unexpected send'))
|
|
cli_analytics.track_command(command='version', exit_code=0, duration_ms=1,
|
|
base_url=base, config_path=tmp_path / 'config.json')
|
|
assert not list(tmp_path.iterdir())
|
|
|
|
|
|
def test_slow_ingestion_does_not_block_exit(monkeypatch, tmp_path):
|
|
monkeypatch.setenv('TREG_TELEMETRY', '1')
|
|
monkeypatch.delenv('DO_NOT_TRACK', raising=False)
|
|
finished = threading.Event()
|
|
monkeypatch.setattr(cli_analytics, '_send', lambda *args: finished.wait(5))
|
|
started = time.monotonic()
|
|
try:
|
|
cli_analytics.track_command(command='version', exit_code=0, duration_ms=1,
|
|
base_url='https://treg.to', config_path=tmp_path / 'config.json')
|
|
assert time.monotonic() - started < 1.5
|
|
finally:
|
|
finished.set()
|
|
|
|
|
|
def test_sdk_failure_is_silent(monkeypatch, tmp_path, capsys):
|
|
import posthog
|
|
|
|
def fail(*args, **kwargs):
|
|
raise RuntimeError("ingestion unavailable")
|
|
|
|
monkeypatch.setattr(posthog, "Posthog", fail)
|
|
cli_analytics._send("phc_test", "http://127.0.0.1:1", tmp_path / "config.json", {})
|
|
assert capsys.readouterr() == ("", "")
|