Skip to content
Merged
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
149 changes: 149 additions & 0 deletions .github/workflows/test-jira-release-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
name: Test jira-release-sync

on:
pull_request:
paths:
- "jira-release-sync/**"
- ".github/workflows/test-jira-release-sync.yml"
push:
branches: [main]
paths:
- "jira-release-sync/**"
- ".github/workflows/test-jira-release-sync.yml"

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
case:
- name: with_keys
release_body: |
## What's Changed
- PP-100 fix login bug
- PP-101 add new dashboard
- duplicate PP-100 should be deduped
- PP-42 older ticket
- QQ-9 (different project, must be ignored)
expected: jira-release-sync/tests/cases/with_keys.expected.json
- name: no_keys
release_body: |
## What's Changed
- just some bullet points
- nothing referencing tickets
expected: jira-release-sync/tests/cases/no_keys.expected.json
- name: android_log
release_body: |
Changes since the last release:

Allow clicking anywhere on the page in Book Details to hide the bottom drawer.
Upgrade pdfjs 2.14.305 -> 5.6.205. (Ticket: #PP-4057)
Add return confirmation. (Ticket: #PP-4126)
Reorganize book details page metadata. (Ticket: #PP-4047)
expected: jira-release-sync/tests/cases/android_log.expected.json
- name: body_via_file
release_body_file_content: |
Release notes
- PP-200 read me from a file
- PP-201 also read from a file
expected: jira-release-sync/tests/cases/body_via_file.expected.json
- name: custom_project
jira_project_key: ABC
release_body: |
## What's Changed
- ABC-7 fix login bug
- ABC-12 add new dashboard
- PP-99 wrong project, must be ignored
expected: jira-release-sync/tests/cases/custom_project.expected.json
- name: version_create_fails
release_body: |
- PP-1 will never be reached
fail_on: POST:/rest/api/3/version
expect_action_failure: true
expected: jira-release-sync/tests/cases/version_create_fails.expected.json

steps:
- uses: actions/checkout@v6

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Write release body file
if: matrix.case.release_body_file_content
env:
CONTENT: ${{ matrix.case.release_body_file_content }}
run: |
mkdir -p "$RUNNER_TEMP/jira-test"
printf '%s' "$CONTENT" > "$RUNNER_TEMP/jira-test/release-body.txt"

- name: Start mock Jira server
env:
FAIL_ON: ${{ matrix.case.fail_on }}
run: |
mkdir -p "$RUNNER_TEMP/jira-test"
fail_args=()
if [ -n "$FAIL_ON" ]; then
fail_args=(--fail-on "$FAIL_ON")
fi
python jira-release-sync/tests/mock_jira.py \
--port 8089 \
--log "$RUNNER_TEMP/jira-test/requests.jsonl" \
"${fail_args[@]}" \
> "$RUNNER_TEMP/jira-test/server.log" 2>&1 &
echo $! > "$RUNNER_TEMP/jira-test/server.pid"
for _ in $(seq 1 20); do
if curl -sf -o /dev/null "http://127.0.0.1:8089/__ready__"; then
echo "mock server ready"
exit 0
fi
sleep 0.25
done
echo "mock server did not become ready" >&2
cat "$RUNNER_TEMP/jira-test/server.log" >&2
exit 1

- name: Run jira-release-sync against mock
id: action
continue-on-error: ${{ matrix.case.expect_action_failure == true }}
uses: ./jira-release-sync
with:
jira-base-url: http://127.0.0.1:8089
jira-user-email: test@example.com
jira-api-token: not-a-real-token
jira-project-key: ${{ matrix.case.jira_project_key || 'PP' }}
release-name: v1.2.3
release-url: https://github.com/example/repo/releases/tag/v1.2.3
release-body: ${{ matrix.case.release_body }}
release-body-file: ${{ matrix.case.release_body_file_content && format('{0}/jira-test/release-body.txt', runner.temp) || '' }}

- name: Verify action outcome
env:
EXPECTED: ${{ (matrix.case.expect_action_failure == true) && 'failure' || 'success' }}
ACTUAL: ${{ steps.action.outcome }}
run: |
if [ "$ACTUAL" != "$EXPECTED" ]; then
echo "Expected action outcome=$EXPECTED, got=$ACTUAL" >&2
exit 1
fi

- name: Assert recorded requests
run: |
python jira-release-sync/tests/assert_calls.py \
--log "$RUNNER_TEMP/jira-test/requests.jsonl" \
--expected "${{ matrix.case.expected }}"

- name: Stop mock Jira server
if: always()
run: |
if [ -f "$RUNNER_TEMP/jira-test/server.pid" ]; then
kill "$(cat "$RUNNER_TEMP/jira-test/server.pid")" || true
fi

- name: Upload server log on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: mock-jira-log-${{ matrix.case.name }}
path: ${{ runner.temp }}/jira-test/
63 changes: 63 additions & 0 deletions jira-release-sync/tests/assert_calls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Assert the mock-jira request log matches expectations for a test case.

Reads a JSONL log produced by mock_jira.py and a JSON expectations file, then
exits non-zero with a diff-style message if they don't match. We compare a
normalized projection of each request (method, path, selected body fields) so
that incidental fields like description text don't make the test brittle.
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path


def load_jsonl(path: Path) -> list[dict]:
return [json.loads(line) for line in path.read_text().splitlines() if line.strip()]


def project(entry: dict) -> dict:
"""Extract just the fields we care about asserting on."""
method = entry["method"]
path = entry["path"]
body = entry.get("body") or {}
out: dict = {"method": method, "path": path}

if method == "POST" and path == "/rest/api/3/version":
out["name"] = body.get("name")
out["project"] = body.get("project")
out["released"] = body.get("released")
elif method == "POST" and path.endswith("/relatedwork"):
out["title"] = body.get("title")
out["url"] = body.get("url")
out["category"] = body.get("category")
elif method == "PUT" and path.startswith("/rest/api/3/issue/"):
out["fixVersions"] = body.get("update", {}).get("fixVersions")
return out


def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--log", type=Path, required=True)
parser.add_argument("--expected", type=Path, required=True)
args = parser.parse_args()

actual = [project(e) for e in load_jsonl(args.log)]
expected = json.loads(args.expected.read_text())

if actual == expected:
print(f"OK: {len(actual)} requests matched expectations")
return 0

print("MISMATCH between actual and expected requests", file=sys.stderr)
print("--- expected ---", file=sys.stderr)
print(json.dumps(expected, indent=2), file=sys.stderr)
print("--- actual ---", file=sys.stderr)
print(json.dumps(actual, indent=2), file=sys.stderr)
return 1


if __name__ == "__main__":
sys.exit(main())
35 changes: 35 additions & 0 deletions jira-release-sync/tests/cases/android_log.expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[
{
"method": "POST",
"path": "/rest/api/3/version",
"name": "v1.2.3",
"project": "PP",
"released": false
},
{
"method": "GET",
"path": "/rest/api/3/project/PP/versions"
},
{
"method": "POST",
"path": "/rest/api/3/version/10001/relatedwork",
"title": "GitHub Release Notes",
"url": "https://github.com/example/repo/releases/tag/v1.2.3",
"category": "release notes"
},
{
"method": "PUT",
"path": "/rest/api/3/issue/PP-4047",
"fixVersions": [{"add": {"name": "v1.2.3"}}]
},
{
"method": "PUT",
"path": "/rest/api/3/issue/PP-4057",
"fixVersions": [{"add": {"name": "v1.2.3"}}]
},
{
"method": "PUT",
"path": "/rest/api/3/issue/PP-4126",
"fixVersions": [{"add": {"name": "v1.2.3"}}]
}
]
30 changes: 30 additions & 0 deletions jira-release-sync/tests/cases/body_via_file.expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[
{
"method": "POST",
"path": "/rest/api/3/version",
"name": "v1.2.3",
"project": "PP",
"released": false
},
{
"method": "GET",
"path": "/rest/api/3/project/PP/versions"
},
{
"method": "POST",
"path": "/rest/api/3/version/10001/relatedwork",
"title": "GitHub Release Notes",
"url": "https://github.com/example/repo/releases/tag/v1.2.3",
"category": "release notes"
},
{
"method": "PUT",
"path": "/rest/api/3/issue/PP-200",
"fixVersions": [{"add": {"name": "v1.2.3"}}]
},
{
"method": "PUT",
"path": "/rest/api/3/issue/PP-201",
"fixVersions": [{"add": {"name": "v1.2.3"}}]
}
]
30 changes: 30 additions & 0 deletions jira-release-sync/tests/cases/custom_project.expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[
{
"method": "POST",
"path": "/rest/api/3/version",
"name": "v1.2.3",
"project": "ABC",
"released": false
},
{
"method": "GET",
"path": "/rest/api/3/project/ABC/versions"
},
{
"method": "POST",
"path": "/rest/api/3/version/10001/relatedwork",
"title": "GitHub Release Notes",
"url": "https://github.com/example/repo/releases/tag/v1.2.3",
"category": "release notes"
},
{
"method": "PUT",
"path": "/rest/api/3/issue/ABC-12",
"fixVersions": [{"add": {"name": "v1.2.3"}}]
},
{
"method": "PUT",
"path": "/rest/api/3/issue/ABC-7",
"fixVersions": [{"add": {"name": "v1.2.3"}}]
}
]
20 changes: 20 additions & 0 deletions jira-release-sync/tests/cases/no_keys.expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[
{
"method": "POST",
"path": "/rest/api/3/version",
"name": "v1.2.3",
"project": "PP",
"released": false
},
{
"method": "GET",
"path": "/rest/api/3/project/PP/versions"
},
{
"method": "POST",
"path": "/rest/api/3/version/10001/relatedwork",
"title": "GitHub Release Notes",
"url": "https://github.com/example/repo/releases/tag/v1.2.3",
"category": "release notes"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{
"method": "POST",
"path": "/rest/api/3/version",
"name": "v1.2.3",
"project": "PP",
"released": false
}
]
35 changes: 35 additions & 0 deletions jira-release-sync/tests/cases/with_keys.expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[
{
"method": "POST",
"path": "/rest/api/3/version",
"name": "v1.2.3",
"project": "PP",
"released": false
},
{
"method": "GET",
"path": "/rest/api/3/project/PP/versions"
},
{
"method": "POST",
"path": "/rest/api/3/version/10001/relatedwork",
"title": "GitHub Release Notes",
"url": "https://github.com/example/repo/releases/tag/v1.2.3",
"category": "release notes"
},
{
"method": "PUT",
"path": "/rest/api/3/issue/PP-100",
"fixVersions": [{"add": {"name": "v1.2.3"}}]
},
{
"method": "PUT",
"path": "/rest/api/3/issue/PP-101",
"fixVersions": [{"add": {"name": "v1.2.3"}}]
},
{
"method": "PUT",
"path": "/rest/api/3/issue/PP-42",
"fixVersions": [{"add": {"name": "v1.2.3"}}]
}
]
Loading
Loading