Skip to content
Open
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
5 changes: 3 additions & 2 deletions .github/workflows/build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ on:
testrail-run-id:
description: The TestRail run ID
type: string
default: "0"
secrets:
SLACK_WEBHOOK:
description: Slack Notifier Incoming Webhook
Expand All @@ -25,10 +26,10 @@ on:
required: true
TESTRAIL_USERNAME:
description: TestRail username
required: true
required: false
TESTRAIL_API_KEY:
description: TestRail API key
required: true
required: false
jobs:
build-and-test:
runs-on: ubuntu-latest
Expand Down
32 changes: 15 additions & 17 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,41 +19,37 @@ on:

permissions: write-all
jobs:
# Create TestRail run
create-testrail-run:
name: Create TestRail Run
uses: ./.github/workflows/create-testrail-run.yaml
secrets:
TESTRAIL_USERNAME: ${{ secrets.TESTRAIL_USERNAME }}
TESTRAIL_API_KEY: ${{ secrets.TESTRAIL_API_KEY }}

# PR Lint
pr-lint:
name: PR Lint
uses: ./.github/workflows/pr-lint.yaml

# Copilot review
copilot-review:
name: PR Lint
needs: pr-lint
uses: ./.github/workflows/copilot-review.yaml

# Build and run unit tests
build-and-test:
name: Build and test
uses: ./.github/workflows/build-and-test.yaml
needs: create-testrail-run
needs: pr-lint
secrets:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
TESTRAIL_USERNAME: ${{ secrets.TESTRAIL_USERNAME }}
TESTRAIL_API_KEY: ${{ secrets.TESTRAIL_API_KEY }}
with:
testrail-run-id: ${{needs.create-testrail-run.outputs.testrail-run-id}}

# Run integration tests on emulators
integration-tests:
name: Integration tests
uses: ./.github/workflows/integration-tests.yaml
if: ${{ vars.ENABLE_INTEGRATION_TESTS == 'true' }}
needs: create-testrail-run
needs: pr-lint
secrets:
E2E_CONFIG: ${{ secrets.E2E_CONFIG }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
TESTRAIL_USERNAME: ${{ secrets.TESTRAIL_USERNAME }}
TESTRAIL_API_KEY: ${{ secrets.TESTRAIL_API_KEY }}
with:
testrail-run-id: ${{needs.create-testrail-run.outputs.testrail-run-id}}

# Build and sign BrowserStack test artifacts
# Skip this step for PRs created by dependabot
Expand Down Expand Up @@ -164,6 +160,7 @@ jobs:
mend-sca-scan:
name: Mend SCA Scan
uses: ./.github/workflows/mend-sca-scan.yaml
needs: pr-lint
secrets:
MEND_EMAIL: ${{ secrets.MEND_EMAIL }}
MEND_USER_KEY: ${{ secrets.MEND_USER_KEY }}
Expand All @@ -173,6 +170,7 @@ jobs:
mend-sast-scan:
name: Mend SAST Scan
uses: ./.github/workflows/mend-sast-scan.yaml
needs: pr-lint
secrets:
MEND_EMAIL: ${{ secrets.MEND_EMAIL }}
MEND_USER_KEY: ${{ secrets.MEND_USER_KEY }}
Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/copilot-review.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Copilot Review

on:
workflow_call:

jobs:
copilot-review:
runs-on: ubuntu-latest
steps:
- name: Request Copilot review
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const pull_number = context.payload.pull_request.number;

await github.rest.pulls.requestReviewers({
owner,
repo,
pull_number,
reviewers: ["github-copilot"]
});
5 changes: 3 additions & 2 deletions .github/workflows/integration-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ on:
testrail-run-id:
description: TestRail Run ID
type: string
default: "0"
secrets:
SLACK_WEBHOOK:
description: Slack Notifier Incoming Webhook
Expand All @@ -25,10 +26,10 @@ on:
required: true
TESTRAIL_USERNAME:
description: TestRail username
required: true
required: false
TESTRAIL_API_KEY:
description: TestRail API key
required: true
required: false
E2E_CONFIG:
description: 'Variables for the e2e tests'
required: true
Expand Down
169 changes: 169 additions & 0 deletions .github/workflows/pr-lint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
name: PR Lint

on:
workflow_call:

jobs:
pr-lint:
runs-on: ubuntu-latest
steps:
- name: Validate PR title + description + commit signatures
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const title = (pr.title || "").trim();
const body = (pr.body || "").trim();

// 1) Title pattern: SDKS-1234 <something>
// - allow multiple spaces, but require at least one space after ticket
const titleRe = /^SDKS-\d+\s+\S.+/;
if (!titleRe.test(title)) {
core.setFailed(
`PR title must match "SDKS-#### <description>". Got: "${title}"`
);
return;
}

// 2) Description exists
if (!body || body.length < 10) {
core.setFailed("PR description must be present and meaningful (>= 10 chars).");
return;
}

// 3) All commits are signed (GitHub verification)
// Note: "verified" is GitHub's verification result for the commit signature.
const { owner, repo } = context.repo;

// paginate commits in PR
const commits = await github.paginate(
github.rest.pulls.listCommits,
{ owner, repo, pull_number: pr.number, per_page: 100 }
);

const failures = [];
for (const c of commits) {
const sha = c.sha;
// Fetch commit detail to reliably get verification state
const commitResp = await github.rest.repos.getCommit({ owner, repo, ref: sha });
const v = commitResp.data?.commit?.verification;

const verified = v?.verified === true;
const reason = v?.reason || "unknown";

if (!verified) {
failures.push(`${sha.substring(0, 7)} (${reason})`);
}
}

if (failures.length) {
core.setFailed(
`All commits must be signed (Verified). Unsigned/unverified commits:\n- ` +
failures.join("\n- ")
);
return;
}

core.info("PR metadata + commit signature checks passed.");

- name: Checkout PR head (for copyirght header checks)
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Validate copyright headers on changed files
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
YEAR="$(date +'%Y')"
echo "Current year: ${YEAR}"

python3 - << 'PY'
import json, os, re, sys, subprocess

repo = os.environ["REPO"]
pr_number = os.environ["PR_NUMBER"]
year = subprocess.check_output(["date", "+%Y"], text=True).strip()

# Fetch changed files JSON (paginated) via gh
cmd = [
"gh","api",
"-H","Accept: application/vnd.github+json",
f"/repos/{repo}/pulls/{pr_number}/files?per_page=100",
"--paginate"
]
raw = subprocess.check_output(cmd, text=True).strip()
if not raw:
print("❌ gh api returned empty response; cannot validate files.")
sys.exit(1)

files = json.loads(raw)

skip_ext = {
".png",".jpg",".jpeg",".gif",".webp",".ico",
".pdf",".zip",".jar",".aar",".so",".dylib",
".keystore",".jks",".p12",".mobileprovision",
".ttf",".otf",".mp4",".mov",".wav",".mp3",
".lock"
}
skip_paths_re = re.compile(
r"^(?:\.github/|\.idea/|\.gradle/|build/|dist/|DerivedData/|Pods/|Carthage/|\.swiftpm/|node_modules/)"
)

# Adjust this to your canonical header text.
header_re = re.compile(
rf"Copyright\s*\(c\)\s*(?:\d{{4}}\s*-\s*)?{re.escape(year)}\s+Ping Identity Corporation\.",
re.IGNORECASE
)

bad = []
checked = []

for f in files:
status = f.get("status")
path = f.get("filename")
if status == "removed" or not path:
continue
if skip_paths_re.search(path):
continue

ext = "." + path.split(".")[-1].lower() if "." in path else ""
if ext in skip_ext:
continue

if status not in ("added","modified","renamed"):
continue

try:
data = open(path, "rb").read()
except FileNotFoundError:
# If renamed, the new path should exist; if not, skip.
continue

# Skip binary-ish
if b"\x00" in data[:4096]:
continue

text = data.decode("utf-8", errors="replace")
head = text[:3000]

checked.append(path)
if not header_re.search(head):
bad.append(path)

if bad:
print("\n❌ Copyright header check failed.")
print(f"Expected current year {year} in header for these changed files:")
for p in bad:
print(f" - {p}")
print("\nExpected header examples:")
print(f" Copyright (c) {year} Ping Identity Corporation. All rights reserved.")
print(f" Copyright (c) 2024-{year} Ping Identity Corporation. All rights reserved.")
sys.exit(1)

print(f"\n✅ Copyright header check passed for {len(checked)} file(s).")
PY
shell: bash
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/*
* Copyright (c) 2024 - 2025 Ping Identity Corporation. All rights reserved.
* Copyright (c) 2024 - 2026 Ping Identity Corporation. All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/

package com.pingidentity.android
package com.pingidentity.android

import android.annotation.SuppressLint
import android.app.Activity
Expand Down
Loading