diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6219cb5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,83 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + - name: Run Ruff + run: uv run --project stapler-scripts ruff check stapler-scripts/ + + test-har2img: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + - name: Run har2img tests + run: | + export PYTHONPATH=$PYTHONPATH:$(pwd)/stapler-scripts + uv run --project stapler-scripts pytest stapler-scripts/test_har2img.py + + test-display-switch: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + - name: Run display-switch tests + run: | + cd stapler-scripts/display-switch + uv run pytest + + test-ark-mod-manager: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + - name: Run ark-mod-manager tests + run: | + cd stapler-scripts/ark-mod-manager + uv run pytest + + test-claude-proxy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + - name: Run claude-proxy tests + run: | + cd stapler-scripts/claude-proxy + uv run python -m pytest test_providers.py + + bazel-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Bazel + uses: bazelbuild/setup-bazel@v2 + with: + bazelrc: | + common --color=yes + - name: Run Bazel Tests + run: bazel test //stapler-scripts/... diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..b6bcdee --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1 @@ +# Empty build file for the root diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..beede12 --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,14 @@ +workspace(name = "stapler_scripts_repo") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +# Rules Python for Python support in Bazel +http_archive( + name = "rules_python", + sha256 = "6f0b4070a25b774f2ec18683e336e4f3a73c17822986423c8a98d363717208d1", + strip_prefix = "rules_python-0.31.0", + url = "https://github.com/bazelbuild/rules_python/releases/download/0.31.0/rules_python-0.31.0.tar.gz", +) + +load("@rules_python//python:repositories.bzl", "py_repositories") +py_repositories() diff --git a/stapler-scripts/BUILD.bazel b/stapler-scripts/BUILD.bazel new file mode 100644 index 0000000..72e90e7 --- /dev/null +++ b/stapler-scripts/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test") + +py_library( + name = "har2img_lib", + srcs = ["har2img.py"], + visibility = ["//visibility:public"], +) + +py_binary( + name = "har2img", + srcs = ["har2img.py"], + main = "har2img.py", + deps = [":har2img_lib"], +) + +py_test( + name = "test_har2img", + srcs = ["test_har2img.py"], + deps = [ + ":har2img_lib", + ], + main = "test_har2img.py", +) diff --git a/stapler-scripts/har2img.py b/stapler-scripts/har2img.py index 82ed9e6..c48a0cc 100755 --- a/stapler-scripts/har2img.py +++ b/stapler-scripts/har2img.py @@ -10,20 +10,7 @@ def parse_args(): parser.add_argument("-o", "--output", type=str, default="imgs", help="Directory where images will be saved. Default is 'imgs'.") return parser.parse_args() -def main(): - args = parse_args() - - # Make sure the output directory exists before running! - folder = os.path.abspath(args.output) - os.makedirs(folder, exist_ok=True) - - try: - with open(args.input, "r") as f: - har = json.load(f) - except (FileNotFoundError, json.JSONDecodeError) as e: - print(f"Error reading HAR file: {e}") - return - +def process_har(har, folder): entries = har.get("log", {}).get("entries", []) if not entries: print("No entries found in HAR file.") @@ -57,5 +44,21 @@ def main(): except Exception as e: print(f"Error writing file {file_path}: {e}") +def main(): + args = parse_args() + + # Make sure the output directory exists before running! + folder = os.path.abspath(args.output) + os.makedirs(folder, exist_ok=True) + + try: + with open(args.input, "r") as f: + har = json.load(f) + except (FileNotFoundError, json.JSONDecodeError) as e: + print(f"Error reading HAR file: {e}") + return + + process_har(har, folder) + if __name__ == "__main__": main() diff --git a/stapler-scripts/pyproject.toml b/stapler-scripts/pyproject.toml new file mode 100644 index 0000000..c11b067 --- /dev/null +++ b/stapler-scripts/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "stapler-scripts" +version = "0.1.0" +description = "A collection of utility scripts for managing stapler services." +readme = "README.md" +requires-python = ">=3.12" +dependencies = [] + +[dependency-groups] +dev = [ + "pytest>=9.0.2", + "ruff>=0.4.0", +] diff --git a/stapler-scripts/test_har2img.py b/stapler-scripts/test_har2img.py new file mode 100644 index 0000000..f7e9bd6 --- /dev/null +++ b/stapler-scripts/test_har2img.py @@ -0,0 +1,149 @@ +import os +import json +import base64 +import pytest +import sys +from har2img import process_har + +def test_process_har_success(tmp_path): + output_dir = tmp_path / "imgs" + output_dir.mkdir() + + # Mock data: a simple transparent 1x1 PNG image + # iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg== + image_content = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==" + + har_data = { + "log": { + "entries": [ + { + "request": {"url": "http://example.com/test.png"}, + "response": { + "content": { + "mimeType": "image/png", + "text": image_content + } + } + } + ] + } + } + + process_har(har_data, str(output_dir)) + + output_file = output_dir / "test.png.png" + assert output_file.exists() + with open(output_file, "rb") as f: + assert f.read() == base64.b64decode(image_content) + +def test_process_har_key_error(tmp_path, capsys): + output_dir = tmp_path / "imgs" + output_dir.mkdir() + + har_data = { + "log": { + "entries": [ + { + # Missing 'request' key + "response": { + "content": { + "mimeType": "image/png", + "text": "somebase64" + } + } + }, + { + # Missing 'mimeType' + "request": {"url": "http://example.com/test2.png"}, + "response": { + "content": { + "text": "somebase64" + } + } + } + ] + } + } + + process_har(har_data, str(output_dir)) + + captured = capsys.readouterr() + assert "KeyError: 'request'" in captured.out + assert "KeyError: 'mimeType'" in captured.out + assert len(list(output_dir.iterdir())) == 0 + +def test_process_har_unsupported_mimetype(tmp_path, capsys): + output_dir = tmp_path / "imgs" + output_dir.mkdir() + + har_data = { + "log": { + "entries": [ + { + "request": {"url": "http://example.com/test.txt"}, + "response": { + "content": { + "mimeType": "text/plain", + "text": "not an image" + } + } + } + ] + } + } + + process_har(har_data, str(output_dir)) + + captured = capsys.readouterr() + assert "Skipping unsupported MIME type: text/plain" in captured.out + assert len(list(output_dir.iterdir())) == 0 + +def test_process_har_no_entries(capsys): + har_data = {"log": {"entries": []}} + process_har(har_data, "some_dir") + + captured = capsys.readouterr() + assert "No entries found in HAR file." in captured.out + +def test_process_har_mixed_entries(tmp_path, capsys): + output_dir = tmp_path / "imgs" + output_dir.mkdir() + + image_content = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==" + + har_data = { + "log": { + "entries": [ + { + "request": {"url": "http://example.com/invalid.png"}, + "response": { + "content": { + # Missing 'text' key + "mimeType": "image/png" + } + } + }, + { + "request": {"url": "http://example.com/valid.png"}, + "response": { + "content": { + "mimeType": "image/png", + "text": image_content + } + } + } + ] + } + } + + process_har(har_data, str(output_dir)) + + captured = capsys.readouterr() + assert "KeyError: 'text'" in captured.out + assert "Successfully saved" in captured.out + + assert (output_dir / "valid.png.png").exists() + assert not (output_dir / "invalid.png.png").exists() + +if __name__ == "__main__": + sys.exit(pytest.main([__file__]))