audit-labs/audit-tools

A collection of scripts, queries, and other goodies you can use in an audit.

clone: git clone https://gitbay.org/audit-labs/audit-tools.git

v1.0.0: tui/tests/test_app.py · raw

  1"""Headless smoke tests for the Textual app.
  2
  3Drives the app through its screens with a Pilot, stubbing the network-bound
  4run_audit so no real GitHub calls are made. Uses asyncio.run so the suite does
  5not require the pytest-asyncio plugin.
  6"""
  7
  8import asyncio
  9
 10from textual.widgets import Button, Input
 11
 12from tui import github_runner as gh
 13from tui.app import AuditApp, ChecksScreen, ConfigScreen, MenuScreen, RunScreen
 14
 15
 16def _run(coro):
 17    asyncio.run(coro)
 18
 19
 20def test_full_navigation(monkeypatch):
 21    def fake_run_audit(*, org, token, output_dir, branch, selected_keys, on_event):
 22        on_event(gh.ProgressEvent("start", "Member roster"))
 23        on_event(gh.ProgressEvent("done", "Member roster", count=3))
 24        on_event(gh.ProgressEvent("summary", output_dir, count=3))
 25        return [("Member roster", 3)]
 26
 27    monkeypatch.setattr("tui.github_runner.run_audit", fake_run_audit)
 28
 29    async def scenario():
 30        app = AuditApp()
 31        async with app.run_test(size=(120, 40)) as pilot:
 32            await pilot.pause()
 33            assert isinstance(app.screen, MenuScreen)
 34
 35            await pilot.click("#github")
 36            await pilot.pause()
 37            assert isinstance(app.screen, ConfigScreen)
 38
 39            app.screen.query_one("#org", Input).value = "acme"
 40            app.screen.query_one("#token", Input).value = "tok"
 41            await pilot.click("#continue")
 42            await pilot.pause()
 43            assert isinstance(app.screen, ChecksScreen)
 44            assert app.settings["org"] == "acme"
 45
 46            await pilot.click("#run")
 47            await pilot.pause()
 48            assert isinstance(app.screen, RunScreen)
 49
 50            await app.workers.wait_for_complete()
 51            await pilot.pause()
 52
 53            # When the run finishes, the exit buttons become enabled.
 54            assert app.screen.query_one("#menu", Button).disabled is False
 55            assert app.screen.query_one("#quit", Button).disabled is False
 56
 57    _run(scenario())
 58
 59
 60def test_config_requires_org_and_token(monkeypatch):
 61    # Make sure env vars don't pre-fill the fields for this test.
 62    monkeypatch.delenv("GITHUB_ORG", raising=False)
 63    monkeypatch.delenv("GITHUB_TOKEN", raising=False)
 64
 65    async def scenario():
 66        app = AuditApp()
 67        async with app.run_test(size=(120, 40)) as pilot:
 68            await pilot.click("#github")
 69            await pilot.pause()
 70            # Continue with empty fields -> stays on ConfigScreen with an error.
 71            await pilot.click("#continue")
 72            await pilot.pause()
 73            assert isinstance(app.screen, ConfigScreen)
 74            error_text = str(app.screen.query_one("#form-error").render())
 75            assert "provide" in error_text.lower()
 76
 77    _run(scenario())
 78
 79
 80def test_gitlab_is_enabled():
 81    async def scenario():
 82        app = AuditApp()
 83        async with app.run_test(size=(120, 40)) as pilot:
 84            await pilot.pause()
 85            assert app.screen.query_one("#gitlab", Button).disabled is False
 86
 87    _run(scenario())
 88
 89
 90def test_gitlab_navigation(monkeypatch):
 91    monkeypatch.delenv("GITLAB_GROUP", raising=False)
 92    monkeypatch.delenv("GITLAB_TOKEN", raising=False)
 93
 94    def fake_run_audit(*, group, token, base_url, output_dir, selected_keys, on_event):
 95        on_event(gh.ProgressEvent("done", "Group members", count=7))
 96        on_event(gh.ProgressEvent("summary", output_dir, count=7))
 97        return [("Group members", 7)]
 98
 99    monkeypatch.setattr("tui.gitlab_runner.run_audit", fake_run_audit)
100
101    async def scenario():
102        app = AuditApp()
103        async with app.run_test(size=(120, 40)) as pilot:
104            await pilot.pause()
105            await pilot.click("#gitlab")
106            await pilot.pause()
107            assert isinstance(app.screen, ConfigScreen)
108
109            app.screen.query_one("#group", Input).value = "my-group"
110            app.screen.query_one("#token", Input).value = "glpat-x"
111            await pilot.click("#continue")
112            await pilot.pause()
113            assert isinstance(app.screen, ChecksScreen)
114            assert app.settings["group"] == "my-group"
115            # Self-hosted URL defaults to gitlab.com.
116            assert app.settings["base_url"] == "https://gitlab.com/api/v4"
117
118            await pilot.click("#run")
119            await pilot.pause()
120            assert isinstance(app.screen, RunScreen)
121            assert "gitlab_audit_my-group" in app.screen.output_dir
122
123            await app.workers.wait_for_complete()
124            await pilot.pause()
125            assert app.screen.query_one("#menu", Button).disabled is False
126
127    _run(scenario())
128
129
130def test_aws_navigation(monkeypatch):
131    for var in ("AWS_PROFILE", "AWS_DEFAULT_REGION", "AWS_AUDIT_ACCOUNT"):
132        monkeypatch.delenv(var, raising=False)
133
134    def fake_run_audit(
135        *, profile, region, account, output_dir, selected_keys, on_event
136    ):
137        on_event(gh.ProgressEvent("done", "IAM users", count=5))
138        on_event(gh.ProgressEvent("summary", output_dir, count=5))
139        return [("IAM users", 5)]
140
141    monkeypatch.setattr("tui.aws_runner.run_audit", fake_run_audit)
142
143    async def scenario():
144        app = AuditApp()
145        async with app.run_test(size=(120, 40)) as pilot:
146            await pilot.pause()
147            await pilot.click("#aws")
148            await pilot.pause()
149            assert isinstance(app.screen, ConfigScreen)
150
151            # AWS has no required fields — continue with defaults (default chain).
152            await pilot.click("#continue")
153            await pilot.pause()
154            assert isinstance(app.screen, ChecksScreen)
155            assert app.settings["profile"] == ""
156
157            await pilot.click("#run")
158            await pilot.pause()
159            assert isinstance(app.screen, RunScreen)
160            # Empty profile renders as "default" in the folder name.
161            assert "aws_audit_default" in app.screen.output_dir
162
163            await app.workers.wait_for_complete()
164            await pilot.pause()
165            assert app.screen.query_one("#menu", Button).disabled is False
166
167    _run(scenario())