Skip to content
Closed
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
111 changes: 56 additions & 55 deletions .github/workflows/test-create-jira-version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
Expand All @@ -39,73 +39,74 @@ jobs:
cd create-jira-version
python -m pytest test_create_jira_version.py -v --cov=create_jira_version --cov-report=term-missing

integration-tests:
name: Integration Tests
sandbox-integration-test:
name: Sandbox Integration Test
runs-on: ubuntu-latest

permissions:
contents: read
id-token: write

steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Test with explicit new version name
id: test-explicit
uses: ./create-jira-version
with:
jira-project-key: 'TESTPROJ'
jira-new-version-name: '1.2.3'
use-jira-sandbox: 'true'
continue-on-error: true

- name: Verify explicit version test outputs
- name: Generate unique version name
id: version
run: |
echo "Test with explicit new version name:"
echo "Expected to fail due to missing credentials (which is expected in CI)"
echo "Action should have determined new-version-name as: 1.2.3"
VERSION_NAME="99.$(date +%-m%d).$(date +%-H%M)"
echo "new-version-name=$VERSION_NAME" >> $GITHUB_OUTPUT
echo "Generated version name: $VERSION_NAME"

- name: Test with current version (auto-increment)
id: test-auto
- name: Create Jira version via action
id: create-version
uses: ./create-jira-version
with:
jira-project-key: 'TESTPROJ'
jira-version-name: '1.2.2'
jira-project-key: 'SONARIAC'
jira-new-version-name: ${{ steps.version.outputs.new-version-name }}
use-jira-sandbox: 'true'
continue-on-error: true

- name: Verify auto-increment test outputs
- name: Verify action outputs
run: |
echo "Test with current version (should auto-increment):"
echo "Expected to fail due to missing credentials (which is expected in CI)"
echo "Action should have determined new-version-name as: 1.2.3"
NEW_NAME="${{ steps.create-version.outputs.jira-new-version-name }}"
NEW_ID="${{ steps.create-version.outputs.jira-new-version-id }}"
if [[ -z "$NEW_NAME" || -z "$NEW_ID" ]]; then
echo "❌ Action did not produce expected outputs (name='$NEW_NAME' id='$NEW_ID')"
exit 1
fi
echo "✅ New version created: $NEW_NAME (id=$NEW_ID)"

- name: Test with environment variables
id: test-env
uses: ./create-jira-version
env:
JIRA_PROJECT_KEY: 'TESTPROJ'
JIRA_VERSION_NAME: '2.0.1'
USE_JIRA_SANDBOX: 'true'
continue-on-error: true
- name: Get Jira credentials from Vault for cleanup
uses: SonarSource/vault-action-wrapper@v3
id: secrets
if: always()
with:
secrets: |
development/kv/data/jira user | JIRA_USER;
development/kv/data/jira token | JIRA_TOKEN;

- name: Verify environment variables test
- name: Clean up created version
if: always()
env:
JIRA_USER: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }}
JIRA_TOKEN: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }}
VERSION_NAME: ${{ steps.version.outputs.new-version-name }}
run: |
echo "Test with environment variables:"
echo "Expected to fail due to missing credentials (which is expected in CI)"
echo "Action should have determined new-version-name as: 2.0.2"
pip install jira
python - <<'EOF'
import os, sys
from jira import JIRA
from jira.exceptions import JIRAError

- name: Test with special characters in version names
id: test-special-chars
uses: ./create-jira-version
with:
jira-project-key: 'TESTPROJ'
jira-version-name: '1.2.3-"beta"'
jira-new-version-name: '1.2.4-"rc1"'
use-jira-sandbox: 'true'
continue-on-error: true
user = os.environ["JIRA_USER"]
token = os.environ["JIRA_TOKEN"]
version_name = os.environ["VERSION_NAME"]

- name: Verify special characters test
run: |
echo "Test with special characters in version names:"
echo "Expected to fail due to missing credentials (which is expected in CI)"
echo "Action should handle version names with quotes without syntax errors"
echo "Test outcome: ${{ steps.test-special-chars.outcome }}"
echo "✓ Action handles quotes and special characters in version names"
jira = JIRA("https://sonarsource-sandbox-608.atlassian.net/", basic_auth=(user, token))
versions = jira.project_versions("SONARIAC")
version = next((v for v in versions if v.name == version_name), None)
if version:
version.delete(moveFixIssuesTo=None)
print(f"✅ Deleted version: {version_name}")
else:
print(f"Version '{version_name}' not found (already deleted or never created)")
EOF
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,6 @@ cython_debug/

/tools/tools.iml
/.idea/

# Jira sandbox test state (generated at runtime, never committed)
scripts/jira-sandbox/test_state.json
83 changes: 83 additions & 0 deletions scripts/jira-sandbox/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Jira Sandbox Test Helper Scripts

End-to-end test helpers for the Jira-related GitHub Actions. These scripts create real Jira objects in the sandbox, run verifications against them, then clean up.

**Sandbox URL:** `https://sonarsource-sandbox-608.atlassian.net/`
**Projects used:** `SONARIAC` (analyzer versions & issues), `REL` (release tickets)

## Prerequisites

Set your sandbox credentials:

```bash
export JIRA_USER=<your-email>
export JIRA_TOKEN=<your-api-token>
```

Install dependencies (same `jira` library used by the actions):

```bash
pip install jira
```

## Running the tests

### Full cycle (recommended)

```bash
cd scripts/jira-sandbox
python run_all.py
```

This runs `setup.py` → `verify.py` → `cleanup.py` in sequence. Cleanup always runs, even if verify fails.

### Individual scripts

```bash
cd scripts/jira-sandbox

# 1. Create test fixtures
python setup.py

# 2. Run verifications
python verify.py

# 3. Remove test fixtures
python cleanup.py
```

## What each script does

### `setup.py`
Creates the test fixtures needed for a full release cycle:
1. Creates a Jira version in `SONARIAC` using the scheme `99.<MMDD>.<HHMM>` (e.g. `99.310.1430` for March 10 at 14:30)
2. Creates one Feature, one Bug, and one Maintenance issue with `fixVersion` set
3. Creates a release ticket in the `REL` project
4. Writes all keys/IDs to `test_state.json`

**Version scheme:** Major `99` is well above real production versions and clearly identifies test data. Patch encodes HHMM so it is always non-zero for any run after midnight. Versions ending in `.0` are avoided because `normalize_version_name()` in `create-jira-version` would strip that suffix.

### `verify.py`
Reads `test_state.json` and runs the following checks:
1. Version exists and is unreleased
2. Issues are assigned to the test version (`fixVersion`)
3. Marks the version released (simulates `release-jira-version` action)
4. Creates the next version with patch+1 (simulates `create-jira-version` action)
5. Fetches release notes and verifies expected issue types (simulates `get-jira-release-notes`)
6. Creates an integration ticket linked to the release ticket (simulates `create-integration-ticket`)
7. Transitions the release ticket to "Start Progress" (simulates `update-release-ticket-status`)

### `cleanup.py`
Reads `test_state.json` and removes:
- All issues created by `setup.py` and `verify.py`
- The test Jira version (and next version if created)
- `test_state.json` itself

### `run_all.py`
Orchestrator that runs setup → verify → cleanup. Exit code is 0 only if all three scripts pass.

## Notes

- These scripts only target the **sandbox** instance, never production
- `test_state.json` is gitignored and lives only locally during a test run
- If cleanup fails or is interrupted, run `python cleanup.py` manually to remove leftover fixtures
104 changes: 104 additions & 0 deletions scripts/jira-sandbox/cleanup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Cleans up Jira sandbox test fixtures created by setup.py.

Reads test_state.json and:
1. Deletes all created issues (Feature, Bug, Maintenance, release ticket)
2. Deletes the test Jira version (moves issues off version first)
3. Removes test_state.json
"""

import json
import os
import sys
from jira.exceptions import JIRAError

from jira_client import get_jira_client

STATE_FILE = os.path.join(os.path.dirname(__file__), "test_state.json")


def load_state() -> dict:
if not os.path.exists(STATE_FILE):
print(f"No state file found at '{STATE_FILE}'. Nothing to clean up.")
sys.exit(0)
with open(STATE_FILE) as f:
return json.load(f)


def delete_issue(jira, key: str) -> bool:
try:
jira.issue(key).delete()
print(f" Deleted issue: {key}")
return True
except JIRAError as e:
if e.status_code == 404:
print(f" Issue {key} not found (already deleted?)")
return True
print(f" Warning: Failed to delete issue {key}. Status: {e.status_code}", file=sys.stderr)
return False


def delete_version(jira, project_key: str, version_name: str) -> bool:
"""Finds and deletes a version by name, moving any remaining issues to 'unresolved'."""
try:
versions = jira.project_versions(project_key)
version = next((v for v in versions if v.name == version_name), None)
if not version:
print(f" Version '{version_name}' not found in '{project_key}' (already deleted?)")
return True

# Delete version; moveFixIssuesTo=None means issues become un-versioned
version.delete(moveFixIssuesTo=None)
print(f" Deleted version: {version_name}")
return True
except JIRAError as e:
print(f" Warning: Failed to delete version '{version_name}'. Status: {e.status_code}: {e.text}",
file=sys.stderr)
return False


def main():
print("=" * 60)
print("CLEANUP: Removing Jira sandbox test fixtures")
print("=" * 60)

state = load_state()
version_name = state["version_name"]
project_key = state["sonariac_project_key"]

jira = get_jira_client()

issues_to_delete = [
state.get("integration_ticket_key"), # created by verify.py (may not exist)
state.get("next_version_created"), # next version issues (none created)
state.get("feature_issue_key"),
state.get("bug_issue_key"),
state.get("maintenance_issue_key"),
state.get("rel_ticket_key"),
]

print("\nDeleting issues...")
for key in issues_to_delete:
if key:
delete_issue(jira, key)

print("\nDeleting Jira versions...")
delete_version(jira, project_key, version_name)

# Also clean up next version if verify.py created one
next_version = state.get("next_version_name")
if next_version:
delete_version(jira, project_key, next_version)

print("\nRemoving state file...")
os.remove(STATE_FILE)
print(f" Removed: {STATE_FILE}")

print("\n✅ Cleanup complete.")


if __name__ == "__main__":
main()
43 changes: 43 additions & 0 deletions scripts/jira-sandbox/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Constants for the Jira sandbox test helpers.
"""

import datetime

SANDBOX_URL = "https://sonarsource-sandbox-608.atlassian.net/"

# Project keys
SONARIAC_PROJECT_KEY = "SONARIAC"
REL_PROJECT_KEY = "REL"

# Test version naming: 99.<MMDD>.<HHMM>
# Major 99 is well above real versions and clearly identifies test data.
# Patch encodes HHMM so it is always non-zero after midnight.
def make_test_version_name() -> str:
now = datetime.datetime.now()
mmdd = now.strftime("%m%d").lstrip("0") or "1" # e.g. "310" for March 10
hhmm = now.strftime("%H%M").lstrip("0") or "1" # e.g. "1430" for 14:30
return f"99.{mmdd}.{hhmm}"


def make_next_version_name(current_version: str) -> str:
"""Increments the patch component of a version string by 1."""
parts = current_version.split(".")
parts[-1] = str(int(parts[-1]) + 1)
return ".".join(parts)


# Default issue type priority order for release notes
ISSUE_TYPE_ORDER = [
"Feature",
"New Feature",
"False Positive",
"False Negative",
"Bug",
"Improvement",
"Security",
"Maintenance",
]
Loading
Loading