Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
240d23d
Add pytest test suite with coverage and CI workflow
TheRealAgentK Apr 9, 2026
1f20800
Fix ExecutionContext tests for FetchResponse return type
TheRealAgentK Apr 9, 2026
ea75516
Add tests for FetchResponse dataclass and headers
TheRealAgentK Apr 9, 2026
c634fce
Fix: preserve pytest exit code with pipefail in CI
TheRealAgentK Apr 9, 2026
e9cef3f
Add testing and coverage documentation to AGENTS.md, README.md, and R…
TheRealAgentK Apr 9, 2026
c9deb10
Add tests to reach 99% coverage: default config path, polling trigger…
TheRealAgentK Apr 10, 2026
f03a688
Add CI, PyPI, Python, and license badges to READMEs; add coverage bad…
TheRealAgentK Apr 10, 2026
2352782
Add coverage badge to READMEs
TheRealAgentK Apr 10, 2026
949272a
Update gistID for coverage badge
TheRealAgentK Apr 10, 2026
856511d
Include commit hash and message in coverage report title
TheRealAgentK Apr 10, 2026
773c888
Add release notes step to RELEASING.md and action-error-demo sample t…
TheRealAgentK Apr 10, 2026
16aea07
Use actual PR head commit for coverage report title instead of merge …
TheRealAgentK Apr 10, 2026
1061a3a
Show commit message and author in coverage report title
TheRealAgentK Apr 10, 2026
6dc609d
Use GitHub username for coverage title and always show coverage table
TheRealAgentK Apr 10, 2026
11d472a
Replace MishaKav action with custom flat coverage comment
TheRealAgentK Apr 10, 2026
71364c5
Fix coverage comment: write markdown to file to avoid backtick escapi…
TheRealAgentK Apr 10, 2026
05d9044
Link commit SHA and missing line numbers in coverage comment
TheRealAgentK Apr 10, 2026
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
156 changes: 156 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
name: Tests

on:
push:
branches: [master]
pull_request:
branches: [master]
Comment thread
TheRealAgentK marked this conversation as resolved.

permissions:
contents: read
pull-requests: write

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.13"]

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[test]"

- name: Get commit info
id: commit-info
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
COMMIT_SHA="${{ github.event.pull_request.head.sha }}"
else
COMMIT_SHA="${{ github.sha }}"
fi
echo "sha=${COMMIT_SHA}" >> "$GITHUB_OUTPUT"
echo "short_sha=$(echo "$COMMIT_SHA" | cut -c1-7)" >> "$GITHUB_OUTPUT"
echo "message=$(git log -1 --pretty=%s "$COMMIT_SHA")" >> "$GITHUB_OUTPUT"

- name: Run tests with coverage
run: |
set -o pipefail
python -m pytest tests/ -v --tb=short \
--cov=autohive_integrations_sdk \
--cov-report=term-missing \
--cov-report=xml:coverage.xml \
| tee pytest-coverage.txt
Comment thread
TheRealAgentK marked this conversation as resolved.

- name: Build coverage comment
id: coverage
run: |
TOTAL=$(grep '^TOTAL' pytest-coverage.txt | awk '{print $(NF-1)}')
echo "total=${TOTAL}" >> "$GITHUB_OUTPUT"

REPO_URL="${{ github.server_url }}/${{ github.repository }}"
SHA="${{ steps.commit-info.outputs.sha }}"
SHORT_SHA="${{ steps.commit-info.outputs.short_sha }}"
COMMIT_LINK="[\`${SHORT_SHA}\`](${REPO_URL}/commit/${SHA})"
TITLE="Coverage — ${COMMIT_LINK} (${{ steps.commit-info.outputs.message }}) by @${{ github.actor }}"

# Convert "308, 315-320, 347" into linked line numbers
linkify_lines() {
local file="$1" missing="$2"
if [ -z "$missing" ]; then
echo ""
return
fi
local result=""
IFS=', ' read -ra PARTS <<< "$missing"
for part in "${PARTS[@]}"; do
[ -z "$part" ] && continue
if [[ "$part" == *-* ]]; then
local start="${part%-*}" end="${part#*-}"
local link="[${part}](${REPO_URL}/blob/${SHA}/${file}#L${start}-L${end})"
else
local link="[${part}](${REPO_URL}/blob/${SHA}/${file}#L${part})"
fi
if [ -n "$result" ]; then
result="${result}, ${link}"
else
result="${link}"
fi
done
echo "$result"
}

{
echo "<!-- coverage-comment -->"
echo "### ${TITLE}"
echo ""
echo "**Total coverage: ${TOTAL}**"
echo ""
echo "| File | Stmts | Miss | Cover | Missing |"
echo "|------|-------|------|-------|---------|"
grep '^src/' pytest-coverage.txt | while IFS= read -r line; do
FILE=$(echo "$line" | awk '{print $1}')
STMTS=$(echo "$line" | awk '{print $2}')
MISS=$(echo "$line" | awk '{print $3}')
COV=$(echo "$line" | awk '{print $4}')
MISSING=$(echo "$line" | awk '{for(i=5;i<=NF;i++) printf "%s ", $i; print ""}' | xargs)
LINKED=$(linkify_lines "$FILE" "$MISSING")
echo "| \`${FILE}\` | ${STMTS} | ${MISS} | ${COV} | ${LINKED} |"
done
} > coverage-comment.md

- name: Post coverage comment on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const body = fs.readFileSync('coverage-comment.md', 'utf8');
const marker = '<!-- coverage-comment -->';

const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c => c.body.includes(marker));

if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}

- name: Update coverage badge
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
uses: schneegans/dynamic-badges-action@v1.7.0
with:
auth: ${{ secrets.GIST_SECRET }}
gistID: e8adf35c8508876ab8ba09422ddc2535
filename: coverage-badge.json
label: coverage
message: ${{ steps.coverage.outputs.total }}
valColorRange: ${{ steps.coverage.outputs.total }}
minColorRange: 50
maxColorRange: 100
14 changes: 14 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@
```
- Always commit the regenerated docs alongside the code changes that caused them.

## Testing

- Tests live in `tests/` and use **pytest** with **pytest-asyncio** and **aioresponses**.
- After any code change in `src/autohive_integrations_sdk/`, run the test suite:
```
python -m pytest tests/ -v
```
- Run with coverage to check for regressions:
```
python -m pytest tests/ -v --cov=autohive_integrations_sdk --cov-report=term-missing
```
- Add or update tests for any new or modified functionality. Aim to maintain ≥95% coverage.
- CI runs automatically on PRs via GitHub Actions (`.github/workflows/tests.yml`).

## Releasing

- Follow the process in [RELEASING.md](RELEASING.md).
Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Integrations SDK for Autohive

[![Tests](https://github.com/Autohive-AI/integrations-sdk/actions/workflows/tests.yml/badge.svg)](https://github.com/Autohive-AI/integrations-sdk/actions/workflows/tests.yml)
[![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/TheRealAgentK/e8adf35c8508876ab8ba09422ddc2535/raw/coverage-badge.json)](https://github.com/Autohive-AI/integrations-sdk/actions/workflows/tests.yml)
[![PyPI version](https://img.shields.io/pypi/v/autohive-integrations-sdk)](https://pypi.org/project/autohive-integrations-sdk/)
[![Python](https://img.shields.io/pypi/pyversions/autohive-integrations-sdk)](https://pypi.org/project/autohive-integrations-sdk/)
[![License: MIT](https://img.shields.io/pypi/l/autohive-integrations-sdk)](https://github.com/Autohive-AI/integrations-sdk/blob/master/LICENSE)

## Overview

This is the SDK for building integrations into Autohive's AI agent platform.
Expand All @@ -25,6 +31,26 @@ Start with the **[Building Your First Integration](docs/manual/building_your_fir
|--------|-------------|
| [`samples/template/`](samples/template/) | Clean starter template — copy this to begin a new integration |
| [`samples/api-fetch/`](samples/api-fetch/) | Working example with unauthenticated, Basic Auth, and Bearer token API calls |
| [`samples/action-error-demo/`](samples/action-error-demo/) | Demonstrates `ActionError` for expected application-level errors |

## Testing

Install test dependencies:
```bash
pip install -e ".[test]"
```

Run tests:
```bash
python -m pytest tests/ -v
```

Run with coverage:
```bash
python -m pytest tests/ -v --cov=autohive_integrations_sdk --cov-report=term-missing
```

CI runs automatically on PRs via GitHub Actions — see [`.github/workflows/tests.yml`](.github/workflows/tests.yml).

## Validation & CI

Expand Down
2 changes: 2 additions & 0 deletions README_PYPI.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Integrations SDK for Autohive

[![Tests](https://github.com/Autohive-AI/integrations-sdk/actions/workflows/tests.yml/badge.svg)](https://github.com/Autohive-AI/integrations-sdk/actions/workflows/tests.yml)
[![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/TheRealAgentK/e8adf35c8508876ab8ba09422ddc2535/raw/coverage-badge.json)](https://github.com/Autohive-AI/integrations-sdk/actions/workflows/tests.yml)
[![PyPI version](https://img.shields.io/pypi/v/autohive-integrations-sdk)](https://pypi.org/project/autohive-integrations-sdk/)
[![Python](https://img.shields.io/pypi/pyversions/autohive-integrations-sdk)](https://pypi.org/project/autohive-integrations-sdk/)
[![License: MIT](https://img.shields.io/pypi/l/autohive-integrations-sdk)](https://github.com/Autohive-AI/integrations-sdk/blob/master/LICENSE)
Expand Down
7 changes: 7 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
- Authors
- Dependencies

* Update `RELEASENOTES.md` with an entry for the new version.

* Run the test suite and ensure all tests pass:
```
python -m pytest tests/ -v --cov=autohive_integrations_sdk --cov-report=term-missing
```

* Release to PyPi:
- `build` is required (`python3 -m pip install --upgrade build`)
- `twine` is required (`python3 -m pip install --upgrade twine`)
Expand Down
13 changes: 13 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ dependencies = [
"aiohttp",
"jsonschema==4.17.3"
]

[project.optional-dependencies]
test = [
"pytest>=8.0",
"pytest-asyncio>=0.24",
"aioresponses>=0.7",
"pytest-cov>=6.0",
]

[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"

[project.urls]
Homepage = "https://github.com/Autohive-AI/integrations-sdk"
Issues = "https://github.com/Autohive-AI/integrations-sdk/issues"
Empty file removed tests/.gitkeep
Empty file.
89 changes: 89 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""Shared fixtures for the Autohive Integrations SDK test suite."""

import json
import pytest
from pathlib import Path

from autohive_integrations_sdk import Integration, ExecutionContext


@pytest.fixture
def config_dict():
"""Minimal config.json content with one action, one trigger, and auth fields."""
return {
"name": "test-integration",
"version": "0.1.0",
"description": "Integration used by the test suite",
"auth": {
"auth_type": "Custom",
"fields": {
"type": "object",
"properties": {
"api_key": {"type": "string"}
},
"required": ["api_key"]
}
},
"actions": {
"test_action": {
"description": "A simple test action",
"input_schema": {
"type": "object",
"properties": {
"name": {"type": "string"}
},
"required": ["name"]
},
"output_schema": {
"type": "object",
"properties": {
"greeting": {"type": "string"}
},
"required": ["greeting"]
}
}
},
"polling_triggers": {
"test_trigger": {
"description": "A simple test trigger",
"polling_interval": "5m",
"input_schema": {
"type": "object",
"properties": {
"channel": {"type": "string"}
},
"required": ["channel"]
},
"output_schema": {
"type": "object",
"properties": {
"message": {"type": "string"}
},
"required": ["message"]
}
}
}
}


@pytest.fixture
def tmp_config(tmp_path, config_dict):
"""Write the config dict to a temporary file and return its path."""
config_file = tmp_path / "config.json"
config_file.write_text(json.dumps(config_dict))
return config_file


@pytest.fixture
def integration(tmp_config):
"""An Integration instance loaded from the temporary config."""
return Integration.load(tmp_config)


@pytest.fixture
def execution_context():
"""A simple ExecutionContext with a Custom API key."""
return ExecutionContext(
auth={"api_key": "test-key-123"},
request_config={"max_retries": 1, "timeout": 1},
)
Loading
Loading