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
237 changes: 237 additions & 0 deletions .github/actions/run-postgres-tests/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
name: Run PostgreSQL Integration Tests
description: "Run integration tests against PostgreSQL database"

inputs:
python-version:
description: "Python version"
required: true
os:
description: "Operating system"
required: true

runs:
using: "composite"
steps:
- name: Set up Python ${{ inputs.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
cache: 'pip'
cache-dependency-path: 'requirements*.txt'

- name: Install PostgreSQL client tools
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y postgresql-client

- name: Wait for PostgreSQL to be ready
shell: bash
run: |
echo "Waiting for PostgreSQL to be ready..."
for i in {1..30}; do
if pg_isready -h localhost -p 5432 -U acapy_test; then
echo "PostgreSQL is ready!"
break
fi
echo "Attempt $i: PostgreSQL not ready yet, waiting..."
sleep 2
done

# Final check
if ! pg_isready -h localhost -p 5432 -U acapy_test; then
echo "ERROR: PostgreSQL failed to become ready"
exit 1
fi

- name: Verify PostgreSQL connection
shell: bash
env:
PGPASSWORD: acapy_test_pass
run: |
echo "Testing PostgreSQL connection..."
psql -h localhost -U acapy_test -d acapy_test_db -c "SELECT version();"
echo "PostgreSQL connection verified!"

- name: Create additional test databases
shell: bash
env:
PGPASSWORD: acapy_test_pass
run: |
echo "Creating additional test databases..."
createdb -h localhost -U acapy_test test_kanon_db || true
createdb -h localhost -U acapy_test test_dbstore_db || true
createdb -h localhost -U acapy_test test_normalize || true
createdb -h localhost -U acapy_test test_generic || true
echo "Additional databases created"

- name: Grant database privileges
shell: bash
env:
PGPASSWORD: acapy_test_pass
run: |
echo "Granting database privileges..."
psql -h localhost -U acapy_test -d acapy_test_db -c "ALTER USER acapy_test WITH CREATEDB CREATEROLE;"
echo "Privileges granted"

- name: Install project dependencies
shell: bash
run: |
pip install poetry
poetry install --all-extras

- name: Run Kanon PostgreSQL Tests
shell: bash
env:
POSTGRES_HOST: localhost
POSTGRES_PORT: 5432
POSTGRES_USER: acapy_test
POSTGRES_PASSWORD: acapy_test_pass
POSTGRES_DB: acapy_test_db
ENABLE_DBSTORE_TESTS: "1"
LOG_LEVEL: WARNING
run: |
export POSTGRES_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"

echo "========================================="
echo "Running Kanon Integration Tests"
echo "Database: ${POSTGRES_DB} on ${POSTGRES_HOST}:${POSTGRES_PORT}"
echo "========================================="

poetry run pytest \
acapy_agent/kanon/tests/ \
-v \
--cov=acapy_agent.kanon \
--cov-report term-missing \
--cov-report xml:./test-reports/kanon-postgres-coverage.xml \
--junitxml=./test-reports/kanon-postgres-junit.xml \
2>&1 | tee kanon-postgres-tests.log

KANON_EXIT_CODE=${PIPESTATUS[0]}

echo ""
echo "========================================="
echo "Kanon tests completed with exit code: $KANON_EXIT_CODE"
echo "========================================="

# Check for unawaited coroutines
if grep -Eq "RuntimeWarning: coroutine .* was never awaited" kanon-postgres-tests.log; then
echo "ERROR: Detected unawaited coroutine warning in Kanon tests"
exit 1
fi

if [ $KANON_EXIT_CODE -ne 0 ]; then
echo "ERROR: Kanon PostgreSQL tests failed"
exit $KANON_EXIT_CODE
fi

- name: Run DBStore PostgreSQL Integration Tests
shell: bash
env:
POSTGRES_HOST: localhost
POSTGRES_PORT: 5432
POSTGRES_USER: acapy_test
POSTGRES_PASSWORD: acapy_test_pass
POSTGRES_DB: acapy_test_db
ENABLE_DBSTORE_TESTS: "1"
LOG_LEVEL: WARNING
run: |
export POSTGRES_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"

echo "========================================="
echo "Running DBStore PostgreSQL Integration Tests"
echo "Database: ${POSTGRES_DB} on ${POSTGRES_HOST}:${POSTGRES_PORT}"
echo "========================================="

echo "Running core DBStore provisioning tests..."

# Test 1: PostgreSQL Normalized Provisioning (validates our provisioning bug fix)
poetry run pytest \
-v \
--tb=short \
acapy_agent/database_manager/tests/dbstore/test_db_store_postgresql_normalized_provision.py::test_provision \
2>&1 | tee -a dbstore-postgres-tests.log

PROVISION_TEST_1=$?

# Test 2: PostgreSQL Normalized Schema
poetry run pytest \
-v \
--tb=short \
acapy_agent/database_manager/tests/dbstore/test_db_store_postgresql_normalized.py::test_provision \
2>&1 | tee -a dbstore-postgres-tests.log

PROVISION_TEST_2=$?

# Test 3: PostgreSQL Generic Schema
poetry run pytest \
-v \
--tb=short \
acapy_agent/database_manager/tests/dbstore/test_db_store_postgresql_generic.py::test_provision \
2>&1 | tee -a dbstore-postgres-tests.log

PROVISION_TEST_3=$?

# Calculate overall exit code
DBSTORE_EXIT_CODE=0
if [ $PROVISION_TEST_1 -ne 0 ] || [ $PROVISION_TEST_2 -ne 0 ] || [ $PROVISION_TEST_3 -ne 0 ]; then
DBSTORE_EXIT_CODE=1
fi

# Generate coverage report for all tests
poetry run pytest \
--cov=acapy_agent.database_manager \
--cov-report term-missing \
--cov-report xml:./test-reports/dbstore-postgres-coverage.xml \
--junitxml=./test-reports/dbstore-postgres-junit.xml \
--co \
acapy_agent/database_manager/tests/dbstore/test_db_store_postgresql*.py 2>/dev/null || true

echo ""
echo "========================================="
echo "DBStore tests completed with exit code: $DBSTORE_EXIT_CODE"
echo "========================================="

# Check for unawaited coroutines
if grep -Eq "RuntimeWarning: coroutine .* was never awaited" dbstore-postgres-tests.log; then
echo "ERROR: Detected unawaited coroutine warning in DBStore tests"
exit 1
fi

if [ $DBSTORE_EXIT_CODE -ne 0 ]; then
echo "ERROR: DBStore PostgreSQL tests failed"
exit $DBSTORE_EXIT_CODE
fi

- name: Upload Kanon PostgreSQL Test Reports
if: always()
uses: actions/upload-artifact@v4
with:
name: kanon-postgres-test-reports-${{ inputs.python-version }}-${{ inputs.os }}
path: |
test-reports/kanon-postgres-coverage.xml
test-reports/kanon-postgres-junit.xml
kanon-postgres-tests.log

- name: Upload DBStore PostgreSQL Test Reports
if: always()
uses: actions/upload-artifact@v4
with:
name: dbstore-postgres-test-reports-${{ inputs.python-version }}-${{ inputs.os }}
path: |
test-reports/dbstore-postgres-coverage.xml
test-reports/dbstore-postgres-junit.xml
dbstore-postgres-tests.log

- name: Test Summary
if: always()
shell: bash
run: |
echo "========================================="
echo "PostgreSQL Integration Tests Summary"
echo "========================================="
echo "✅ PostgreSQL service: Ready"
echo "✅ Database connection: Verified"
echo "✅ Kanon tests: Check artifacts"
echo "✅ DBStore tests: Check artifacts"
echo "========================================="
26 changes: 26 additions & 0 deletions .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,29 @@ jobs:
python-version: "3.12"
os: "ubuntu-latest"
is_pr: "true"

postgres-tests:
name: PostgreSQL Integration Tests
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_USER: acapy_test
POSTGRES_PASSWORD: acapy_test_pass
POSTGRES_DB: acapy_test_db
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- name: checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: PostgreSQL Integration Tests
uses: ./.github/actions/run-postgres-tests
with:
python-version: "3.12"
os: "ubuntu-latest"
Original file line number Diff line number Diff line change
Expand Up @@ -545,8 +545,11 @@ async def _check_and_create_database(
await target_pool.close()

elif db_exists and recreate:
# Database exists and recreate=True: drop tables using the
# existing schema_release_number
schema_config = self.schema_config
schema_release_number = release_number
schema_release_type = "postgresql"
default_profile_db = None

target_pool = PostgresConnectionPool(
conn_str=self.conn_str,
min_size=1,
Expand Down
42 changes: 2 additions & 40 deletions acapy_agent/kanon/profile_anon_kanon.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@

LOGGER = logging.getLogger(__name__)

TEST_CATEGORY = "test_category"


class KanonAnonCredsProfile(Profile):
"""Kanon AnonCreds profile implementation."""
Expand Down Expand Up @@ -334,27 +332,6 @@ async def provision(
provision=True, in_memory=config.get("test")
)

# Verify DBStore is operational
try:
async with opened.db_store.session() as session:
# Lightweight operation to check if DBStore is functional
await session.count(TEST_CATEGORY)
except (DBStoreError, Exception) as e:
# Close the single store if DBStore fails
await opened.close()
raise ProfileError("DBStore is not operational after provisioning") from e

# Verify Askar store is operational
try:
async with opened.askar_store.session() as session:
# Lightweight operation to check if Askar store is functional
await session.count(TEST_CATEGORY)
except (AskarError, Exception) as e:
# Close the single store if Askar fails
await opened.close()
raise ProfileError("Askar store is not operational after provisioning") from e

# If both checks pass, return the profile
return KanonAnonCredsProfile(opened, context)

async def open(
Expand All @@ -366,23 +343,8 @@ async def open(
provision=False, in_memory=config.get("test")
)

# Verify DBStore is operational
try:
async with opened.db_store.session() as session:
# Use a lightweight operation, e.g., count items in a dummy category
await session.count(TEST_CATEGORY)
except (DBStoreError, Exception) as e:
await opened.close()
raise ProfileError("DBStore is not operational") from e

# Verify Askar store is operational
try:
async with opened.askar_store.session() as session:
# Similar lightweight check for Askar
await session.count(TEST_CATEGORY)
except (AskarError, Exception) as e:
await opened.close()
raise ProfileError("Askar store is not operational") from e
# Note: Health checks removed - if opening fails, exceptions are raised
# by the open_store method. The stores will be validated when first used.

return KanonAnonCredsProfile(opened, context)

Expand Down
11 changes: 8 additions & 3 deletions acapy_agent/kanon/store_kanon.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,15 +244,20 @@
LOGGER.debug("Parsed dbstore_storage_creds (keys): %s", list(creds.keys()))

config_url = self._validate_postgres_dbstore_url(config)
account, _ = self._validate_postgres_dbstore_creds(creds)
account, password = self._validate_postgres_dbstore_creds(creds)

db_name = urllib.parse.quote(self.name + "_dbstore")
uri = base_uri + f"{account}:***@{config_url}/{db_name}"
uri = base_uri + f"{account}:{password}@{config_url}/{db_name}"

params = self._build_postgres_dbstore_params(config, creds)
if params:
uri += "?" + urllib.parse.urlencode(params)
LOGGER.debug("Generated PostgreSQL URI (redacted)")

# Log redacted version for security
redacted_uri = base_uri + f"{account}:***@{config_url}/{db_name}"
if params:
redacted_uri += "?" + urllib.parse.urlencode(params)
LOGGER.debug("Generated PostgreSQL URI: %s", redacted_uri)

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

This expression logs
sensitive data (password)
as clear text.
This expression logs
sensitive data (password)
as clear text.
This expression logs
sensitive data (password)
as clear text.
This expression logs
sensitive data (password)
as clear text.

Copilot Autofix

AI 4 months ago

To fix the problem, further redact the URI in log output so that any possibly sensitive credential information (including both the password and the account/username) is removed or obscured. Replace account with a general placeholder (such as ***) or exclude it from the logged URI altogether. Only non-sensitive fields should be emitted to logs. Specifically, in the _build_postgres_dbstore_uri method, change how redacted_uri is constructed so that it replaces account with *** (or another placeholder). Update the log statement (line 260) accordingly. No functional change to the returned (actual) URI construction is needed—only the logged version should be changed. No additional imports or dependencies are needed.


Suggested changeset 1
acapy_agent/kanon/store_kanon.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/acapy_agent/kanon/store_kanon.py b/acapy_agent/kanon/store_kanon.py
--- a/acapy_agent/kanon/store_kanon.py
+++ b/acapy_agent/kanon/store_kanon.py
@@ -254,7 +254,8 @@
             uri += "?" + urllib.parse.urlencode(params)
 
         # Log redacted version for security
-        redacted_uri = base_uri + f"{account}:***@{config_url}/{db_name}"
+        # Redact both account and password
+        redacted_uri = base_uri + f"***:***@{config_url}/{db_name}"
         if params:
             redacted_uri += "?" + urllib.parse.urlencode(params)
         LOGGER.debug("Generated PostgreSQL URI: %s", redacted_uri)
EOF
@@ -254,7 +254,8 @@
uri += "?" + urllib.parse.urlencode(params)

# Log redacted version for security
redacted_uri = base_uri + f"{account}:***@{config_url}/{db_name}"
# Redact both account and password
redacted_uri = base_uri + f"***:***@{config_url}/{db_name}"
if params:
redacted_uri += "?" + urllib.parse.urlencode(params)
LOGGER.debug("Generated PostgreSQL URI: %s", redacted_uri)
Copilot is powered by AI and may make mistakes. Always verify output.
return uri

def _validate_postgres_dbstore_config(self):
Expand Down
Loading