strix/tests/test_cloud_session.py
itzzdev09 a3d60a72a0 test: remove Unix-only and developer-specific assumptions from the suite
A full run on Windows 11 failed 31 tests, almost all because a fixture
assumed POSIX behaviour or read the developer's own Git configuration
rather than because Strix misbehaved. That noise hides real regressions.

- Resolve git with `shutil.which` instead of `/usr/bin/env git`, and skip
  the module when no git is present. This single line accounted for 19 of
  the 31 failures.
- Isolate every git subprocess from `GIT_CONFIG_GLOBAL`/`GIT_CONFIG_SYSTEM`
  and supply a commit identity, so a global `core.excludesFile`,
  `commit.gpgSign`, or `core.hooksPath` cannot change what a test sees.
- Create control-character filenames through a fixture that skips where
  the filesystem rejects them, instead of failing to build the fixture.
- Choose the protected system directory for the platform in the mount
  policy test. `check_mountable_dir` already handles both families, so on
  Windows this now exercises the real policy instead of failing early.
- Assert secret-file permissions through a fixture that keeps the POSIX
  0o600 check exact and, on Windows, skips with the reason stated rather
  than weakening the assertion. The device-identity test is split so its
  identity contract still runs everywhere.
- Pin mypy to `platform = "linux"` so type checking resolves the same
  APIs as CI and the container target. This removes the six
  `fcntl.flock`/`os.getuid`/`os.getgid` attribute errors reported on
  Windows without adding ignores that `warn_unused_ignores` would then
  flag on Linux.

Windows now reports 12 failures, all owned elsewhere: #1258 (7-8,
intermittent), #648/#652, #1288, and #1285.

Refs #1259

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 09:28:49 +05:30

174 lines
5.7 KiB
Python

"""CLI-session lifecycle, scope, and workspace-race behavior."""
from __future__ import annotations
import json
from typing import TYPE_CHECKING, Any
import pytest
import requests
from rich.console import Console
from strix.interface import cloud, platform_cli, platform_identity
from strix.interface.cloud import http
from strix.interface.cloud import session as cloud_session
if TYPE_CHECKING:
from pathlib import Path
class Response:
def __init__(self, payload: Any = None, status_code: int = 200) -> None:
self._payload = payload
self.status_code = status_code
self.ok = 200 <= status_code < 400
self.text = json.dumps(payload) if payload is not None else ""
self.headers = {"content-type": "application/json"}
def json(self) -> Any:
return self._payload
@pytest.fixture
def auth_path(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
path = tmp_path / "platform-auth.json"
monkeypatch.setattr(platform_cli, "AUTH_PATH", path)
monkeypatch.delenv("STRIX_API_TOKEN", raising=False)
monkeypatch.delenv("STRIX_WORKSPACE_ID", raising=False)
return path
def test_http_workspace_pin_is_captured_once(
auth_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
platform_cli.save_record(
{
"api_token": "secret",
"organization_id": "org_start",
"app_url": "https://app.example.test",
}
)
assert auth_path.exists()
sent: list[dict[str, str]] = []
def fake_request(*_args: Any, **kwargs: Any) -> Response:
sent.append(dict(kwargs["headers"]))
return Response({})
monkeypatch.setattr(requests, "request", fake_request)
http.configure()
platform_cli.save_record(
{
"api_token": "secret",
"organization_id": "org_changed_elsewhere",
"app_url": "https://app.example.test",
}
)
http.request("GET", "/scans")
assert sent[0]["X-Strix-Workspace"] == "org_start"
def test_session_scope_update_persists_only_for_stored_session(
auth_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: Any
) -> None:
platform_cli.save_record(
{
"api_token": "secret",
"organization_id": "org_1",
"app_url": "https://app.example.test",
"scopes": ["scans:read"],
}
)
monkeypatch.setattr(
http,
"request",
lambda *_args, **_kwargs: Response(
{
"scopes": ["scans:read", "scans:write", "billing:read"],
"requested_scopes": ["scans:read", "scans:write", "billing:read"],
"scope_ceiling": ["scans:read", "scans:write", "billing:read"],
"scope_profile": "minimal",
}
),
)
assert cloud.run_cloud(["session", "scopes", "set", "minimal", "--json"]) == 0
stored = platform_cli.read_record()
assert stored is not None
assert stored["scope_profile"] == "minimal"
assert json.loads(capsys.readouterr().out)["scope_profile"] == "minimal"
before = auth_path.read_text(encoding="utf-8")
assert (
cloud.run_cloud(["session", "scopes", "set", "minimal", "--token", "override", "--json"])
== 0
)
assert auth_path.read_text(encoding="utf-8") == before
def test_logout_keeps_local_token_when_remote_outcome_is_not_definitive(
auth_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: Any
) -> None:
platform_cli.save_record(
{
"api_token": "secret",
"organization_id": "org_1",
"app_url": "https://app.example.test",
}
)
monkeypatch.setattr(
requests,
"delete",
lambda *_args, **_kwargs: Response({"detail": "unavailable"}, 503),
)
assert cloud.run_cloud(["logout", "--json"]) == 1
assert auth_path.exists()
assert json.loads(capsys.readouterr().out)["removed"] is False
def test_local_only_logout_is_explicit_and_recoverable(auth_path: Path, capsys: Any) -> None:
platform_cli.save_record({"api_token": "secret"})
assert cloud.run_cloud(["logout", "--local-only", "--json"]) == 0
payload = json.loads(capsys.readouterr().out)
assert payload["local_only"] is True
assert payload["remotely_revoked"] is False
assert not auth_path.exists()
def test_session_json_errors_preserve_machine_readable_server_details(capsys: Any) -> None:
error = http.CloudError(
"workspace changed",
payload={
"detail": "workspace changed",
"code": "workspace_session_changed",
"current_organization_id": "org_current",
},
)
assert cloud_session._error(Console(), error, as_json=True) == http.EXIT_ERROR
payload = json.loads(capsys.readouterr().out)
assert payload == {
"code": "workspace_session_changed",
"current_organization_id": "org_current",
"error": "workspace changed",
}
def test_cli_device_identity_is_stable_and_privacy_safe(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
path = tmp_path / "cli-identity.json"
monkeypatch.setattr(platform_identity, "IDENTITY_PATH", path)
first = platform_identity.read_or_create_identity()
second = platform_identity.read_or_create_identity(device_name=" Build laptop ")
assert second["client_instance_id"] == first["client_instance_id"]
assert second["device_name"] == "Build laptop"
def test_cli_identity_file_is_owner_only(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, assert_secret_file_permissions: Any
) -> None:
path = tmp_path / "cli-identity.json"
monkeypatch.setattr(platform_identity, "IDENTITY_PATH", path)
platform_identity.read_or_create_identity()
assert_secret_file_permissions(path)