diff --git a/.github/workflows/check-coverage.yml b/.github/workflows/check-coverage.yml index e55838ed..efd2f6e6 100644 --- a/.github/workflows/check-coverage.yml +++ b/.github/workflows/check-coverage.yml @@ -18,7 +18,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }} uses: actions/setup-python@v6 @@ -46,6 +46,7 @@ jobs: - name: Comment on pull request with coverage uses: MishaKav/pytest-coverage-comment@main + continue-on-error: true with: pytest-coverage-path: ./pytest-coverage.txt # TODO: check if this has been fixed diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 5a337908..669a3240 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/generate-metadata.yml b/.github/workflows/generate-metadata.yml index 8b47b312..4bf2d6bf 100644 --- a/.github/workflows/generate-metadata.yml +++ b/.github/workflows/generate-metadata.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up Python uses: actions/setup-python@v6 diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 2aea8b0b..6b560231 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -61,7 +61,7 @@ jobs: UPLOAD_FILE_NAME: tabcmd steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: @@ -98,7 +98,7 @@ jobs: - name: Upload build artifact for ${{ matrix.TARGET }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: tabcmd-${{ matrix.TARGET }} path: ./dist/${{ matrix.TARGET }}/${{ matrix.UPLOAD_FILE_NAME }} @@ -112,4 +112,3 @@ jobs: file: ./dist/${{ matrix.TARGET }}/${{ matrix.UPLOAD_FILE_NAME }} tag: ${{ github.ref_name }} overwrite: true - promote: true diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 5f5d84bd..5f06e8aa 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -7,15 +7,15 @@ on: description: 'Test PyPi' required: true type: boolean - push: - tags: 'pypi' + release: + types: [published] jobs: build-n-publish: name: Build dist files for PyPi runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 - uses: actions/setup-python@v6 @@ -32,13 +32,13 @@ jobs: python -m build - name: Publish distribution 📦 to Test PyPI - if: ${{ inputs.is_draft }} + if: ${{ github.event_name == 'workflow_dispatch' && inputs.is_draft == true }} uses: pypa/gh-action-pypi-publish@release/v1 # license BSD-2 with: password: ${{ secrets.TEST_PYPI_API_TOKEN }} repository_url: https://test.pypi.org/legacy/ - name: Publish distribution 📦 to PyPI - if: ${{ !inputs.is_draft }} + if: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.is_draft != true) }} uses: pypa/gh-action-pypi-publish@release/v1 # license BSD-2 with: password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/release-on-merge.yml b/.github/workflows/release-on-merge.yml new file mode 100644 index 00000000..28c3dff1 --- /dev/null +++ b/.github/workflows/release-on-merge.yml @@ -0,0 +1,60 @@ +name: Draft release on merge to main + +# Triggers when a PR is merged into main (push to main covers all merge strategies). +# Creates a draft GitHub release with auto-generated notes and pushes the version tag. +# The tag push triggers package.yml to build and attach binaries. +# When you publish the draft release, publish-pypi.yml uploads to PyPI automatically. + +on: + push: + branches: + - main + +permissions: + contents: write + +jobs: + draft-release: + name: Create draft release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Compute next patch version + id: version + run: | + LAST_TAG=$(git tag --list | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1) + if [ -z "$LAST_TAG" ]; then + echo "No prior semver tag found; cannot auto-increment version." >&2 + exit 1 + fi + BASE="${LAST_TAG#v}" + MAJOR=$(echo "$BASE" | cut -d. -f1) + MINOR=$(echo "$BASE" | cut -d. -f2) + PATCH=$(echo "$BASE" | cut -d. -f3) + NEXT="v${MAJOR}.${MINOR}.$((PATCH + 1))" + echo "tag=$NEXT" >> "$GITHUB_OUTPUT" + echo "last_tag=$LAST_TAG" >> "$GITHUB_OUTPUT" + + - name: Push version tag + env: + TAG: ${{ steps.version.outputs.tag }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag "$TAG" + git push origin "$TAG" + + - name: Create draft release with auto-generated notes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ steps.version.outputs.tag }} + LAST_TAG: ${{ steps.version.outputs.last_tag }} + run: | + gh release create "$TAG" \ + --draft \ + --title "$TAG" \ + --generate-notes \ + --notes-start-tag "$LAST_TAG" diff --git a/.github/workflows/run-e2-tests.yml b/.github/workflows/run-e2-tests.yml index e16e58fa..01a863c7 100644 --- a/.github/workflows/run-e2-tests.yml +++ b/.github/workflows/run-e2-tests.yml @@ -11,6 +11,60 @@ on: required: true pat: required: true + server_admin: + description: 'Account has server admin role' + required: false + default: 'false' + type: choice + options: ['false', 'true'] + site_admin: + description: 'Account has site admin role' + required: false + default: 'false' + type: choice + options: ['false', 'true'] + project_admin: + description: 'Account has project admin role' + required: false + default: 'false' + type: choice + options: ['false', 'true'] + extract_encryption_enabled: + description: 'Site has extract encryption enabled' + required: false + default: 'false' + type: choice + options: ['false', 'true'] + workflow_call: + inputs: + server: + required: true + type: string + site: + required: true + type: string + patname: + required: true + type: string + server_admin: + required: false + type: boolean + default: false + site_admin: + required: false + type: boolean + default: false + project_admin: + required: false + type: boolean + default: false + extract_encryption_enabled: + required: false + type: boolean + default: false + secrets: + pat: + required: true jobs: build: @@ -23,7 +77,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }} uses: actions/setup-python@v6 @@ -42,6 +96,15 @@ jobs: pip install .[test] - name: Run e2e tests - run: | - python -m tabcmd login --server "${{ github.event.inputs.server }}" --site "${{ github.event.inputs.site }}" --token-name "${{ github.event.inputs.patname }}" --token-value "${{ github.event.inputs.pat }}" + env: + E2E_SERVER: ${{ inputs.server }} + E2E_SITE: ${{ inputs.site }} + E2E_PATNAME: ${{ inputs.patname }} + E2E_PAT: ${{ secrets.pat || inputs.pat }} + E2E_SERVER_ADMIN: ${{ inputs.server_admin || 'false' }} + E2E_SITE_ADMIN: ${{ inputs.site_admin || 'false' }} + E2E_PROJECT_ADMIN: ${{ inputs.project_admin || 'false' }} + E2E_EXTRACT_ENCRYPTION: ${{ inputs.extract_encryption_enabled || 'false' }} + run: | + python -m tabcmd login --server "$E2E_SERVER" --site "$E2E_SITE" --token-name "$E2E_PATNAME" --token-value "$E2E_PAT" pytest -q tests/e2e/online_tests.py -r pfE diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 68434d9a..31b205df 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -20,7 +20,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }} uses: actions/setup-python@v6 diff --git a/tests/e2e/online_tests.py b/tests/e2e/online_tests.py index 226fbf72..8f1d50d6 100644 --- a/tests/e2e/online_tests.py +++ b/tests/e2e/online_tests.py @@ -24,11 +24,18 @@ # config variables for test run debug_log = "--logging-level=DEBUG" indexing_sleep_time = 1 # wait 1 second to confirm server has indexed updates -# Flags to let us skip tests if we know we don't have the required access -server_admin = False -site_admin = True -project_admin = False -extract_encryption_enabled = False + + +def _env_bool(name: str, default: bool = False) -> bool: + return os.environ.get(name, str(default)).lower() == "true" + + +# Flags to let us skip tests if we know we don't have the required access. +# Override by setting env vars (e.g. E2E_SITE_ADMIN=true) or via workflow inputs. +server_admin = _env_bool("E2E_SERVER_ADMIN") +site_admin = _env_bool("E2E_SITE_ADMIN") +project_admin = _env_bool("E2E_PROJECT_ADMIN") +extract_encryption_enabled = _env_bool("E2E_EXTRACT_ENCRYPTION") use_tabcmd_classic = False # toggle between testing using tabcmd 2 or tabcmd classic @@ -73,11 +80,12 @@ def get_publishable_name(file_value: str) -> str: def _test_command(test_args: list[str]): # this will raise an exception if it gets a non-zero return code # that will bubble up and fail the test + login_args = setup_e2e.get_login_args() + if login_args is None: + pytest.skip("No credentials available (credentials.py not found)") # default: run tests using tabcmd 2 - calling_args = ( - ["python", "-m", "tabcmd"] + test_args + setup_e2e.get_login_args() + [debug_log] + ["--no-certcheck"] - ) + calling_args = ["python", "-m", "tabcmd"] + test_args + login_args + [debug_log] + ["--no-certcheck"] # call the executable directly: lets us drop in classic tabcmd if use_tabcmd_classic: @@ -86,7 +94,8 @@ def _test_command(test_args: list[str]): + test_args + ["--no-certcheck"] ) - if database_password not in calling_args: + safe_to_print = not any(v in calling_args for v in login_args if v not in ("--server", "--site", "--token-name")) + if safe_to_print: print(calling_args) return subprocess.check_call(calling_args) @@ -486,6 +495,8 @@ def test_refresh_ds_extract(self): @pytest.mark.order(14) def test_delete_extract(self): + if not extract_encryption_enabled: + pytest.skip("delete-extract requires extract encryption to be enabled on the site") name_on_server = TestAssets.get_publishable_name(TestAssets.TDSX_FILE_WITH_EXTRACT) TabcmdCall._delete_extract(name_on_server, "-d") diff --git a/tests/e2e/setup_e2e.py b/tests/e2e/setup_e2e.py index eda6f791..e4d7427c 100644 --- a/tests/e2e/setup_e2e.py +++ b/tests/e2e/setup_e2e.py @@ -3,8 +3,11 @@ try: from tests.e2e import credentials # type: ignore + + _has_credentials = True except ImportError: credentials = {} # type: ignore + _has_credentials = False our_program = "tabcmd.exe" launch_path = os.path.join("dist", "tabcmd") @@ -46,6 +49,8 @@ def get_executable(): def get_login_args(): + if not _has_credentials: + return None return [ "--server", credentials.server,