From bffcdd5ef87008dcee1ef2514cbe82fdf9aaa349 Mon Sep 17 00:00:00 2001 From: Andreas Hubert <989010+peshay@users.noreply.github.com> Date: Mon, 4 May 2026 05:56:24 +0000 Subject: [PATCH 1/5] ci: add github actions with ruff, pytest, and codecov --- .github/workflows/ci.yml | 45 ++++++++++++++++++++++++++++++++++++++++ tests/test_tpm_v4.py | 26 +++++++++++------------ tests/test_tpm_v5.py | 26 +++++++++++------------ 3 files changed, 69 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d091dce --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12'] + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install -r tests/requirements.txt + pip install pytest pytest-cov ruff + pip install -e . + + - name: Ruff + run: ruff check tpm.py tests + + - name: Tests with coverage + run: pytest --cov=tpm --cov-report=xml tests/ + + - name: Upload coverage + uses: codecov/codecov-action@v4 + with: + use_oidc: true + files: ./coverage.xml diff --git a/tests/test_tpm_v4.py b/tests/test_tpm_v4.py index 3168e7c..5c37102 100644 --- a/tests/test_tpm_v4.py +++ b/tests/test_tpm_v4.py @@ -1,4 +1,3 @@ -import requests import requests_mock import unittest import os.path @@ -8,7 +7,6 @@ import hmac import hashlib import time -import random log = logging.getLogger(__name__) @@ -24,7 +22,7 @@ def fake_data(url, m, altpath=False): """ # Map path from url to a file path_parts = url.split('/')[6:] - if altpath == False: + if not altpath: path = '/'.join(path_parts) else: path = altpath @@ -845,12 +843,12 @@ def test_provide_unlock_reason(self): request_path = local_path + path_to_mock resource_file = os.path.normpath(request_path) data_file = open(resource_file) - data = json.load(data_file) + json.load(data_file) unlock_reason = 'because I can' client = tpm.TpmApiv4('https://tpm.example.com', username='USER', password='PASS', unlock_reason=unlock_reason) with requests_mock.Mocker() as m: m.get(request_url, request_headers={'X-Unlock-Reason': unlock_reason}) - response = client.show_password('14') + client.show_password('14') history = m.request_history request_unlock_reason = history[0].headers.get('X-Unlock-Reason') self.assertEqual(request_unlock_reason, unlock_reason) @@ -864,12 +862,12 @@ def test_key_authentciation(self): request_url = api_url + path_to_mock with requests_mock.Mocker() as m: fake_data(request_url, m) - response = client.get_version() + client.get_version() history = m.request_history request_hash = history[0].headers.get('X-Request-Hash') - request_pubkey = history[0].headers.get('X-Public-Key') + history[0].headers.get('X-Public-Key') request_timestamp = history[0].headers.get('X-Request-Timestamp') - timestamp = str(int(time.time())) + str(int(time.time())) unhashed = 'api/v4/' + path_to_mock + request_timestamp hashed = hmac.new(str.encode(private_key), msg=unhashed.encode('utf-8'), @@ -936,7 +934,7 @@ def test_function_up_to_date_true(self): request_path = local_path + path_to_mock resource_file = os.path.normpath(request_path) data_file = open(resource_file) - data = json.load(data_file) + json.load(data_file) with requests_mock.Mocker() as m: fake_data(request_url, m) response_up_to_date_true = self.client.up_to_date() @@ -1014,7 +1012,7 @@ def test_value_error_exception(self): with self.assertRaises(ValueError) as context: with requests_mock.Mocker() as m: m.get(request_url, text=str(data)) - response = self.client.show_password('value_error') + self.client.show_password('value_error') log.debug("context exception: {}".format(context.exception)) self.assertTrue(str(context.exception).startswith(exception_error) or str(context.exception).startswith(exception_error3)) @@ -1027,7 +1025,7 @@ def test_exception_on_error_in_result(self): with self.assertRaises(tpm.TPMException) as context: with requests_mock.Mocker() as m: m.get(request_url, json=error_json) - response = self.client.show_password('json_error') + self.client.show_password('json_error') log.debug("context exception: {}".format(context.exception)) self.assertTrue(exception_error in str(context.exception)) @@ -1039,7 +1037,7 @@ def test_exception_on_403(self): with self.assertRaises(tpm.TPMException) as context: with requests_mock.Mocker() as m: m.get(request_url, text='forbidden', status_code=403) - response = self.client.list_passwords() + self.client.list_passwords() log.debug("context exception: {}".format(context.exception)) self.assertTrue(exception_error in str(context.exception)) @@ -1051,7 +1049,7 @@ def test_exception_on_404(self): with self.assertRaises(tpm.TPMException) as context: with requests_mock.Mocker() as m: m.get(request_url, text='not found', status_code=404) - response = self.client.list_passwords() + self.client.list_passwords() log.debug("context exception: {}".format(context.exception)) self.assertTrue(exception_error in str(context.exception)) @@ -1063,6 +1061,6 @@ def test_exception_on_405(self): with self.assertRaises(ValueError) as context: with requests_mock.Mocker() as m: m.get(request_url, text='Method Not Allowed', status_code=405) - response = self.client.list_passwords() + self.client.list_passwords() log.debug("context exception: {}".format(context.exception)) self.assertTrue(str(context.exception).endswith(exception_error)) diff --git a/tests/test_tpm_v5.py b/tests/test_tpm_v5.py index d52ecc5..2eea8c9 100644 --- a/tests/test_tpm_v5.py +++ b/tests/test_tpm_v5.py @@ -1,4 +1,3 @@ -import requests import requests_mock import unittest import os.path @@ -8,7 +7,6 @@ import hmac import hashlib import time -import random log = logging.getLogger(__name__) @@ -24,7 +22,7 @@ def fake_data(url, m, altpath=False): """ # Map path from url to a file path_parts = url.split('/')[6:] - if altpath == False: + if not altpath: path = '/'.join(path_parts) else: path = altpath @@ -920,12 +918,12 @@ def test_provide_unlock_reason(self): request_path = local_path + path_to_mock resource_file = os.path.normpath(request_path) data_file = open(resource_file) - data = json.load(data_file) + json.load(data_file) unlock_reason = 'because I can' client = tpm.TpmApiv5('https://tpm.example.com', username='USER', password='PASS', unlock_reason=unlock_reason) with requests_mock.Mocker() as m: m.get(request_url, request_headers={'X-Unlock-Reason': unlock_reason}) - response = client.show_password('14') + client.show_password('14') history = m.request_history request_unlock_reason = history[0].headers.get('X-Unlock-Reason') self.assertEqual(request_unlock_reason, unlock_reason) @@ -939,12 +937,12 @@ def test_key_authentciation(self): request_url = api_url + path_to_mock with requests_mock.Mocker() as m: fake_data(request_url, m) - response = client.get_version() + client.get_version() history = m.request_history request_hash = history[0].headers.get('X-Request-Hash') - request_pubkey = history[0].headers.get('X-Public-Key') + history[0].headers.get('X-Public-Key') request_timestamp = history[0].headers.get('X-Request-Timestamp') - timestamp = str(int(time.time())) + str(int(time.time())) unhashed = 'api/v5/' + path_to_mock + request_timestamp hashed = hmac.new(str.encode(private_key), msg=unhashed.encode('utf-8'), @@ -1011,7 +1009,7 @@ def test_function_up_to_date_true(self): request_path = local_path + path_to_mock resource_file = os.path.normpath(request_path) data_file = open(resource_file) - data = json.load(data_file) + json.load(data_file) with requests_mock.Mocker() as m: fake_data(request_url, m) response_up_to_date_true = self.client.up_to_date() @@ -1089,7 +1087,7 @@ def test_value_error_exception(self): with self.assertRaises(ValueError) as context: with requests_mock.Mocker() as m: m.get(request_url, text=str(data)) - response = self.client.show_password('value_error') + self.client.show_password('value_error') log.debug("context exception: {}".format(context.exception)) self.assertTrue(str(context.exception).startswith(exception_error) or str(context.exception).startswith(exception_error3)) @@ -1102,7 +1100,7 @@ def test_exception_on_error_in_result(self): with self.assertRaises(tpm.TPMException) as context: with requests_mock.Mocker() as m: m.get(request_url, json=error_json) - response = self.client.show_password('json_error') + self.client.show_password('json_error') log.debug("context exception: {}".format(context.exception)) self.assertTrue(exception_error in str(context.exception)) @@ -1114,7 +1112,7 @@ def test_exception_on_403(self): with self.assertRaises(tpm.TPMException) as context: with requests_mock.Mocker() as m: m.get(request_url, text='forbidden', status_code=403) - response = self.client.list_passwords() + self.client.list_passwords() log.debug("context exception: {}".format(context.exception)) self.assertTrue(exception_error in str(context.exception)) @@ -1126,7 +1124,7 @@ def test_exception_on_404(self): with self.assertRaises(tpm.TPMException) as context: with requests_mock.Mocker() as m: m.get(request_url, text='not found', status_code=404) - response = self.client.list_passwords() + self.client.list_passwords() log.debug("context exception: {}".format(context.exception)) self.assertTrue(exception_error in str(context.exception)) @@ -1138,7 +1136,7 @@ def test_exception_on_405(self): with self.assertRaises(ValueError) as context: with requests_mock.Mocker() as m: m.get(request_url, text='Method Not Allowed', status_code=405) - response = self.client.list_passwords() + self.client.list_passwords() log.debug("context exception: {}".format(context.exception)) self.assertTrue(str(context.exception).endswith(exception_error)) From 79c08f173b8d61088332b5fb34f24819024a130b Mon Sep 17 00:00:00 2001 From: Andreas Hubert Date: Tue, 5 May 2026 09:48:41 +0200 Subject: [PATCH 2/5] docs: add TPM repository audit note Co-authored-by: OpenClaw service runtime --- ...repository-structure-packaging-tests-ci.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/audits/repository-structure-packaging-tests-ci.md diff --git a/docs/audits/repository-structure-packaging-tests-ci.md b/docs/audits/repository-structure-packaging-tests-ci.md new file mode 100644 index 0000000..fe8577d --- /dev/null +++ b/docs/audits/repository-structure-packaging-tests-ci.md @@ -0,0 +1,21 @@ +# Repository structure, packaging, tests, CI audit + +Scope: `peshay/tpm` + +## Findings +- Repo is a single-module package: `tpm.py`, `setup.py`, `requirements.txt`, `tests/`. +- No `pyproject.toml` yet; packaging still uses `distutils.core.setup` in `setup.py`. +- Runtime dependencies are legacy-pinned (`requests<=2.26.0`, `future`, `urllib3`). +- CI is Travis-based, targets Python 3.6-3.9 plus nightly, installs with `python setup.py -q install`, and runs `nose2 -v --with-coverage`. +- Tests are fixture-driven under `tests/resources/` and cover API v4/v5 behavior. +- Cross-cutting gap: the request layer disables TLS verification by default (`verify=False`). + +## Risks / gaps +- Packaging path is outdated for current Python tooling. +- CI/runtime support matrix is stale and likely misses current supported versions. +- TLS verification default is unsafe for production use. + +## Follow-up +- Modernize packaging to `pyproject.toml`. +- Refresh the test/CI matrix. +- Revisit transport security defaults in a separate hardening card. From 3f8e49b6e40af58e2101f3a1fefd314a71abb2e3 Mon Sep 17 00:00:00 2001 From: Andreas Hubert Date: Tue, 5 May 2026 09:48:52 +0200 Subject: [PATCH 3/5] docs: align README top badges with active CI Co-authored-by: Andreas Hubert <989010+peshay@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 49fa75b..2708427 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > Modern Python SDK groundwork for TeamPasswordManager with safer API boundaries and maintainable packaging. -[![Build Status](https://travis-ci.org/peshay/tpm.svg?branch=master)](https://travis-ci.org/peshay/tpm) +[![CI](https://github.com/peshay/tpm/actions/workflows/ci.yml/badge.svg)](https://github.com/peshay/tpm/actions/workflows/ci.yml) [![Python version](https://img.shields.io/pypi/pyversions/tpm.svg)](https://pypi.python.org/pypi/tpm) [![license](https://img.shields.io/github/license/peshay/tpm.svg)](https://github.com/peshay/tpm/blob/master/LICENSE) From 08200767142c2e716d987218f77e2e9a56f567f6 Mon Sep 17 00:00:00 2001 From: OpenClaw service runtime Date: Tue, 5 May 2026 08:50:16 +0000 Subject: [PATCH 4/5] fix(ci): enable codecov OIDC token AI-Agent: Monkey AI-Model: openai-codex/gpt-5.4 Tests: python3 - <<'PY' / git diff --check from pathlib import Path import sys try: import yaml except Exception as exc: print(f'PyYAML unavailable: {exc}') sys.exit(0) text = Path('.github/workflows/ci.yml').read_text() yaml.safe_load(text) print('workflow YAML ok') PY Agent: Monkey Worker-Model: openai-codex/gpt-5.4 Worker-Thinking: medium --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d091dce..d37fd79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,7 @@ on: permissions: contents: read + id-token: write jobs: test: From e6df353dc6b7d7c62e8063a0c9dbea72aa2e2668 Mon Sep 17 00:00:00 2001 From: OpenClaw service runtime Date: Tue, 5 May 2026 08:51:33 +0000 Subject: [PATCH 5/5] ci: skip codecov upload on pull requests GitHub pull_request runs are missing the OIDC env vars required by codecov-action@v4 in this repo, so the coverage upload step fails after Ruff and pytest pass. Keep uploads on push and skip them on PR validation. Agent: Monkey Worker-Model: openai-codex/gpt-5.4 Worker-Thinking: medium --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d37fd79..35ec1dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,7 @@ jobs: run: pytest --cov=tpm --cov-report=xml tests/ - name: Upload coverage + if: github.event_name == 'push' uses: codecov/codecov-action@v4 with: use_oidc: true