Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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/...
1 change: 1 addition & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Empty build file for the root
14 changes: 14 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
@@ -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()
23 changes: 23 additions & 0 deletions stapler-scripts/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -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",
)
31 changes: 17 additions & 14 deletions stapler-scripts/har2img.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

process_har() assumes folder already exists. Since this is now a standalone function (and is called directly by tests/other potential callers), consider creating the directory inside process_har (or explicitly validating and raising) so callers don’t silently get per-file write errors when a non-existent output folder is passed in.

Suggested change
def process_har(har, folder):
def process_har(har, folder):
# Ensure the output folder exists to avoid per-file write errors for non-existent directories.
folder = os.path.abspath(folder)
os.makedirs(folder, exist_ok=True)

Copilot uses AI. Check for mistakes.
entries = har.get("log", {}).get("entries", [])
if not entries:
print("No entries found in HAR file.")
Expand Down Expand Up @@ -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()
13 changes: 13 additions & 0 deletions stapler-scripts/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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",
]
149 changes: 149 additions & 0 deletions stapler-scripts/test_har2img.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import os
import json
import base64
import pytest
Comment on lines +1 to +4
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused imports (os, json, pytest) add noise and can trigger lint failures in stricter environments. Since this test file relies only on built-in pytest fixtures (tmp_path, capsys) and base64, consider removing the unused imports.

Suggested change
import os
import json
import base64
import pytest
import base64

Copilot uses AI. Check for mistakes.
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()
Comment on lines +34 to +35
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These assertions lock in an odd output naming convention (test.png.png). If the URL already ends with an image extension, appending another extension based on MIME type is likely unintended; it also makes output harder to predict. Consider adjusting har2img.py to avoid double extensions (and update this expectation accordingly).

Suggested change
output_file = output_dir / "test.png.png"
assert output_file.exists()
output_files = list(output_dir.iterdir())
assert len(output_files) == 1
output_file = output_files[0]

Copilot uses AI. Check for mistakes.
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()
Comment on lines +145 to +146
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same double-extension expectation here (valid.png.png). If you update the production code to avoid adding an extra extension when the URL already has one, this assertion should be updated to the intended filename.

Suggested change
assert (output_dir / "valid.png.png").exists()
assert not (output_dir / "invalid.png.png").exists()
assert (output_dir / "valid.png").exists()
assert not (output_dir / "invalid.png").exists()

Copilot uses AI. Check for mistakes.

if __name__ == "__main__":
sys.exit(pytest.main([__file__]))