From d6d75d1d04127192ba2fb14691e2b4eafa1fd839 Mon Sep 17 00:00:00 2001 From: JPtheOne Date: Wed, 27 Aug 2025 13:09:22 -0600 Subject: [PATCH 1/4] First stable version of unified network command. --- Dockerfile | 2 +- cli/app.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7e12522..2de91a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM python:3-slim-bullseye -RUN apt-get update && apt-get install -y htop iotop iftop net-tools sysstat procps coreutils grep sed gawk curl wget && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y htop iotop iftop net-tools sysstat procps coreutils grep sed gawk curl wget iputils-ping traceroute mtr-tiny dnsutils iproute2 iperf3 curl && rm -rf /var/lib/apt/lists/* WORKDIR /app diff --git a/cli/app.py b/cli/app.py index d6ae4fe..779749b 100644 --- a/cli/app.py +++ b/cli/app.py @@ -6,7 +6,6 @@ app = typer.Typer(help = 'Surge - A DevOps CLI Tool For System Monitoring and Production Reliability') - def run_cmd(cmd: str) -> str: """ Helper function to abstract lengthy subprocess command implementation :D @@ -119,17 +118,78 @@ def monitor( print('---------------------') print(f'Size: {size} | Used: {used} | Available: {available} | Usage: {percent}') - -@app.command() +@app.command("network") def network( - url: Annotated[str, typer.Argument(help = 'URL to test network/API metrics')], - requests: Annotated[int, typer.Option('-n', '--count', help = 'Number of requests to send')] = 5 + url: Annotated[str, typer.Option("-u", "--url", help="HTTP URL to test (curl)")], + host: Annotated[str, typer.Option("-h", "--host", help="Host/IP for ping/trace")], + domain: Annotated[str, typer.Option("-d", "--domain", help="Domain for DNS lookup")], + requests: Annotated[int, typer.Option("-n", "--count", help="Ping echo requests")] = 5, + dtype: Annotated[str, typer.Option("-t", "--type", help="DNS record type (A, AAAA, MX, TXT, etc.)")] = "A", + listening: Annotated[bool, typer.Option("-l", "--listening", help="Show only listening sockets")] = False, + bw_server: Annotated[str | None, typer.Option("-s", "--bw-server", help="iperf3 server (optional)")] = None, + bw_time: Annotated[int, typer.Option("-T", "--bw-time", help="iperf3 duration (sec)")] = 10, + quick: Annotated[bool, typer.Option("-q", "--quick", help="Skip traceroute and sockets")] = False, ): """ - Run basic network/API tests with a number of requests. + One-stop network check: ping, traceroute, HTTP (curl), DNS, sockets, and optional iperf3. """ - print(f'Testing network connection to {url} with {requests} requests.') - # TODO: Add http requests or use curl through subprocess + + def header(title: str): + print(f"\n[bold]{title}[/bold]") + print("—" * len(title)) + + # ---- ping ---- + header("Ping") + # Linux-style -c; if your environment is Windows-not-in-Docker, switch to 'ping -n' + ping_out = run_cmd(f"ping -c {requests} {host}") + if not ping_out: + ping_out = "[warn] ping not available or no output" + print(ping_out) + + # ---- traceroute / mtr (skip if quick) ---- + if not quick: + header("Traceroute") + trace_out = run_cmd(f"traceroute {host}") + if not trace_out: + # fallback to mtr if available + trace_out = run_cmd(f"mtr -r {host}") + if not trace_out: + trace_out = "[warn] traceroute/mtr not available or no output" + print(trace_out) + + # ---- http (curl) ---- + header("HTTP (curl)") + http_out = run_cmd(f"curl -s -i {url}") + if not http_out: + http_out = "[warn] curl not available or no output" + print(http_out) + + # ---- dns (dig/nslookup) ---- + header("DNS") + dns_out = run_cmd(f"dig +short {domain} {dtype}") + if not dns_out: + dns_out = run_cmd(f"nslookup -type={dtype} {domain}") + if not dns_out: + dns_out = "[warn] dig/nslookup not available or no output" + print(dns_out) + + # ---- sockets (ss) (skip if quick) ---- + if not quick: + header("Sockets (ss)") + ss_cmd = "ss -tulwn" if listening else "ss -tupan" + ss_out = run_cmd(ss_cmd) + if not ss_out: + ss_out = "[warn] ss not available or no output" + print(ss_out) + + # ---- bandwidth (iperf3) ---- + if bw_server: + header("Bandwidth (iperf3)") + iperf_out = run_cmd(f"iperf3 -c {bw_server} -t {bw_time}") + if not iperf_out: + iperf_out = "[warn] iperf3 not available or no output" + print(iperf_out) + if __name__ == "__main__": app() \ No newline at end of file From 1dbb411de2716a2104ea3c2c2ce9177b71a9db9c Mon Sep 17 00:00:00 2001 From: JPtheOne Date: Wed, 27 Aug 2025 13:19:35 -0600 Subject: [PATCH 2/4] Allows running INDIVIDUALLY ping, traceroute, curl, DNS, sockets, or iperf3 selectively with simple flags, similar to the existing monitor command. --- cli/app.py | 100 +++++++++++++++++++++++------------------------------ 1 file changed, 44 insertions(+), 56 deletions(-) diff --git a/cli/app.py b/cli/app.py index 779749b..7ef5552 100644 --- a/cli/app.py +++ b/cli/app.py @@ -120,76 +120,64 @@ def monitor( @app.command("network") def network( - url: Annotated[str, typer.Option("-u", "--url", help="HTTP URL to test (curl)")], - host: Annotated[str, typer.Option("-h", "--host", help="Host/IP for ping/trace")], - domain: Annotated[str, typer.Option("-d", "--domain", help="Domain for DNS lookup")], - requests: Annotated[int, typer.Option("-n", "--count", help="Ping echo requests")] = 5, + url: Annotated[str | None, typer.Option("-u", "--url", help="HTTP URL to test (curl)", show_default=False)] = None, + host: Annotated[str | None, typer.Option("-h", "--host", help="Host/IP for ping and traceroute", show_default=False)] = None, + domain: Annotated[str | None, typer.Option("-d", "--domain", help="Domain for DNS lookup", show_default=False)] = None, + requests: Annotated[int, typer.Option("-n", "--count", help="Number of ICMP echo requests")] = 5, dtype: Annotated[str, typer.Option("-t", "--type", help="DNS record type (A, AAAA, MX, TXT, etc.)")] = "A", - listening: Annotated[bool, typer.Option("-l", "--listening", help="Show only listening sockets")] = False, - bw_server: Annotated[str | None, typer.Option("-s", "--bw-server", help="iperf3 server (optional)")] = None, - bw_time: Annotated[int, typer.Option("-T", "--bw-time", help="iperf3 duration (sec)")] = 10, - quick: Annotated[bool, typer.Option("-q", "--quick", help="Skip traceroute and sockets")] = False, + sockets: Annotated[bool, typer.Option("--sockets", help="Show socket information (ss)")] = False, + bw_server: Annotated[str | None, typer.Option("--bw-server", help="iperf3 server (optional)")] = None, + bw_time: Annotated[int, typer.Option("--bw-time", help="iperf3 duration in seconds")] = 10, + no_trace: Annotated[bool, typer.Option("--no-trace", help="Skip traceroute/mtr when --host is set")] = False, ): """ - One-stop network check: ping, traceroute, HTTP (curl), DNS, sockets, and optional iperf3. + Flexible network diagnostics: run only what you request. + Provide one or more of: --host, --url, --domain, --sockets, --bw-server. """ def header(title: str): print(f"\n[bold]{title}[/bold]") - print("—" * len(title)) - - # ---- ping ---- - header("Ping") - # Linux-style -c; if your environment is Windows-not-in-Docker, switch to 'ping -n' - ping_out = run_cmd(f"ping -c {requests} {host}") - if not ping_out: - ping_out = "[warn] ping not available or no output" - print(ping_out) - - # ---- traceroute / mtr (skip if quick) ---- - if not quick: - header("Traceroute") - trace_out = run_cmd(f"traceroute {host}") - if not trace_out: - # fallback to mtr if available - trace_out = run_cmd(f"mtr -r {host}") - if not trace_out: - trace_out = "[warn] traceroute/mtr not available or no output" - print(trace_out) + print("-" * len(title)) + + # guard: require at least one section + if not any([host, url, domain, sockets, bw_server]): + print("[warn] Nothing to do. Provide at least one of: --host, --url, --domain, --sockets, --bw-server") + raise typer.Exit(code=1) + + # ---- ping / traceroute ---- + if host: + header("Ping") + ping_out = run_cmd(f"ping -c {requests} {host}") + print(ping_out or "[warn] ping not available or produced no output") + + if not no_trace: + header("Traceroute") + trace_out = run_cmd(f"traceroute {host}") or run_cmd(f"mtr -r {host}") + print(trace_out or "[warn] traceroute/mtr not available or produced no output") # ---- http (curl) ---- - header("HTTP (curl)") - http_out = run_cmd(f"curl -s -i {url}") - if not http_out: - http_out = "[warn] curl not available or no output" - print(http_out) - - # ---- dns (dig/nslookup) ---- - header("DNS") - dns_out = run_cmd(f"dig +short {domain} {dtype}") - if not dns_out: - dns_out = run_cmd(f"nslookup -type={dtype} {domain}") - if not dns_out: - dns_out = "[warn] dig/nslookup not available or no output" - print(dns_out) - - # ---- sockets (ss) (skip if quick) ---- - if not quick: + if url: + header("HTTP (curl)") + http_out = run_cmd(f"curl -s -i {url}") + print(http_out or "[warn] curl not available or produced no output") + + # ---- dns ---- + if domain: + header("DNS") + dns_out = run_cmd(f"dig +short {domain} {dtype}") or run_cmd(f"nslookup -type={dtype} {domain}") + print(dns_out or "[warn] dig/nslookup not available or produced no output") + + # ---- sockets (ss) ---- + if sockets: header("Sockets (ss)") - ss_cmd = "ss -tulwn" if listening else "ss -tupan" - ss_out = run_cmd(ss_cmd) - if not ss_out: - ss_out = "[warn] ss not available or no output" - print(ss_out) + ss_out = run_cmd("ss -tulwn") + print(ss_out or "[warn] ss not available or produced no output") # ---- bandwidth (iperf3) ---- if bw_server: header("Bandwidth (iperf3)") iperf_out = run_cmd(f"iperf3 -c {bw_server} -t {bw_time}") - if not iperf_out: - iperf_out = "[warn] iperf3 not available or no output" - print(iperf_out) - - + print(iperf_out or "[warn] iperf3 not available or produced no output") + if __name__ == "__main__": app() \ No newline at end of file From cdb0dd2ca77d2a03f12ccac88b010062a84f9d0b Mon Sep 17 00:00:00 2001 From: JPtheOne Date: Fri, 29 Aug 2025 14:50:34 -0600 Subject: [PATCH 3/4] =?UTF-8?q?olish=20network:=20validate=20empty=20-u/-h?= =?UTF-8?q?/-d,=20run=20all=20requested=20sections,=20add=20SRE-friendly?= =?UTF-8?q?=20output=20(ping=20summary,=20curl=20brief,=20condensed=20trac?= =?UTF-8?q?eroute/mtr,=20sockets),=20and=20clarify=20traceroute=E2=86=92mt?= =?UTF-8?q?r=20fallback.=20Remove=20iperf3,=20add=20tests,=20and=20ensure?= =?UTF-8?q?=20required=20tools=20(ping/traceroute/mtr/curl/dig/ss)=20are?= =?UTF-8?q?=20packaged.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 5 +-- cli/app.py | 82 +++++++++++++++++++++++++++++---------- cli/tests/test_network.py | 73 ++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 23 deletions(-) create mode 100644 cli/tests/test_network.py diff --git a/Dockerfile b/Dockerfile index 2de91a3..ae64bf8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,14 @@ FROM python:3-slim-bullseye -RUN apt-get update && apt-get install -y htop iotop iftop net-tools sysstat procps coreutils grep sed gawk curl wget iputils-ping traceroute mtr-tiny dnsutils iproute2 iperf3 curl && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y --no-install-recommends iputils-ping traceroute mtr-tiny curl dnsutils iproute2 htop iotop iftop net-tools sysstat procps coreutils grep sed gawk wget && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY cli/requirements.txt . - RUN pip install --no-cache-dir -r requirements.txt +RUN pip install --no-cache-dir pytest COPY cli /app/cli ENTRYPOINT ["python", "cli/app.py"] - CMD [""] diff --git a/cli/app.py b/cli/app.py index 7ef5552..40f1398 100644 --- a/cli/app.py +++ b/cli/app.py @@ -126,58 +126,100 @@ def network( requests: Annotated[int, typer.Option("-n", "--count", help="Number of ICMP echo requests")] = 5, dtype: Annotated[str, typer.Option("-t", "--type", help="DNS record type (A, AAAA, MX, TXT, etc.)")] = "A", sockets: Annotated[bool, typer.Option("--sockets", help="Show socket information (ss)")] = False, - bw_server: Annotated[str | None, typer.Option("--bw-server", help="iperf3 server (optional)")] = None, - bw_time: Annotated[int, typer.Option("--bw-time", help="iperf3 duration in seconds")] = 10, no_trace: Annotated[bool, typer.Option("--no-trace", help="Skip traceroute/mtr when --host is set")] = False, ): """ Flexible network diagnostics: run only what you request. - Provide one or more of: --host, --url, --domain, --sockets, --bw-server. + Provide one or more of: --host, --url, --domain, --sockets. """ def header(title: str): print(f"\n[bold]{title}[/bold]") print("-" * len(title)) - # guard: require at least one section - if not any([host, url, domain, sockets, bw_server]): - print("[warn] Nothing to do. Provide at least one of: --host, --url, --domain, --sockets, --bw-server") + def warn(msg: str): + print(f"[warn] {msg}") + + def normalize_url(u: str) -> str: + return u if u.startswith(("http://", "https://")) else f"http://{u}" + + def curl_brief(u: str) -> str: + fmt = "HTTP %{http_code} | total %{time_total}s | connect %{time_connect}s | ttfb %{time_starttransfer}s\n" + return run_cmd(f'curl -s -o /dev/null -w "{fmt}" {u}') + + def summarize_ping(out: str) -> str: + lines = out.splitlines() + sent = loss = avg = None + for ln in lines: + if "packets transmitted" in ln and "packet loss" in ln: + parts = ln.replace(",", "").split() + try: + sent = int(parts[0]); loss = parts[6] + except Exception: + pass + if "rtt min/avg/max" in ln or "round-trip min/avg/max" in ln: + try: + avg = ln.split("=")[1].split("/")[1].strip() + except Exception: + pass + bits = [] + if sent is not None: bits.append(f"sent={sent}") + if loss is not None: bits.append(f"loss={loss}") + if avg is not None: bits.append(f"avg_rtt_ms={avg}") + return " | ".join(bits) if bits else (out.strip()[:200] if out else "") + + def summarize_trace(out: str, max_lines: int = 12) -> str: + lines = [l for l in out.splitlines() if l.strip()] + if len(lines) <= max_lines: + return out + return "\n".join(lines[:6] + ["..."] + lines[-6:]) + + # --- validate empty values FIRST --- + if host is not None and not str(host).strip(): + warn("--host was provided but empty") + raise typer.Exit(code=2) + if url is not None and not str(url).strip(): + warn("--url was provided but empty") + raise typer.Exit(code=2) + if domain is not None and not str(domain).strip(): + warn("--domain was provided but empty") + raise typer.Exit(code=2) + + # --- then require at least one section --- + if not any([host, url, domain, sockets]): + warn("Nothing to do. Provide at least one of: --host, --url, --domain, --sockets") raise typer.Exit(code=1) - # ---- ping / traceroute ---- + # ---- ping / traceroute (or mtr -r fallback) ---- if host: header("Ping") ping_out = run_cmd(f"ping -c {requests} {host}") - print(ping_out or "[warn] ping not available or produced no output") + print(summarize_ping(ping_out) if ping_out else "[warn] ping not available or produced no output") if not no_trace: header("Traceroute") trace_out = run_cmd(f"traceroute {host}") or run_cmd(f"mtr -r {host}") - print(trace_out or "[warn] traceroute/mtr not available or produced no output") + print(summarize_trace(trace_out) if trace_out else "[warn] traceroute/mtr not available or produced no output") # ---- http (curl) ---- if url: header("HTTP (curl)") - http_out = run_cmd(f"curl -s -i {url}") - print(http_out or "[warn] curl not available or produced no output") + u = normalize_url(url) + print(curl_brief(u)) + headers = run_cmd(f"curl -s -I {u}") + print(headers.strip() if headers else "[warn] curl not available or produced no output") # ---- dns ---- if domain: header("DNS") dns_out = run_cmd(f"dig +short {domain} {dtype}") or run_cmd(f"nslookup -type={dtype} {domain}") - print(dns_out or "[warn] dig/nslookup not available or produced no output") + print(dns_out.strip() if dns_out else "[warn] dig/nslookup not available or produced no output") # ---- sockets (ss) ---- if sockets: header("Sockets (ss)") - ss_out = run_cmd("ss -tulwn") - print(ss_out or "[warn] ss not available or produced no output") - - # ---- bandwidth (iperf3) ---- - if bw_server: - header("Bandwidth (iperf3)") - iperf_out = run_cmd(f"iperf3 -c {bw_server} -t {bw_time}") - print(iperf_out or "[warn] iperf3 not available or produced no output") + ss_out = run_cmd("ss -tulwn | head -n 30") + print(ss_out if ss_out else "[warn] ss not available or produced no output") if __name__ == "__main__": app() \ No newline at end of file diff --git a/cli/tests/test_network.py b/cli/tests/test_network.py new file mode 100644 index 0000000..cc9a069 --- /dev/null +++ b/cli/tests/test_network.py @@ -0,0 +1,73 @@ +import click +import types +from typer.testing import CliRunner + +import cli.app as appmod +runner = CliRunner() + +def run_cmd_spy_factory(): + calls = [] + def _spy(cmd: str): + calls.append(cmd) + if cmd.startswith("ping "): + return "5 packets transmitted, 5 received, 0% packet loss\nrtt min/avg/max/mdev = 10/11/12/0.3 ms" + if cmd.startswith("traceroute "): + return "traceroute to host\n1 a\n2 b\n3 c" + if cmd.startswith("mtr -r "): + return "Start: mtr report\n1. a\n2. b" + if cmd.startswith("curl -s -o /dev/null"): + return "HTTP 200 | total 0.123s | connect 0.010s | ttfb 0.050s\n" + if cmd.startswith("curl -s -I "): + return "HTTP/1.1 200 OK\nServer: test\n" + if cmd.startswith("dig +short "): + return "93.184.216.34\n" + if cmd.startswith("nslookup "): + return "Server: 8.8.8.8\nName: example.com\nAddress: 93.184.216.34\n" + if cmd.startswith("ss -tulwn"): + return "Netid State Local Address:Port Peer Address:Port\n" + return "" + _spy.calls = calls + return _spy + +def test_network_runs_all_sections(monkeypatch, capsys): + spy = run_cmd_spy_factory() + monkeypatch.setattr(appmod, "run_cmd", spy) + appmod.network(url="http://example.com", host="1.1.1.1", domain="example.com", sockets=True) + out = capsys.readouterr().out + assert "Ping" in out + assert "Traceroute" in out + assert "HTTP (curl)" in out + assert "DNS" in out + assert "Sockets (ss)" in out + assert "HTTP 200 | total" in out + +def test_empty_flags_fail_fast(monkeypatch): + spy = run_cmd_spy_factory() + monkeypatch.setattr(appmod, "run_cmd", spy) + # url vacío debe fallar con exit_code 2 (click.exceptions.Exit) + try: + appmod.network(url="", host=None, domain=None, sockets=False) + assert False, "Expected click.exceptions.Exit" + except click.exceptions.Exit as e: + assert e.exit_code == 2 + +def test_traceroute_then_mtr_fallback(monkeypatch, capsys): + def run_cmd_fake(cmd: str): + if cmd.startswith("ping "): + return "5 packets transmitted, 5 received, 0% packet loss\nrtt min/avg/max/mdev = 10/11/12/0.3 ms" + if cmd.startswith("traceroute "): + return "" # force fallback + if cmd.startswith("mtr -r "): + return "Start: mtr report\n1. a\n2. b" + return "" + monkeypatch.setattr(appmod, "run_cmd", run_cmd_fake) + appmod.network(host="1.1.1.1") + out = capsys.readouterr().out + assert "mtr report" in out + +def test_cli_invocation_smoke(monkeypatch): + spy = run_cmd_spy_factory() + monkeypatch.setattr(appmod, "run_cmd", spy) + result = runner.invoke(appmod.app, ["network", "-u", "http://example.com", "-h", "1.1.1.1", "-d", "example.com", "--sockets"]) + assert result.exit_code == 0 + assert "HTTP (curl)" in result.stdout From 94dfd08bc67501bfa0ec0abb1fc7c17de9f8aebb Mon Sep 17 00:00:00 2001 From: sidsun1 Date: Fri, 29 Aug 2025 14:47:50 -0700 Subject: [PATCH 4/4] fix: cleaned up tests/resolved merge conflicts w/ network-related items --- cli/app.py | 22 +++++++++---------- tests/test_app.py | 32 ---------------------------- {cli/tests => tests}/test_network.py | 1 - 3 files changed, 11 insertions(+), 44 deletions(-) rename {cli/tests => tests}/test_network.py (99%) diff --git a/cli/app.py b/cli/app.py index 5f034e5..83e1fe8 100644 --- a/cli/app.py +++ b/cli/app.py @@ -176,7 +176,8 @@ def summarize_ping(out: str) -> str: if "packets transmitted" in ln and "packet loss" in ln: parts = ln.replace(",", "").split() try: - sent = int(parts[0]); loss = parts[6] + sent = int(parts[0]) + loss = parts[6] except Exception: pass if "rtt min/avg/max" in ln or "round-trip min/avg/max" in ln: @@ -185,13 +186,18 @@ def summarize_ping(out: str) -> str: except Exception: pass bits = [] - if sent is not None: bits.append(f"sent={sent}") - if loss is not None: bits.append(f"loss={loss}") - if avg is not None: bits.append(f"avg_rtt_ms={avg}") + + if sent is not None: + bits.append(f"sent={sent}") + if loss is not None: + bits.append(f"loss={loss}") + if avg is not None: + bits.append(f"avg_rtt_ms={avg}") + return " | ".join(bits) if bits else (out.strip()[:200] if out else "") def summarize_trace(out: str, max_lines: int = 12) -> str: - lines = [l for l in out.splitlines() if l.strip()] + lines = [line for line in out.splitlines() if line.strip()] if len(lines) <= max_lines: return out return "\n".join(lines[:6] + ["..."] + lines[-6:]) @@ -243,11 +249,5 @@ def summarize_trace(out: str, max_lines: int = 12) -> str: ss_out = run_cmd("ss -tulwn") print(ss_out or "[warn] ss not available or produced no output") - # ---- bandwidth (iperf3) ---- - if bw_server: - header("Bandwidth (iperf3)") - iperf_out = run_cmd(f"iperf3 -c {bw_server} -t {bw_time}") - print(iperf_out or "[warn] iperf3 not available or produced no output") - if __name__ == "__main__": app() diff --git a/tests/test_app.py b/tests/test_app.py index a33bf43..db6bd47 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -144,35 +144,3 @@ def test_monitor_empty_flag(self, runner): assert result.exit_code != 0 assert isinstance(result.exception, SystemExit) assert result.exit_code == 2 - - def test_network_command_prints_message(self, runner): - result = runner.invoke( - app_mod.app, ["network", "https://example.com", "--count", "2"] - ) - assert result.exit_code == 0 - assert ( - "Testing network connection to https://example.com with 2 requests." - in result.stdout - ) - - def test_network_command_with_defaults(self, runner): - result = runner.invoke(app_mod.app, ["network", "https://example.com"]) - assert result.exit_code == 0 - assert ( - "Testing network connection to https://example.com with 5 requests." - in result.stdout - ) - - def test_network_missing_url_errors(self, runner): - result = runner.invoke(app_mod.app, ["network"]) - assert result.exit_code != 0 - assert isinstance(result.exception, SystemExit) - assert result.exit_code == 2 - - def test_network_invalid_count_errors(self, runner): - result = runner.invoke( - app_mod.app, ["network", "https://example.com", "--count", "-3"] - ) - assert result.exit_code != 0 - assert isinstance(result.exception, SystemExit) - assert result.exit_code == 2 diff --git a/cli/tests/test_network.py b/tests/test_network.py similarity index 99% rename from cli/tests/test_network.py rename to tests/test_network.py index cc9a069..abd25d0 100644 --- a/cli/tests/test_network.py +++ b/tests/test_network.py @@ -1,5 +1,4 @@ import click -import types from typer.testing import CliRunner import cli.app as appmod